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: 467742 $ $Date: 2006-10-25 21:30:38 +0200 (Wed, 25 Oct 2006) $
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 private 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 private 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 }