MailcapCommandMap.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. /* MailcapCommandMap.java -- Command map implementation using a mailcap file.
  2. Copyright (C) 2004 Free Software Foundation, Inc.
  3. This file is part of GNU Classpath.
  4. GNU Classpath is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2, or (at your option)
  7. any later version.
  8. GNU Classpath is distributed in the hope that it will be useful, but
  9. WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with GNU Classpath; see the file COPYING. If not, write to the
  14. Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  15. 02110-1301 USA.
  16. Linking this library statically or dynamically with other modules is
  17. making a combined work based on this library. Thus, the terms and
  18. conditions of the GNU General Public License cover the whole
  19. combination.
  20. As a special exception, the copyright holders of this library give you
  21. permission to link this library with independent modules to produce an
  22. executable, regardless of the license terms of these independent
  23. modules, and to copy and distribute the resulting executable under
  24. terms of your choice, provided that you also meet, for each linked
  25. independent module, the terms and conditions of the license of that
  26. module. An independent module is a module which is not derived from
  27. or based on this library. If you modify this library, you may extend
  28. this exception to your version of the library, but you are not
  29. obligated to do so. If you do not wish to do so, delete this
  30. exception statement from your version. */
  31. package javax.activation;
  32. import gnu.java.lang.CPStringBuilder;
  33. import java.io.BufferedReader;
  34. import java.io.File;
  35. import java.io.FileReader;
  36. import java.io.InputStream;
  37. import java.io.InputStreamReader;
  38. import java.io.IOException;
  39. import java.io.Reader;
  40. import java.io.StringReader;
  41. import java.net.URL;
  42. import java.util.ArrayList;
  43. import java.util.Enumeration;
  44. import java.util.LinkedHashMap;
  45. import java.util.Iterator;
  46. import java.util.List;
  47. import java.util.Map;
  48. /**
  49. * Implementation of a command map using a <code>mailcap</code> file (RFC
  50. * 1524). Mailcap files are searched for in the following places:
  51. * <ol>
  52. * <li>Programmatically added entries to this interface</li>
  53. * <li>the file <tt>.mailcap</tt> in the user's home directory</li>
  54. * <li>the file <i>&lt;java.home&gt;</i><tt>/lib/mailcap</tt></li>
  55. * <li>the resource <tt>META-INF/mailcap</tt></li>
  56. * <li>the resource <tt>META-INF/mailcap.default</tt> in the JAF
  57. * distribution</li>
  58. * </ol>
  59. *
  60. * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  61. * @version 1.1
  62. */
  63. public class MailcapCommandMap
  64. extends CommandMap
  65. {
  66. private static final int PROG = 0;
  67. private static final int HOME = 1;
  68. private static final int SYS = 2;
  69. private static final int JAR = 3;
  70. private static final int DEF = 4;
  71. private static boolean debug = false;
  72. private static final int NORMAL = 0;
  73. private static final int FALLBACK = 1;
  74. static
  75. {
  76. try
  77. {
  78. String d = System.getProperty("javax.activation.debug");
  79. debug = Boolean.valueOf(d).booleanValue();
  80. }
  81. catch (SecurityException e)
  82. {
  83. }
  84. }
  85. private Map<String,Map<String,List<String>>>[][] mailcaps;
  86. /**
  87. * Default constructor.
  88. */
  89. public MailcapCommandMap()
  90. {
  91. init(null);
  92. }
  93. /**
  94. * Constructor specifying a filename.
  95. * @param fileName the name of the file to read mailcap entries from
  96. */
  97. public MailcapCommandMap(String fileName)
  98. throws IOException
  99. {
  100. Reader in = null;
  101. try
  102. {
  103. in = new FileReader(fileName);
  104. }
  105. catch (IOException e)
  106. {
  107. }
  108. init(in);
  109. if (in != null)
  110. {
  111. try
  112. {
  113. in.close();
  114. }
  115. catch (IOException e)
  116. {
  117. }
  118. }
  119. }
  120. /**
  121. * Constructor specifying an input stream.
  122. * @param is the input stream to read mailcap entries from
  123. */
  124. public MailcapCommandMap(InputStream is)
  125. {
  126. init(new InputStreamReader(is));
  127. }
  128. private void init(Reader in)
  129. {
  130. mailcaps = new Map[5][2];
  131. for (int i = 0; i < 5; i++)
  132. {
  133. for (int j = 0; j < 2; j++)
  134. {
  135. mailcaps[i][j] =
  136. new LinkedHashMap<String,Map<String,List<String>>>();
  137. }
  138. }
  139. if (in != null)
  140. {
  141. if (debug)
  142. {
  143. System.out.println("MailcapCommandMap: load PROG");
  144. }
  145. try
  146. {
  147. parse(PROG, in);
  148. }
  149. catch (IOException e)
  150. {
  151. }
  152. }
  153. if (debug)
  154. {
  155. System.out.println("MailcapCommandMap: load HOME");
  156. }
  157. try
  158. {
  159. String home = System.getProperty("user.home");
  160. if (home != null)
  161. {
  162. parseFile(HOME, new CPStringBuilder(home)
  163. .append(File.separatorChar)
  164. .append(".mailcap")
  165. .toString());
  166. }
  167. }
  168. catch (SecurityException e)
  169. {
  170. }
  171. if (debug)
  172. {
  173. System.out.println("MailcapCommandMap: load SYS");
  174. }
  175. try
  176. {
  177. parseFile(SYS,
  178. new CPStringBuilder(System.getProperty("java.home"))
  179. .append(File.separatorChar)
  180. .append("lib")
  181. .append(File.separatorChar)
  182. .append("mailcap")
  183. .toString());
  184. }
  185. catch (SecurityException e)
  186. {
  187. }
  188. if (debug)
  189. {
  190. System.out.println("MailcapCommandMap: load JAR");
  191. }
  192. List<URL> systemResources = getSystemResources("META-INF/mailcap");
  193. int len = systemResources.size();
  194. if (len > 0)
  195. {
  196. for (int i = 0; i < len ; i++)
  197. {
  198. Reader urlIn = null;
  199. URL url = systemResources.get(i);
  200. try
  201. {
  202. if (debug)
  203. {
  204. System.out.println("\t" + url.toString());
  205. }
  206. urlIn = new InputStreamReader(url.openStream());
  207. parse(JAR, urlIn);
  208. }
  209. catch (IOException e)
  210. {
  211. if (debug)
  212. {
  213. System.out.println(e.getClass().getName() + ": " +
  214. e.getMessage());
  215. }
  216. }
  217. finally
  218. {
  219. if (urlIn != null)
  220. {
  221. try
  222. {
  223. urlIn.close();
  224. }
  225. catch (IOException e)
  226. {
  227. }
  228. }
  229. }
  230. }
  231. }
  232. else
  233. {
  234. parseResource(JAR, "/META-INF/mailcap");
  235. }
  236. if (debug)
  237. {
  238. System.out.println("MailcapCommandMap: load DEF");
  239. }
  240. parseResource(DEF, "/META-INF/mailcap.default");
  241. }
  242. /**
  243. * Returns the list of preferred commands for a given MIME type.
  244. * @param mimeType the MIME type
  245. */
  246. public synchronized CommandInfo[] getPreferredCommands(String mimeType)
  247. {
  248. List<CommandInfo> cmdList = new ArrayList<CommandInfo>();
  249. List<String> verbList = new ArrayList<String>();
  250. for (int i = 0; i < 2; i++)
  251. {
  252. for (int j = 0; j < 5; j++)
  253. {
  254. Map<String,List<String>> map = getCommands(mailcaps[j][i], mimeType);
  255. if (map != null)
  256. {
  257. for (Map.Entry<String,List<String>> entry : map.entrySet())
  258. {
  259. String verb = entry.getKey();
  260. if (!verbList.contains(verb))
  261. {
  262. List<String> classNames = entry.getValue();
  263. String className = classNames.get(0);
  264. CommandInfo cmd = new CommandInfo(verb, className);
  265. cmdList.add(cmd);
  266. verbList.add(verb);
  267. }
  268. }
  269. }
  270. }
  271. }
  272. CommandInfo[] cmds = new CommandInfo[cmdList.size()];
  273. cmdList.toArray(cmds);
  274. return cmds;
  275. }
  276. /**
  277. * Returns all commands for the given MIME type.
  278. * @param mimeType the MIME type
  279. */
  280. public synchronized CommandInfo[] getAllCommands(String mimeType)
  281. {
  282. List<CommandInfo> cmdList = new ArrayList<CommandInfo>();
  283. for (int i = 0; i < 2; i++)
  284. {
  285. for (int j = 0; j < 5; j++)
  286. {
  287. Map<String,List<String>> map = getCommands(mailcaps[j][i], mimeType);
  288. if (map != null)
  289. {
  290. for (Map.Entry<String,List<String>> entry : map.entrySet())
  291. {
  292. String verb = entry.getKey();
  293. List<String> classNames = entry.getValue();
  294. int len = classNames.size();
  295. for (int l = 0; l < len; l++)
  296. {
  297. String className = classNames.get(l);
  298. CommandInfo cmd = new CommandInfo(verb, className);
  299. cmdList.add(cmd);
  300. }
  301. }
  302. }
  303. }
  304. }
  305. CommandInfo[] cmds = new CommandInfo[cmdList.size()];
  306. cmdList.toArray(cmds);
  307. return cmds;
  308. }
  309. /**
  310. * Returns the command with the specified name for the given MIME type.
  311. * @param mimeType the MIME type
  312. * @param cmdName the command verb
  313. */
  314. public synchronized CommandInfo getCommand(String mimeType,
  315. String cmdName)
  316. {
  317. for (int i = 0; i < 2; i++)
  318. {
  319. for (int j = 0; j < 5; j++)
  320. {
  321. Map<String,List<String>> map =
  322. getCommands(mailcaps[j][i], mimeType);
  323. if (map != null)
  324. {
  325. List<String> classNames = map.get(cmdName);
  326. if (classNames == null)
  327. {
  328. classNames = map.get("x-java-" + cmdName);
  329. }
  330. if (classNames != null)
  331. {
  332. String className = classNames.get(0);
  333. return new CommandInfo(cmdName, className);
  334. }
  335. }
  336. }
  337. }
  338. return null;
  339. }
  340. /**
  341. * Adds entries programmatically to the registry.
  342. * @param mailcap a mailcap string
  343. */
  344. public synchronized void addMailcap(String mailcap)
  345. {
  346. if (debug)
  347. {
  348. System.out.println("MailcapCommandMap: add to PROG");
  349. }
  350. try
  351. {
  352. parse(PROG, new StringReader(mailcap));
  353. }
  354. catch (IOException e)
  355. {
  356. }
  357. }
  358. /**
  359. * Returns the DCH for the specified MIME type.
  360. * @param mimeType the MIME type
  361. */
  362. public synchronized DataContentHandler
  363. createDataContentHandler(String mimeType)
  364. {
  365. if (debug)
  366. {
  367. System.out.println("MailcapCommandMap: " +
  368. "createDataContentHandler for " + mimeType);
  369. }
  370. for (int i = 0; i < 2; i++)
  371. {
  372. for (int j = 0; j < 5; j++)
  373. {
  374. if (debug)
  375. {
  376. System.out.println(" search DB #" + i);
  377. }
  378. Map<String,List<String>> map = getCommands(mailcaps[j][i], mimeType);
  379. if (map != null)
  380. {
  381. List<String> classNames = map.get("content-handler");
  382. if (classNames == null)
  383. {
  384. classNames = map.get("x-java-content-handler");
  385. }
  386. if (classNames != null)
  387. {
  388. String className = classNames.get(0);
  389. if (debug)
  390. {
  391. System.out.println(" In " + nameOf(j) +
  392. ", content-handler=" + className);
  393. }
  394. try
  395. {
  396. Class<?> clazz = Class.forName(className);
  397. return (DataContentHandler)clazz.newInstance();
  398. }
  399. catch (IllegalAccessException e)
  400. {
  401. if (debug)
  402. {
  403. e.printStackTrace();
  404. }
  405. }
  406. catch (ClassNotFoundException e)
  407. {
  408. if (debug)
  409. {
  410. e.printStackTrace();
  411. }
  412. }
  413. catch (InstantiationException e)
  414. {
  415. if (debug)
  416. {
  417. e.printStackTrace();
  418. }
  419. }
  420. }
  421. }
  422. }
  423. }
  424. return null;
  425. }
  426. /**
  427. * Get the native commands for the given MIME type.
  428. * Returns an array of strings where each string is
  429. * an entire mailcap file entry. The application
  430. * will need to parse the entry to extract the actual
  431. * command as well as any attributes it needs. See
  432. * <a href="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</a>
  433. * for details of the mailcap entry syntax. Only mailcap
  434. * entries that specify a view command for the specified
  435. * MIME type are returned.
  436. * @return array of native command entries
  437. * @since JAF 1.1
  438. */
  439. public String[] getNativeCommands(String mimeType)
  440. {
  441. List<String> acc = new ArrayList<String>();
  442. for (int i = 0; i < 2; i++)
  443. {
  444. for (int j = 0; j < 5; j++)
  445. {
  446. addNativeCommands(acc, mailcaps[j][i], mimeType);
  447. }
  448. }
  449. String[] ret = new String[acc.size()];
  450. acc.toArray(ret);
  451. return ret;
  452. }
  453. private void addNativeCommands(List<String> acc,
  454. Map<String,Map<String,List<String>>> mailcap,
  455. String mimeType)
  456. {
  457. for (Map.Entry<String,Map<String,List<String>>> mEntry : mailcap.entrySet())
  458. {
  459. String entryMimeType = mEntry.getKey();
  460. if (!entryMimeType.equals(mimeType))
  461. {
  462. continue;
  463. }
  464. Map<String,List<String>> commands = mEntry.getValue();
  465. String viewCommand = commands.get("view-command").get(0);
  466. if (viewCommand == null)
  467. {
  468. continue;
  469. }
  470. CPStringBuilder buf = new CPStringBuilder();
  471. buf.append(mimeType);
  472. buf.append(';');
  473. buf.append(' ');
  474. buf.append(viewCommand);
  475. for (Map.Entry<String,List<String>> cEntry : commands.entrySet())
  476. {
  477. String verb = cEntry.getKey();
  478. List<String> classNames = cEntry.getValue();
  479. if (!"view-command".equals(verb))
  480. {
  481. for (String command : classNames)
  482. {
  483. buf.append(';');
  484. buf.append(' ');
  485. buf.append(verb);
  486. buf.append('=');
  487. buf.append(command);
  488. }
  489. }
  490. }
  491. if (buf.length() > 0)
  492. {
  493. acc.add(buf.toString());
  494. }
  495. }
  496. }
  497. private static String nameOf(int mailcap)
  498. {
  499. switch (mailcap)
  500. {
  501. case PROG:
  502. return "PROG";
  503. case HOME:
  504. return "HOME";
  505. case SYS:
  506. return "SYS";
  507. case JAR:
  508. return "JAR";
  509. case DEF:
  510. return "DEF";
  511. default:
  512. return "ERR";
  513. }
  514. }
  515. private void parseFile(int index, String filename)
  516. {
  517. Reader in = null;
  518. try
  519. {
  520. if (debug)
  521. {
  522. System.out.println("\t" + filename);
  523. }
  524. in = new FileReader(filename);
  525. parse(index, in);
  526. }
  527. catch (IOException e)
  528. {
  529. if (debug)
  530. {
  531. System.out.println(e.getClass().getName() + ": " +
  532. e.getMessage());
  533. }
  534. }
  535. finally
  536. {
  537. if (in != null)
  538. {
  539. try
  540. {
  541. in.close();
  542. }
  543. catch (IOException e)
  544. {
  545. }
  546. }
  547. }
  548. }
  549. private void parseResource(int index, String name)
  550. {
  551. Reader in = null;
  552. try
  553. {
  554. InputStream is = getClass().getResourceAsStream(name);
  555. if (is != null)
  556. {
  557. if (debug)
  558. {
  559. System.out.println("\t" + name);
  560. }
  561. in = new InputStreamReader(is);
  562. parse(index, in);
  563. }
  564. }
  565. catch (IOException e)
  566. {
  567. if (debug)
  568. {
  569. System.out.println(e.getClass().getName() + ": " +
  570. e.getMessage());
  571. }
  572. }
  573. finally
  574. {
  575. if (in != null)
  576. {
  577. try
  578. {
  579. in.close();
  580. }
  581. catch (IOException e)
  582. {
  583. }
  584. }
  585. }
  586. }
  587. private void parse(int index, Reader in)
  588. throws IOException
  589. {
  590. BufferedReader br = new BufferedReader(in);
  591. CPStringBuilder buf = null;
  592. for (String line = br.readLine(); line != null; line = br.readLine())
  593. {
  594. line = line.trim();
  595. int len = line.length();
  596. if (len == 0 || line.charAt(0) == '#')
  597. {
  598. continue; // Comment
  599. }
  600. if (line.charAt(len - 1) == '\\')
  601. {
  602. if (buf == null)
  603. {
  604. buf = new CPStringBuilder();
  605. }
  606. buf.append(line.substring(0, len - 1));
  607. }
  608. else if (buf != null)
  609. {
  610. buf.append(line);
  611. parseEntry(index, buf.toString());
  612. buf = null;
  613. }
  614. else
  615. {
  616. parseEntry(index, line);
  617. }
  618. }
  619. }
  620. private void parseEntry(int index, String line)
  621. {
  622. // Tokenize entry into fields
  623. char[] chars = line.toCharArray();
  624. int len = chars.length;
  625. boolean inQuotedString = false;
  626. boolean fallback = false;
  627. CPStringBuilder buffer = new CPStringBuilder();
  628. List<String> fields = new ArrayList<String>();
  629. for (int i = 0; i < len; i++)
  630. {
  631. char c = chars[i];
  632. if (c == '\\')
  633. {
  634. c = chars[++i]; // qchar
  635. }
  636. if (c == ';' && !inQuotedString)
  637. {
  638. String field = buffer.toString().trim();
  639. if ("x-java-fallback-entry".equals(field))
  640. {
  641. fallback = true;
  642. }
  643. fields.add(field);
  644. buffer.setLength(0);
  645. }
  646. else
  647. {
  648. if (c == '"')
  649. {
  650. inQuotedString = !inQuotedString;
  651. }
  652. buffer.append(c);
  653. }
  654. }
  655. String field = buffer.toString().trim();
  656. if ("x-java-fallback-entry".equals(field))
  657. {
  658. fallback = true;
  659. }
  660. fields.add(field);
  661. len = fields.size();
  662. if (len < 2)
  663. {
  664. if (debug)
  665. {
  666. System.err.println("Invalid mailcap entry: " + line);
  667. }
  668. return;
  669. }
  670. Map<String,Map<String,List<String>>> mailcap =
  671. fallback ? mailcaps[index][FALLBACK] : mailcaps[index][NORMAL];
  672. String mimeType = fields.get(0);
  673. addField(mailcap, mimeType, "view-command", (String) fields.get(1));
  674. for (int i = 2; i < len; i++)
  675. {
  676. addField(mailcap, mimeType, null, (String) fields.get(i));
  677. }
  678. }
  679. private void addField(Map<String,Map<String,List<String>>> mailcap,
  680. String mimeType, String verb, String command)
  681. {
  682. if (verb == null)
  683. {
  684. int ei = command.indexOf('=');
  685. if (ei != -1)
  686. {
  687. verb = command.substring(0, ei);
  688. command = command.substring(ei + 1);
  689. }
  690. }
  691. if (command.length() == 0 || verb == null || verb.length() == 0)
  692. {
  693. return; // Invalid field or flag
  694. }
  695. Map<String,List<String>> commands = mailcap.get(mimeType);
  696. if (commands == null)
  697. {
  698. commands = new LinkedHashMap<String,List<String>>();
  699. mailcap.put(mimeType, commands);
  700. }
  701. List<String> classNames = commands.get(verb);
  702. if (classNames == null)
  703. {
  704. classNames = new ArrayList<String>();
  705. commands.put(verb, classNames);
  706. }
  707. classNames.add(command);
  708. }
  709. private Map<String,List<String>>
  710. getCommands(Map<String,Map<String,List<String>>> mailcap,
  711. String mimeType)
  712. {
  713. int si = mimeType.indexOf('/');
  714. String genericMimeType = new CPStringBuilder(mimeType.substring(0, si))
  715. .append('/')
  716. .append('*')
  717. .toString();
  718. Map<String,List<String>> specific = mailcap.get(mimeType);
  719. Map<String,List<String>> generic = mailcap.get(genericMimeType);
  720. if (generic == null)
  721. {
  722. return specific;
  723. }
  724. if (specific == null)
  725. {
  726. return generic;
  727. }
  728. Map<String,List<String>> combined = new LinkedHashMap<String,List<String>>();
  729. combined.putAll(specific);
  730. for (String verb : generic.keySet())
  731. {
  732. List<String> genericClassNames = generic.get(verb);
  733. List<String> classNames = combined.get(verb);
  734. if (classNames == null)
  735. {
  736. combined.put(verb, genericClassNames);
  737. }
  738. else
  739. {
  740. classNames.addAll(genericClassNames);
  741. }
  742. }
  743. return combined;
  744. }
  745. // -- Utility methods --
  746. private List<URL> getSystemResources(String name)
  747. {
  748. List<URL> acc = new ArrayList<URL>();
  749. try
  750. {
  751. for (Enumeration<URL> i = ClassLoader.getSystemResources(name);
  752. i.hasMoreElements(); )
  753. {
  754. acc.add(i.nextElement());
  755. }
  756. }
  757. catch (IOException e)
  758. {
  759. }
  760. return acc;
  761. }
  762. }