blob: 536083fa7abd27deb15f3b88067a3932da10d547 [file] [log] [blame]
/*
* 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;
}
}