001: /*
002: * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.activation.registries;
027:
028: import java.io.*;
029: import java.util.*;
030:
031: public class MailcapFile {
032:
033: /**
034: * A Map indexed by MIME type (string) that references
035: * a Map of commands for each type. The comand Map
036: * is indexed by the command name and references a List of
037: * class names (strings) for each command.
038: */
039: private Map type_hash = new HashMap();
040:
041: /**
042: * Another Map like above, but for fallback entries.
043: */
044: private Map fallback_hash = new HashMap();
045:
046: /**
047: * A Map indexed by MIME type (string) that references
048: * a List of native commands (string) corresponding to the type.
049: */
050: private Map native_commands = new HashMap();
051:
052: private static boolean addReverse = false;
053:
054: static {
055: try {
056: addReverse = Boolean
057: .getBoolean("javax.activation.addreverse");
058: } catch (Throwable t) {
059: // ignore any errors
060: }
061: }
062:
063: /**
064: * The constructor that takes a filename as an argument.
065: *
066: * @param new_fname The file name of the mailcap file.
067: */
068: public MailcapFile(String new_fname) throws IOException {
069: if (LogSupport.isLoggable())
070: LogSupport.log("new MailcapFile: file " + new_fname);
071: FileReader reader = null;
072: try {
073: reader = new FileReader(new_fname);
074: parse(new BufferedReader(reader));
075: } finally {
076: if (reader != null) {
077: try {
078: reader.close();
079: } catch (IOException ex) {
080: }
081: }
082: }
083: }
084:
085: /**
086: * The constructor that takes an input stream as an argument.
087: *
088: * @param is the input stream
089: */
090: public MailcapFile(InputStream is) throws IOException {
091: if (LogSupport.isLoggable())
092: LogSupport.log("new MailcapFile: InputStream");
093: parse(new BufferedReader(
094: new InputStreamReader(is, "iso-8859-1")));
095: }
096:
097: /**
098: * Mailcap file default constructor.
099: */
100: public MailcapFile() {
101: if (LogSupport.isLoggable())
102: LogSupport.log("new MailcapFile: default");
103: }
104:
105: /**
106: * Get the Map of MailcapEntries based on the MIME type.
107: *
108: * <p>
109: * <strong>Semantics:</strong> First check for the literal mime type,
110: * if that fails looks for wildcard <type>/\* and return that. Return the
111: * list of all that hit.
112: */
113: public Map getMailcapList(String mime_type) {
114: Map search_result = null;
115: Map wildcard_result = null;
116:
117: // first try the literal
118: search_result = (Map) type_hash.get(mime_type);
119:
120: // ok, now try the wildcard
121: int separator = mime_type.indexOf('/');
122: String subtype = mime_type.substring(separator + 1);
123: if (!subtype.equals("*")) {
124: String type = mime_type.substring(0, separator + 1) + "*";
125: wildcard_result = (Map) type_hash.get(type);
126:
127: if (wildcard_result != null) { // damn, we have to merge!!!
128: if (search_result != null)
129: search_result = mergeResults(search_result,
130: wildcard_result);
131: else
132: search_result = wildcard_result;
133: }
134: }
135: return search_result;
136: }
137:
138: /**
139: * Get the Map of fallback MailcapEntries based on the MIME type.
140: *
141: * <p>
142: * <strong>Semantics:</strong> First check for the literal mime type,
143: * if that fails looks for wildcard <type>/\* and return that. Return the
144: * list of all that hit.
145: */
146: public Map getMailcapFallbackList(String mime_type) {
147: Map search_result = null;
148: Map wildcard_result = null;
149:
150: // first try the literal
151: search_result = (Map) fallback_hash.get(mime_type);
152:
153: // ok, now try the wildcard
154: int separator = mime_type.indexOf('/');
155: String subtype = mime_type.substring(separator + 1);
156: if (!subtype.equals("*")) {
157: String type = mime_type.substring(0, separator + 1) + "*";
158: wildcard_result = (Map) fallback_hash.get(type);
159:
160: if (wildcard_result != null) { // damn, we have to merge!!!
161: if (search_result != null)
162: search_result = mergeResults(search_result,
163: wildcard_result);
164: else
165: search_result = wildcard_result;
166: }
167: }
168: return search_result;
169: }
170:
171: /**
172: * Return all the MIME types known to this mailcap file.
173: */
174: public String[] getMimeTypes() {
175: Set types = new HashSet(type_hash.keySet());
176: types.addAll(fallback_hash.keySet());
177: types.addAll(native_commands.keySet());
178: String[] mts = new String[types.size()];
179: mts = (String[]) types.toArray(mts);
180: return mts;
181: }
182:
183: /**
184: * Return all the native comands for the given MIME type.
185: */
186: public String[] getNativeCommands(String mime_type) {
187: String[] cmds = null;
188: List v = (List) native_commands.get(mime_type.toLowerCase());
189: if (v != null) {
190: cmds = new String[v.size()];
191: cmds = (String[]) v.toArray(cmds);
192: }
193: return cmds;
194: }
195:
196: /**
197: * Merge the first hash into the second.
198: * This merge will only effect the hashtable that is
199: * returned, we don't want to touch the one passed in since
200: * its integrity must be maintained.
201: */
202: private Map mergeResults(Map first, Map second) {
203: Iterator verb_enum = second.keySet().iterator();
204: Map clonedHash = new HashMap(first);
205:
206: // iterate through the verbs in the second map
207: while (verb_enum.hasNext()) {
208: String verb = (String) verb_enum.next();
209: List cmdVector = (List) clonedHash.get(verb);
210: if (cmdVector == null) {
211: clonedHash.put(verb, second.get(verb));
212: } else {
213: // merge the two
214: List oldV = (List) second.get(verb);
215: cmdVector = new ArrayList(cmdVector);
216: cmdVector.addAll(oldV);
217: clonedHash.put(verb, cmdVector);
218: }
219: }
220: return clonedHash;
221: }
222:
223: /**
224: * appendToMailcap: Append to this Mailcap DB, use the mailcap
225: * format:
226: * Comment == "# <i>comment string</i>
227: * Entry == "mimetype; javabeanclass<nl>
228: *
229: * Example:
230: * # this is a comment
231: * image/gif jaf.viewers.ImageViewer
232: */
233: public void appendToMailcap(String mail_cap) {
234: if (LogSupport.isLoggable())
235: LogSupport.log("appendToMailcap: " + mail_cap);
236: try {
237: parse(new StringReader(mail_cap));
238: } catch (IOException ex) {
239: // can't happen
240: }
241: }
242:
243: /**
244: * parse file into a hash table of MC Type Entry Obj
245: */
246: private void parse(Reader reader) throws IOException {
247: BufferedReader buf_reader = new BufferedReader(reader);
248: String line = null;
249: String continued = null;
250:
251: while ((line = buf_reader.readLine()) != null) {
252: // LogSupport.log("parsing line: " + line);
253:
254: line = line.trim();
255:
256: try {
257: if (line.charAt(0) == '#')
258: continue;
259: if (line.charAt(line.length() - 1) == '\\') {
260: if (continued != null)
261: continued += line.substring(0,
262: line.length() - 1);
263: else
264: continued = line
265: .substring(0, line.length() - 1);
266: } else if (continued != null) {
267: // handle the two strings
268: continued = continued + line;
269: // LogSupport.log("parse: " + continued);
270: try {
271: parseLine(continued);
272: } catch (MailcapParseException e) {
273: //e.printStackTrace();
274: }
275: continued = null;
276: } else {
277: // LogSupport.log("parse: " + line);
278: try {
279: parseLine(line);
280: // LogSupport.log("hash.size = " + type_hash.size());
281: } catch (MailcapParseException e) {
282: //e.printStackTrace();
283: }
284: }
285: } catch (StringIndexOutOfBoundsException e) {
286: }
287: }
288: }
289:
290: /**
291: * A routine to parse individual entries in a Mailcap file.
292: *
293: * Note that this routine does not handle line continuations.
294: * They should have been handled prior to calling this routine.
295: */
296: protected void parseLine(String mailcapEntry)
297: throws MailcapParseException, IOException {
298: MailcapTokenizer tokenizer = new MailcapTokenizer(mailcapEntry);
299: tokenizer.setIsAutoquoting(false);
300:
301: if (LogSupport.isLoggable())
302: LogSupport.log("parse: " + mailcapEntry);
303: // parse the primary type
304: int currentToken = tokenizer.nextToken();
305: if (currentToken != MailcapTokenizer.STRING_TOKEN) {
306: reportParseError(MailcapTokenizer.STRING_TOKEN,
307: currentToken, tokenizer.getCurrentTokenValue());
308: }
309: String primaryType = tokenizer.getCurrentTokenValue()
310: .toLowerCase();
311: String subType = "*";
312:
313: // parse the '/' between primary and sub
314: // if it's not present that's ok, we just don't have a subtype
315: currentToken = tokenizer.nextToken();
316: if ((currentToken != MailcapTokenizer.SLASH_TOKEN)
317: && (currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) {
318: reportParseError(MailcapTokenizer.SLASH_TOKEN,
319: MailcapTokenizer.SEMICOLON_TOKEN, currentToken,
320: tokenizer.getCurrentTokenValue());
321: }
322:
323: // only need to look for a sub type if we got a '/'
324: if (currentToken == MailcapTokenizer.SLASH_TOKEN) {
325: // parse the sub type
326: currentToken = tokenizer.nextToken();
327: if (currentToken != MailcapTokenizer.STRING_TOKEN) {
328: reportParseError(MailcapTokenizer.STRING_TOKEN,
329: currentToken, tokenizer.getCurrentTokenValue());
330: }
331: subType = tokenizer.getCurrentTokenValue().toLowerCase();
332:
333: // get the next token to simplify the next step
334: currentToken = tokenizer.nextToken();
335: }
336:
337: String mimeType = primaryType + "/" + subType;
338:
339: if (LogSupport.isLoggable())
340: LogSupport.log(" Type: " + mimeType);
341:
342: // now setup the commands hashtable
343: Map commands = new LinkedHashMap(); // keep commands in order found
344:
345: // parse the ';' that separates the type from the parameters
346: if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) {
347: reportParseError(MailcapTokenizer.SEMICOLON_TOKEN,
348: currentToken, tokenizer.getCurrentTokenValue());
349: }
350: // eat it
351:
352: // parse the required view command
353: tokenizer.setIsAutoquoting(true);
354: currentToken = tokenizer.nextToken();
355: tokenizer.setIsAutoquoting(false);
356: if ((currentToken != MailcapTokenizer.STRING_TOKEN)
357: && (currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) {
358: reportParseError(MailcapTokenizer.STRING_TOKEN,
359: MailcapTokenizer.SEMICOLON_TOKEN, currentToken,
360: tokenizer.getCurrentTokenValue());
361: }
362:
363: if (currentToken == MailcapTokenizer.STRING_TOKEN) {
364: // have a native comand, save the entire mailcap entry
365: //String nativeCommand = tokenizer.getCurrentTokenValue();
366: List v = (List) native_commands.get(mimeType);
367: if (v == null) {
368: v = new ArrayList();
369: v.add(mailcapEntry);
370: native_commands.put(mimeType, v);
371: } else {
372: // XXX - check for duplicates?
373: v.add(mailcapEntry);
374: }
375: }
376:
377: // only have to get the next token if the current one isn't a ';'
378: if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) {
379: currentToken = tokenizer.nextToken();
380: }
381:
382: // look for a ';' which will indicate whether
383: // a parameter list is present or not
384: if (currentToken == MailcapTokenizer.SEMICOLON_TOKEN) {
385: boolean isFallback = false;
386: do {
387: // eat the ';'
388:
389: // parse the parameter name
390: currentToken = tokenizer.nextToken();
391: if (currentToken != MailcapTokenizer.STRING_TOKEN) {
392: reportParseError(MailcapTokenizer.STRING_TOKEN,
393: currentToken, tokenizer
394: .getCurrentTokenValue());
395: }
396: String paramName = tokenizer.getCurrentTokenValue()
397: .toLowerCase();
398:
399: // parse the '=' which separates the name from the value
400: currentToken = tokenizer.nextToken();
401: if ((currentToken != MailcapTokenizer.EQUALS_TOKEN)
402: && (currentToken != MailcapTokenizer.SEMICOLON_TOKEN)
403: && (currentToken != MailcapTokenizer.EOI_TOKEN)) {
404: reportParseError(MailcapTokenizer.EQUALS_TOKEN,
405: MailcapTokenizer.SEMICOLON_TOKEN,
406: MailcapTokenizer.EOI_TOKEN, currentToken,
407: tokenizer.getCurrentTokenValue());
408: }
409:
410: // we only have a useful command if it is named
411: if (currentToken == MailcapTokenizer.EQUALS_TOKEN) {
412: // eat it
413:
414: // parse the parameter value (which is autoquoted)
415: tokenizer.setIsAutoquoting(true);
416: currentToken = tokenizer.nextToken();
417: tokenizer.setIsAutoquoting(false);
418: if (currentToken != MailcapTokenizer.STRING_TOKEN) {
419: reportParseError(MailcapTokenizer.STRING_TOKEN,
420: currentToken, tokenizer
421: .getCurrentTokenValue());
422: }
423: String paramValue = tokenizer
424: .getCurrentTokenValue();
425:
426: // add the class to the list iff it is one we care about
427: if (paramName.startsWith("x-java-")) {
428: String commandName = paramName.substring(7);
429: // 7 == "x-java-".length
430:
431: if (commandName.equals("fallback-entry")
432: && paramValue.equalsIgnoreCase("true")) {
433: isFallback = true;
434: } else {
435:
436: // setup the class entry list
437: if (LogSupport.isLoggable())
438: LogSupport.log(" Command: "
439: + commandName + ", Class: "
440: + paramValue);
441: List classes = (List) commands
442: .get(commandName);
443: if (classes == null) {
444: classes = new ArrayList();
445: commands.put(commandName, classes);
446: }
447: if (addReverse)
448: classes.add(0, paramValue);
449: else
450: classes.add(paramValue);
451: }
452: }
453:
454: // set up the next iteration
455: currentToken = tokenizer.nextToken();
456: }
457: } while (currentToken == MailcapTokenizer.SEMICOLON_TOKEN);
458:
459: Map masterHash = isFallback ? fallback_hash : type_hash;
460: Map curcommands = (Map) masterHash.get(mimeType);
461: if (curcommands == null) {
462: masterHash.put(mimeType, commands);
463: } else {
464: if (LogSupport.isLoggable())
465: LogSupport.log("Merging commands for type "
466: + mimeType);
467: // have to merge current and new commands
468: // first, merge list of classes for commands already known
469: Iterator cn = curcommands.keySet().iterator();
470: while (cn.hasNext()) {
471: String cmdName = (String) cn.next();
472: List ccv = (List) curcommands.get(cmdName);
473: List cv = (List) commands.get(cmdName);
474: if (cv == null)
475: continue;
476: // add everything in cv to ccv, if it's not already there
477: Iterator cvn = cv.iterator();
478: while (cvn.hasNext()) {
479: String clazz = (String) cvn.next();
480: if (!ccv.contains(clazz))
481: if (addReverse)
482: ccv.add(0, clazz);
483: else
484: ccv.add(clazz);
485: }
486: }
487: // now, add commands not previously known
488: cn = commands.keySet().iterator();
489: while (cn.hasNext()) {
490: String cmdName = (String) cn.next();
491: if (curcommands.containsKey(cmdName))
492: continue;
493: List cv = (List) commands.get(cmdName);
494: curcommands.put(cmdName, cv);
495: }
496: }
497: } else if (currentToken != MailcapTokenizer.EOI_TOKEN) {
498: reportParseError(MailcapTokenizer.EOI_TOKEN,
499: MailcapTokenizer.SEMICOLON_TOKEN, currentToken,
500: tokenizer.getCurrentTokenValue());
501: }
502: }
503:
504: protected static void reportParseError(int expectedToken,
505: int actualToken, String actualTokenValue)
506: throws MailcapParseException {
507: throw new MailcapParseException("Encountered a "
508: + MailcapTokenizer.nameForToken(actualToken)
509: + " token (" + actualTokenValue
510: + ") while expecting a "
511: + MailcapTokenizer.nameForToken(expectedToken)
512: + " token.");
513: }
514:
515: protected static void reportParseError(int expectedToken,
516: int otherExpectedToken, int actualToken,
517: String actualTokenValue) throws MailcapParseException {
518: throw new MailcapParseException("Encountered a "
519: + MailcapTokenizer.nameForToken(actualToken)
520: + " token (" + actualTokenValue
521: + ") while expecting a "
522: + MailcapTokenizer.nameForToken(expectedToken)
523: + " or a "
524: + MailcapTokenizer.nameForToken(otherExpectedToken)
525: + " token.");
526: }
527:
528: protected static void reportParseError(int expectedToken,
529: int otherExpectedToken, int anotherExpectedToken,
530: int actualToken, String actualTokenValue)
531: throws MailcapParseException {
532: if (LogSupport.isLoggable())
533: LogSupport.log("PARSE ERROR: "
534: + "Encountered a "
535: + MailcapTokenizer.nameForToken(actualToken)
536: + " token ("
537: + actualTokenValue
538: + ") while expecting a "
539: + MailcapTokenizer.nameForToken(expectedToken)
540: + ", a "
541: + MailcapTokenizer.nameForToken(otherExpectedToken)
542: + ", or a "
543: + MailcapTokenizer
544: .nameForToken(anotherExpectedToken)
545: + " token.");
546: throw new MailcapParseException("Encountered a "
547: + MailcapTokenizer.nameForToken(actualToken)
548: + " token (" + actualTokenValue
549: + ") while expecting a "
550: + MailcapTokenizer.nameForToken(expectedToken) + ", a "
551: + MailcapTokenizer.nameForToken(otherExpectedToken)
552: + ", or a "
553: + MailcapTokenizer.nameForToken(anotherExpectedToken)
554: + " token.");
555: }
556:
557: /** for debugging
558: public static void main(String[] args) throws Exception {
559: Map masterHash = new HashMap();
560: for (int i = 0; i < args.length; ++i) {
561: System.out.println("Entry " + i + ": " + args[i]);
562: parseLine(args[i], masterHash);
563: }
564:
565: Enumeration types = masterHash.keys();
566: while (types.hasMoreElements()) {
567: String key = (String)types.nextElement();
568: System.out.println("MIME Type: " + key);
569:
570: Map commandHash = (Map)masterHash.get(key);
571: Enumeration commands = commandHash.keys();
572: while (commands.hasMoreElements()) {
573: String command = (String)commands.nextElement();
574: System.out.println(" Command: " + command);
575:
576: Vector classes = (Vector)commandHash.get(command);
577: for (int i = 0; i < classes.size(); ++i) {
578: System.out.println(" Class: " +
579: (String)classes.elementAt(i));
580: }
581: }
582:
583: System.out.println("");
584: }
585: }
586: */
587: }
|