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