| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package javax.activation; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.net.URL; |
| import java.security.Security; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @version $Rev$ $Date$ |
| */ |
| public class MailcapCommandMap extends CommandMap { |
| /** |
| * A string that holds all the special chars. |
| */ |
| private static final String TSPECIALS = "()<>@,;:/[]?=\\\""; |
| private final Map mimeTypes = new HashMap(); |
| private final Map preferredCommands = new HashMap(); |
| private final Map allCommands = new HashMap(); |
| // the unparsed commands from the mailcap file. |
| private final Map nativeCommands = new HashMap(); |
| // commands identified as fallbacks...these are used last, and also used as wildcards. |
| private final Map fallbackCommands = new HashMap(); |
| private URL url; |
| |
| public MailcapCommandMap() { |
| ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); |
| // process /META-INF/mailcap.default |
| try { |
| InputStream is = MailcapCommandMap.class.getResourceAsStream("/META-INF/mailcap.default"); |
| if (is != null) { |
| try { |
| parseMailcap(is); |
| } finally { |
| is.close(); |
| } |
| } |
| } catch (IOException e) { |
| // ignore |
| } |
| |
| // process /META-INF/mailcap resources |
| try { |
| Enumeration e = contextLoader.getResources("META-INF/mailcap"); |
| while (e.hasMoreElements()) { |
| url = ((URL) e.nextElement()); |
| try { |
| InputStream is = url.openStream(); |
| try { |
| parseMailcap(is); |
| } finally { |
| is.close(); |
| } |
| } catch (IOException e1) { |
| continue; |
| } |
| } |
| } catch (SecurityException e) { |
| // ignore |
| } catch (IOException e) { |
| // ignore |
| } |
| |
| // process ${java.home}/lib/mailcap |
| try { |
| File file = new File(System.getProperty("java.home"), "lib/mailcap"); |
| InputStream is = new FileInputStream(file); |
| try { |
| parseMailcap(is); |
| } finally { |
| is.close(); |
| } |
| } catch (SecurityException e) { |
| // ignore |
| } catch (IOException e) { |
| // ignore |
| } |
| |
| // process ${user.home}/lib/mailcap |
| try { |
| File file = new File(System.getProperty("user.home"), ".mailcap"); |
| InputStream is = new FileInputStream(file); |
| try { |
| parseMailcap(is); |
| } finally { |
| is.close(); |
| } |
| } catch (SecurityException e) { |
| // ignore |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| |
| public MailcapCommandMap(String fileName) throws IOException { |
| this(); |
| FileReader reader = new FileReader(fileName); |
| try { |
| parseMailcap(reader); |
| } finally { |
| reader.close(); |
| } |
| } |
| |
| public MailcapCommandMap(InputStream is) { |
| this(); |
| parseMailcap(is); |
| } |
| |
| private void parseMailcap(InputStream is) { |
| try { |
| parseMailcap(new InputStreamReader(is)); |
| } catch (IOException e) { |
| // spec API means all we can do is swallow this |
| } |
| } |
| |
| void parseMailcap(Reader reader) throws IOException { |
| BufferedReader br = new BufferedReader(reader); |
| String line; |
| while ((line = br.readLine()) != null) { |
| addMailcap(line); |
| } |
| } |
| |
| public synchronized void addMailcap(String mail_cap) { |
| int index = 0; |
| // skip leading whitespace |
| index = skipSpace(mail_cap, index); |
| if (index == mail_cap.length() || mail_cap.charAt(index) == '#') { |
| return; |
| } |
| |
| // get primary type |
| int start = index; |
| index = getToken(mail_cap, index); |
| if (start == index) { |
| return; |
| } |
| String mimeType = mail_cap.substring(start, index); |
| |
| // skip any spaces after the primary type |
| index = skipSpace(mail_cap, index); |
| if (index == mail_cap.length() || mail_cap.charAt(index) == '#') { |
| return; |
| } |
| |
| // get sub-type |
| if (mail_cap.charAt(index) == '/') { |
| index = skipSpace(mail_cap, ++index); |
| start = index; |
| index = getToken(mail_cap, index); |
| mimeType = mimeType + '/' + mail_cap.substring(start, index); |
| } else { |
| |
| mimeType = mimeType + "/*"; |
| } |
| |
| // we record all mappings using the lowercase version. |
| mimeType = mimeType.toLowerCase(); |
| |
| // skip spaces after mime type |
| index = skipSpace(mail_cap, index); |
| |
| // expect a ';' to terminate field 1 |
| if (index == mail_cap.length() || mail_cap.charAt(index) != ';') { |
| return; |
| } |
| // ok, we've parsed the mime text field, now parse the view field. If there's something |
| // there, then we add this to the native text. |
| index = skipSpace(mail_cap, index + 1); |
| // if the next encountered text is not a ";", then we have a view. This gets added to the |
| // native list. |
| if (index == mail_cap.length() || mail_cap.charAt(index) != ';') { |
| ArrayList nativeCommandList = (ArrayList)nativeCommands.get(mimeType); |
| |
| // if this is the first for this mimetype, create a holder |
| if (nativeCommandList == null) { |
| nativeCommandList = new ArrayList(); |
| nativeCommands.put(mimeType, nativeCommandList); |
| } |
| |
| // now add this as an entry in the list. |
| nativeCommandList.add(mail_cap); |
| // now skip forward to the next field marker, if any |
| index = getMText(mail_cap, index); |
| } |
| |
| // we don't know which list this will be added to until we finish parsing, as there |
| // can be an x-java-fallback-entry parameter that moves this to the fallback list. |
| List commandList = new ArrayList(); |
| // but by default, this is not a fallback. |
| boolean fallback = false; |
| |
| int fieldNumber = 0; |
| |
| // parse fields |
| while (index < mail_cap.length() && mail_cap.charAt(index) == ';') { |
| index = skipSpace(mail_cap, index + 1); |
| start = index; |
| index = getToken(mail_cap, index); |
| String fieldName = mail_cap.substring(start, index).toLowerCase(); |
| index = skipSpace(mail_cap, index); |
| if (index < mail_cap.length() && mail_cap.charAt(index) == '=') { |
| index = skipSpace(mail_cap, index + 1); |
| start = index; |
| index = getMText(mail_cap, index); |
| String value = mail_cap.substring(start, index); |
| index = skipSpace(mail_cap, index); |
| if (fieldName.startsWith("x-java-") && fieldName.length() > 7) { |
| String command = fieldName.substring(7); |
| value = value.trim(); |
| if (command.equals("fallback-entry")) { |
| if (value.equals("true")) { |
| fallback = true; |
| } |
| } |
| else { |
| // create a CommandInfo item and add it the accumulator |
| CommandInfo info = new CommandInfo(command, value); |
| commandList.add(info); |
| } |
| } |
| } |
| } |
| addCommands(mimeType, commandList, fallback); |
| } |
| |
| /** |
| * Add a parsed list of commands to the appropriate command list. |
| * |
| * @param mimeType The mimeType name this is added under. |
| * @param commands A List containing the command information. |
| * @param fallback The target list identifier. |
| */ |
| protected void addCommands(String mimeType, List commands, boolean fallback) { |
| // add this to the mimeType set |
| mimeTypes.put(mimeType, mimeType); |
| // the target list changes based on the type of entry. |
| Map target = fallback ? fallbackCommands : preferredCommands; |
| |
| // now process |
| for (Iterator i = commands.iterator(); i.hasNext();) { |
| CommandInfo info = (CommandInfo)i.next(); |
| addCommand(target, mimeType, info); |
| // if this is not a fallback position, then this to the allcommands list. |
| if (!fallback) { |
| List cmdList = (List) allCommands.get(mimeType); |
| if (cmdList == null) { |
| cmdList = new ArrayList(); |
| allCommands.put(mimeType, cmdList); |
| } |
| cmdList.add(info); |
| } |
| } |
| } |
| |
| |
| /** |
| * Add a command to a target command list (preferred or fallback). |
| * |
| * @param commandList |
| * The target command list. |
| * @param mimeType The MIME type the command is associated with. |
| * @param command The command information. |
| */ |
| protected void addCommand(Map commandList, String mimeType, CommandInfo command) { |
| |
| Map commands = (Map) commandList.get(mimeType); |
| if (commands == null) { |
| commands = new HashMap(); |
| commandList.put(mimeType, commands); |
| } |
| commands.put(command.getCommandName(), command); |
| } |
| |
| |
| private int skipSpace(String s, int index) { |
| while (index < s.length() && Character.isWhitespace(s.charAt(index))) { |
| index++; |
| } |
| return index; |
| } |
| |
| private int getToken(String s, int index) { |
| while (index < s.length() && s.charAt(index) != '#' && !isSpecialCharacter(s.charAt(index))) { |
| index++; |
| } |
| return index; |
| } |
| |
| private int getMText(String s, int index) { |
| while (index < s.length()) { |
| char c = s.charAt(index); |
| if (c == '#' || c == ';' || Character.isISOControl(c)) { |
| return index; |
| } |
| if (c == '\\') { |
| index++; |
| if (index == s.length()) { |
| return index; |
| } |
| } |
| index++; |
| } |
| return index; |
| } |
| |
| public synchronized CommandInfo[] getPreferredCommands(String mimeType) { |
| // get the mimetype as a lowercase version. |
| mimeType = mimeType.toLowerCase(); |
| |
| Map commands = (Map) preferredCommands.get(mimeType); |
| if (commands == null) { |
| commands = (Map) preferredCommands.get(getWildcardMimeType(mimeType)); |
| } |
| |
| Map fallbackCommands = getFallbackCommands(mimeType); |
| |
| // if we have fall backs, then we need to merge this stuff. |
| if (fallbackCommands != null) { |
| // if there's no command list, we can just use this as the master list. |
| if (commands == null) { |
| commands = fallbackCommands; |
| } |
| else { |
| // merge the two lists. The ones in the commands list will take precedence. |
| commands = mergeCommandMaps(commands, fallbackCommands); |
| } |
| } |
| |
| // now convert this into an array result. |
| if (commands == null) { |
| return new CommandInfo[0]; |
| } |
| return (CommandInfo[]) commands.values().toArray(new CommandInfo[commands.size()]); |
| } |
| |
| private Map getFallbackCommands(String mimeType) { |
| Map commands = (Map) fallbackCommands.get(mimeType); |
| |
| // now we also need to search this as if it was a wildcard. If we get a wildcard hit, |
| // we have to merge the two lists. |
| Map wildcardCommands = (Map)fallbackCommands.get(getWildcardMimeType(mimeType)); |
| // no wildcard version |
| if (wildcardCommands == null) { |
| return commands; |
| } |
| // we need to merge these. |
| return mergeCommandMaps(commands, wildcardCommands); |
| } |
| |
| |
| private Map mergeCommandMaps(Map main, Map fallback) { |
| // create a cloned copy of the second map. We're going to use a PutAll operation to |
| // overwrite any duplicates. |
| Map result = new HashMap(fallback); |
| result.putAll(main); |
| |
| return result; |
| } |
| |
| public synchronized CommandInfo[] getAllCommands(String mimeType) { |
| mimeType = mimeType.toLowerCase(); |
| List exactCommands = (List) allCommands.get(mimeType); |
| if (exactCommands == null) { |
| exactCommands = Collections.EMPTY_LIST; |
| } |
| List wildCommands = (List) allCommands.get(getWildcardMimeType(mimeType)); |
| if (wildCommands == null) { |
| wildCommands = Collections.EMPTY_LIST; |
| } |
| |
| Map fallbackCommands = getFallbackCommands(mimeType); |
| if (fallbackCommands == null) { |
| fallbackCommands = Collections.EMPTY_MAP; |
| } |
| |
| |
| CommandInfo[] result = new CommandInfo[exactCommands.size() + wildCommands.size() + fallbackCommands.size()]; |
| int j = 0; |
| for (int i = 0; i < exactCommands.size(); i++) { |
| result[j++] = (CommandInfo) exactCommands.get(i); |
| } |
| for (int i = 0; i < wildCommands.size(); i++) { |
| result[j++] = (CommandInfo) wildCommands.get(i); |
| } |
| |
| for (Iterator i = fallbackCommands.keySet().iterator(); i.hasNext();) { |
| result[j++] = (CommandInfo) fallbackCommands.get((String)i.next()); |
| } |
| return result; |
| } |
| |
| public synchronized CommandInfo getCommand(String mimeType, String cmdName) { |
| mimeType = mimeType.toLowerCase(); |
| // strip any parameters from the supplied mimeType |
| int i = mimeType.indexOf(';'); |
| if (i != -1) { |
| mimeType = mimeType.substring(0, i).trim(); |
| } |
| cmdName = cmdName.toLowerCase(); |
| |
| // search for an exact match |
| Map commands = (Map) preferredCommands.get(mimeType); |
| if (commands == null || commands.get(cmdName) == null) { |
| // then a wild card match |
| commands = (Map) preferredCommands.get(getWildcardMimeType(mimeType)); |
| if (commands == null || commands.get(cmdName) == null) { |
| // then fallback searches, both standard and wild card. |
| commands = (Map) fallbackCommands.get(mimeType); |
| if (commands == null || commands.get(cmdName) == null) { |
| commands = (Map) fallbackCommands.get(getWildcardMimeType(mimeType)); |
| } |
| if (commands == null) { |
| return null; |
| } |
| } |
| } |
| return (CommandInfo) commands.get(cmdName); |
| } |
| |
| private String getWildcardMimeType(String mimeType) { |
| int i = mimeType.indexOf('/'); |
| if (i == -1) { |
| return mimeType + "/*"; |
| } else { |
| return mimeType.substring(0, i + 1) + "*"; |
| } |
| } |
| |
| public synchronized DataContentHandler createDataContentHandler(String mimeType) { |
| |
| CommandInfo info = getCommand(mimeType, "content-handler"); |
| if (info == null) { |
| return null; |
| } |
| |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| if (cl == null) { |
| cl = getClass().getClassLoader(); |
| } |
| try { |
| return (DataContentHandler) cl.loadClass(info.getCommandClass()).newInstance(); |
| } catch (ClassNotFoundException e) { |
| return null; |
| } catch (IllegalAccessException e) { |
| return null; |
| } catch (InstantiationException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Get all MIME types known to this command map. |
| * |
| * @return A String array of the MIME type names. |
| */ |
| public synchronized String[] getMimeTypes() { |
| ArrayList types = new ArrayList(mimeTypes.values()); |
| return (String[])types.toArray(new String[types.size()]); |
| } |
| |
| /** |
| * Return the list of raw command strings parsed |
| * from the mailcap files for a given mimeType. |
| * |
| * @param mimeType The target mime type |
| * |
| * @return A String array of the raw command strings. Returns |
| * an empty array if the mimetype is not currently known. |
| */ |
| public synchronized String[] getNativeCommands(String mimeType) { |
| ArrayList commands = (ArrayList)nativeCommands.get(mimeType.toLowerCase()); |
| if (commands == null) { |
| return new String[0]; |
| } |
| return (String[])commands.toArray(new String[commands.size()]); |
| } |
| |
| private boolean isSpecialCharacter(char c) { |
| return Character.isWhitespace(c) || Character.isISOControl(c) || TSPECIALS.indexOf(c) != -1; |
| } |
| } |