001 /** 002 * 003 * Copyright 2003-2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package javax.activation; 019 020 import java.io.BufferedReader; 021 import java.io.File; 022 import java.io.FileInputStream; 023 import java.io.FileReader; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.io.InputStreamReader; 027 import java.io.Reader; 028 import java.net.URL; 029 import java.security.Security; 030 import java.util.ArrayList; 031 import java.util.Collections; 032 import java.util.Enumeration; 033 import java.util.HashMap; 034 import java.util.Iterator; 035 import java.util.List; 036 import java.util.Map; 037 038 /** 039 * @version $Rev: 376811 $ $Date: 2006-02-10 11:40:13 -0800 (Fri, 10 Feb 2006) $ 040 */ 041 public class MailcapCommandMap extends CommandMap { 042 private final Map preferredCommands = new HashMap(); 043 private final Map allCommands = new HashMap(); 044 // commands identified as fallbacks...these are used last, and also used as wildcards. 045 private final Map fallbackCommands = new HashMap(); 046 private URL url; 047 048 public MailcapCommandMap() { 049 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); 050 // process /META-INF/mailcap.default 051 try { 052 InputStream is = MailcapCommandMap.class.getResourceAsStream("/META-INF/mailcap.default"); 053 if (is != null) { 054 try { 055 parseMailcap(is); 056 } finally { 057 is.close(); 058 } 059 } 060 } catch (IOException e) { 061 // ignore 062 } 063 064 // process /META-INF/mailcap resources 065 try { 066 Enumeration e = contextLoader.getResources("META-INF/mailcap"); 067 while (e.hasMoreElements()) { 068 url = ((URL) e.nextElement()); 069 try { 070 InputStream is = url.openStream(); 071 try { 072 parseMailcap(is); 073 } finally { 074 is.close(); 075 } 076 } catch (IOException e1) { 077 continue; 078 } 079 } 080 } catch (SecurityException e) { 081 // ignore 082 } catch (IOException e) { 083 // ignore 084 } 085 086 // process ${java.home}/lib/mailcap 087 try { 088 File file = new File(System.getProperty("java.home"), "lib/mailcap"); 089 InputStream is = new FileInputStream(file); 090 try { 091 parseMailcap(is); 092 } finally { 093 is.close(); 094 } 095 } catch (SecurityException e) { 096 // ignore 097 } catch (IOException e) { 098 // ignore 099 } 100 101 // process ${user.home}/lib/mailcap 102 try { 103 File file = new File(System.getProperty("user.home"), ".mailcap"); 104 InputStream is = new FileInputStream(file); 105 try { 106 parseMailcap(is); 107 } finally { 108 is.close(); 109 } 110 } catch (SecurityException e) { 111 // ignore 112 } catch (IOException e) { 113 // ignore 114 } 115 } 116 117 public MailcapCommandMap(String fileName) throws IOException { 118 this(); 119 FileReader reader = new FileReader(fileName); 120 try { 121 parseMailcap(reader); 122 } finally { 123 reader.close(); 124 } 125 } 126 127 public MailcapCommandMap(InputStream is) { 128 this(); 129 parseMailcap(is); 130 } 131 132 private void parseMailcap(InputStream is) { 133 try { 134 parseMailcap(new InputStreamReader(is)); 135 } catch (IOException e) { 136 // spec API means all we can do is swallow this 137 } 138 } 139 140 void parseMailcap(Reader reader) throws IOException { 141 BufferedReader br = new BufferedReader(reader); 142 String line; 143 while ((line = br.readLine()) != null) { 144 addMailcap(line); 145 } 146 } 147 148 public synchronized void addMailcap(String mail_cap) { 149 int index = 0; 150 // skip leading whitespace 151 index = skipSpace(mail_cap, index); 152 if (index == mail_cap.length() || mail_cap.charAt(index) == '#') { 153 return; 154 } 155 156 // get primary type 157 int start = index; 158 index = getToken(mail_cap, index); 159 if (start == index) { 160 return; 161 } 162 String mimeType = mail_cap.substring(start, index); 163 164 // skip any spaces after the primary type 165 index = skipSpace(mail_cap, index); 166 if (index == mail_cap.length() || mail_cap.charAt(index) == '#') { 167 return; 168 } 169 170 // get sub-type 171 if (mail_cap.charAt(index) == '/') { 172 index = skipSpace(mail_cap, ++index); 173 start = index; 174 index = getToken(mail_cap, index); 175 mimeType = mimeType + '/' + mail_cap.substring(start, index); 176 } else { 177 178 mimeType = mimeType + "/*"; 179 } 180 181 // we record all mappings using the lowercase version. 182 mimeType = mimeType.toLowerCase(); 183 184 // skip spaces after mime type 185 index = skipSpace(mail_cap, index); 186 187 // expect a ';' to terminate field 1 188 if (index == mail_cap.length() || mail_cap.charAt(index) != ';') { 189 return; 190 } 191 index = getMText(mail_cap, index); 192 // expect a ';' to terminate field 2 193 if (index == mail_cap.length() || mail_cap.charAt(index) != ';') { 194 return; 195 } 196 197 // we don't know which list this will be added to until we finish parsing, as there 198 // can be an x-java-fallback-entry parameter that moves this to the fallback list. 199 List commandList = new ArrayList(); 200 // but by default, this is not a fallback. 201 boolean fallback = false; 202 203 // parse fields 204 while (index < mail_cap.length() && mail_cap.charAt(index) == ';') { 205 index = skipSpace(mail_cap, index + 1); 206 start = index; 207 index = getToken(mail_cap, index); 208 String fieldName = mail_cap.substring(start, index).toLowerCase(); 209 index = skipSpace(mail_cap, index); 210 if (index < mail_cap.length() && mail_cap.charAt(index) == '=') { 211 index = skipSpace(mail_cap, index + 1); 212 start = index; 213 index = getMText(mail_cap, index); 214 String value = mail_cap.substring(start, index); 215 index = skipSpace(mail_cap, index); 216 if (fieldName.startsWith("x-java-") && fieldName.length() > 7) { 217 String command = fieldName.substring(7); 218 value = value.trim(); 219 if (command.equals("fallback-entry")) { 220 if (value.equals("true")) { 221 fallback = true; 222 } 223 } 224 else { 225 // create a CommandInfo item and add it the accumulator 226 CommandInfo info = new CommandInfo(command, value); 227 commandList.add(info); 228 } 229 } 230 } 231 } 232 addCommands(mimeType, commandList, fallback); 233 } 234 235 /** 236 * Add a parsed list of commands to the appropriate command list. 237 * 238 * @param mimeType The mimeType name this is added under. 239 * @param commands A List containing the command information. 240 * @param fallback The target list identifier. 241 */ 242 private void addCommands(String mimeType, List commands, boolean fallback) { 243 // the target list changes based on the type of entry. 244 Map target = fallback ? fallbackCommands : preferredCommands; 245 246 // now process 247 for (Iterator i = commands.iterator(); i.hasNext();) { 248 CommandInfo info = (CommandInfo)i.next(); 249 addCommand(target, mimeType, info); 250 // if this is not a fallback position, then this to the allcommands list. 251 if (!fallback) { 252 List cmdList = (List) allCommands.get(mimeType); 253 if (cmdList == null) { 254 cmdList = new ArrayList(); 255 allCommands.put(mimeType, cmdList); 256 } 257 cmdList.add(info); 258 } 259 } 260 } 261 262 263 /** 264 * Add a command to a target command list (preferred or fallback). 265 * 266 * @param commandList 267 * The target command list. 268 * @param mimeType The MIME type the command is associated with. 269 * @param command The command information. 270 */ 271 private void addCommand(Map commandList, String mimeType, CommandInfo command) { 272 273 Map commands = (Map) commandList.get(mimeType); 274 if (commands == null) { 275 commands = new HashMap(); 276 commandList.put(mimeType, commands); 277 } 278 commands.put(command.getCommandName(), command); 279 } 280 281 282 private int skipSpace(String s, int index) { 283 while (index < s.length() && Character.isWhitespace(s.charAt(index))) { 284 index++; 285 } 286 return index; 287 } 288 289 private int getToken(String s, int index) { 290 while (index < s.length() && s.charAt(index) != '#' && !MimeType.isSpecial(s.charAt(index))) { 291 index++; 292 } 293 return index; 294 } 295 296 private int getMText(String s, int index) { 297 while (index < s.length()) { 298 char c = s.charAt(index); 299 if (c == '#' || c == ';' || Character.isISOControl(c)) { 300 return index; 301 } 302 if (c == '\\') { 303 index++; 304 if (index == s.length()) { 305 return index; 306 } 307 } 308 index++; 309 } 310 return index; 311 } 312 313 public synchronized CommandInfo[] getPreferredCommands(String mimeType) { 314 // get the mimetype as a lowercase version. 315 mimeType = mimeType.toLowerCase(); 316 317 Map commands = (Map) preferredCommands.get(mimeType); 318 if (commands == null) { 319 commands = (Map) preferredCommands.get(getWildcardMimeType(mimeType)); 320 } 321 322 Map fallbackCommands = getFallbackCommands(mimeType); 323 324 // if we have fall backs, then we need to merge this stuff. 325 if (fallbackCommands != null) { 326 // if there's no command list, we can just use this as the master list. 327 if (commands == null) { 328 commands = fallbackCommands; 329 } 330 else { 331 // merge the two lists. The ones in the commands list will take precedence. 332 commands = mergeCommandMaps(commands, fallbackCommands); 333 } 334 } 335 336 // now convert this into an array result. 337 if (commands == null) { 338 return new CommandInfo[0]; 339 } 340 return (CommandInfo[]) commands.values().toArray(new CommandInfo[commands.size()]); 341 } 342 343 private Map getFallbackCommands(String mimeType) { 344 Map commands = (Map) fallbackCommands.get(mimeType); 345 346 // now we also need to search this as if it was a wildcard. If we get a wildcard hit, 347 // we have to merge the two lists. 348 Map wildcardCommands = (Map)fallbackCommands.get(getWildcardMimeType(mimeType)); 349 // no wildcard version 350 if (wildcardCommands == null) { 351 return commands; 352 } 353 // we need to merge these. 354 return mergeCommandMaps(commands, wildcardCommands); 355 } 356 357 358 private Map mergeCommandMaps(Map main, Map fallback) { 359 // create a cloned copy of the second map. We're going to use a PutAll operation to 360 // overwrite any duplicates. 361 Map result = new HashMap(fallback); 362 result.putAll(main); 363 364 return result; 365 } 366 367 public synchronized CommandInfo[] getAllCommands(String mimeType) { 368 mimeType = mimeType.toLowerCase(); 369 List exactCommands = (List) allCommands.get(mimeType); 370 if (exactCommands == null) { 371 exactCommands = Collections.EMPTY_LIST; 372 } 373 List wildCommands = (List) allCommands.get(getWildcardMimeType(mimeType)); 374 if (wildCommands == null) { 375 wildCommands = Collections.EMPTY_LIST; 376 } 377 378 Map fallbackCommands = getFallbackCommands(mimeType); 379 if (fallbackCommands == null) { 380 fallbackCommands = Collections.EMPTY_MAP; 381 } 382 383 384 CommandInfo[] result = new CommandInfo[exactCommands.size() + wildCommands.size() + fallbackCommands.size()]; 385 int j = 0; 386 for (int i = 0; i < exactCommands.size(); i++) { 387 result[j++] = (CommandInfo) exactCommands.get(i); 388 } 389 for (int i = 0; i < wildCommands.size(); i++) { 390 result[j++] = (CommandInfo) wildCommands.get(i); 391 } 392 393 for (Iterator i = fallbackCommands.keySet().iterator(); i.hasNext();) { 394 result[j++] = (CommandInfo) fallbackCommands.get((String)i.next()); 395 } 396 return result; 397 } 398 399 public synchronized CommandInfo getCommand(String mimeType, String cmdName) { 400 mimeType = mimeType.toLowerCase(); 401 // strip any parameters from the supplied mimeType 402 int i = mimeType.indexOf(';'); 403 if (i != -1) { 404 mimeType = mimeType.substring(0, i).trim(); 405 } 406 407 // search for an exact match 408 Map commands = (Map) preferredCommands.get(mimeType); 409 if (commands == null) { 410 // then a wild card match 411 commands = (Map) preferredCommands.get(getWildcardMimeType(mimeType)); 412 if (commands == null) { 413 // then fallback searches, both standard and wild card. 414 commands = (Map) fallbackCommands.get(mimeType); 415 if (commands == null) { 416 commands = (Map) fallbackCommands.get(getWildcardMimeType(mimeType)); 417 } 418 if (commands == null) { 419 return null; 420 } 421 } 422 } 423 return (CommandInfo) commands.get(cmdName.toLowerCase()); 424 } 425 426 private String getWildcardMimeType(String mimeType) { 427 int i = mimeType.indexOf('/'); 428 if (i == -1) { 429 return mimeType + "/*"; 430 } else { 431 return mimeType.substring(0, i + 1) + "*"; 432 } 433 } 434 435 public synchronized DataContentHandler createDataContentHandler(String mimeType) { 436 437 CommandInfo info = getCommand(mimeType, "content-handler"); 438 if (info == null) { 439 return null; 440 } 441 442 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 443 if (cl == null) { 444 cl = getClass().getClassLoader(); 445 } 446 try { 447 return (DataContentHandler) cl.loadClass(info.getCommandClass()).newInstance(); 448 } catch (ClassNotFoundException e) { 449 return null; 450 } catch (IllegalAccessException e) { 451 return null; 452 } catch (InstantiationException e) { 453 return null; 454 } 455 } 456 }