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