001/* MailcapCommandMap.java -- Command map implementation using a mailcap file. 002 Copyright (C) 2004 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038package javax.activation; 039 040import gnu.java.lang.CPStringBuilder; 041 042import java.io.BufferedReader; 043import java.io.File; 044import java.io.FileReader; 045import java.io.InputStream; 046import java.io.InputStreamReader; 047import java.io.IOException; 048import java.io.Reader; 049import java.io.StringReader; 050import java.net.URL; 051import java.util.ArrayList; 052import java.util.Enumeration; 053import java.util.LinkedHashMap; 054import java.util.Iterator; 055import java.util.List; 056import java.util.Map; 057 058/** 059 * Implementation of a command map using a <code>mailcap</code> file (RFC 060 * 1524). Mailcap files are searched for in the following places: 061 * <ol> 062 * <li>Programmatically added entries to this interface</li> 063 * <li>the file <tt>.mailcap</tt> in the user's home directory</li> 064 * <li>the file <i><java.home></i><tt>/lib/mailcap</tt></li> 065 * <li>the resource <tt>META-INF/mailcap</tt></li> 066 * <li>the resource <tt>META-INF/mailcap.default</tt> in the JAF 067 * distribution</li> 068 * </ol> 069 * 070 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> 071 * @version 1.1 072 */ 073public class MailcapCommandMap 074 extends CommandMap 075{ 076 077 private static final int PROG = 0; 078 private static final int HOME = 1; 079 private static final int SYS = 2; 080 private static final int JAR = 3; 081 private static final int DEF = 4; 082 private static boolean debug = false; 083 private static final int NORMAL = 0; 084 private static final int FALLBACK = 1; 085 086 static 087 { 088 try 089 { 090 String d = System.getProperty("javax.activation.debug"); 091 debug = Boolean.valueOf(d).booleanValue(); 092 } 093 catch (SecurityException e) 094 { 095 } 096 } 097 098 private Map<String,Map<String,List<String>>>[][] mailcaps; 099 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}