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