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