Source for javax.activation.MailcapCommandMap

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