1 18 19 package org.apache.tools.ant.taskdefs; 20 21 import java.io.ByteArrayInputStream ; 22 import java.io.ByteArrayOutputStream ; 23 import java.io.File ; 24 import java.io.FileOutputStream ; 25 import java.io.FileInputStream ; 26 import java.io.IOException ; 27 import java.io.InputStream ; 28 import java.io.UnsupportedEncodingException ; 29 import java.io.InputStreamReader ; 30 import java.io.OutputStreamWriter ; 31 import java.io.PrintWriter ; 32 import java.io.Reader ; 33 import java.util.ArrayList ; 34 import java.util.Collections ; 35 import java.util.Comparator ; 36 import java.util.Enumeration ; 37 import java.util.HashSet ; 38 import java.util.Iterator ; 39 import java.util.List ; 40 import java.util.StringTokenizer ; 41 import java.util.TreeMap ; 42 import java.util.Vector ; 43 import java.util.zip.ZipEntry ; 44 import java.util.zip.ZipFile ; 45 import org.apache.tools.ant.BuildException; 46 import org.apache.tools.ant.Project; 47 import org.apache.tools.ant.types.EnumeratedAttribute; 48 import org.apache.tools.ant.types.Path; 49 import org.apache.tools.ant.types.ResourceCollection; 50 import org.apache.tools.ant.types.ZipFileSet; 51 import org.apache.tools.ant.types.spi.Service; 52 import org.apache.tools.zip.JarMarker; 53 import org.apache.tools.zip.ZipExtraField; 54 import org.apache.tools.zip.ZipOutputStream; 55 56 63 public class Jar extends Zip { 64 65 private static final String INDEX_NAME = "META-INF/INDEX.LIST"; 66 67 68 private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 69 70 73 private List serviceList = new ArrayList (); 74 75 76 private Manifest configuredManifest; 77 78 private Manifest savedConfiguredManifest; 79 80 81 private Manifest filesetManifest; 82 83 87 private Manifest originalManifest; 88 89 93 private FilesetManifestConfig filesetManifestConfig; 94 95 99 private boolean mergeManifestsMain = true; 100 101 102 private Manifest manifest; 103 104 105 private String manifestEncoding; 106 107 113 private File manifestFile; 114 115 116 private boolean index = false; 117 118 122 private boolean createEmpty = false; 123 124 133 private Vector rootEntries; 134 135 140 private Path indexJars; 141 142 147 private static final ZipExtraField[] JAR_MARKER = new ZipExtraField[] { 148 JarMarker.getInstance() 149 }; 150 151 protected String emptyBehavior = "create"; 153 155 156 public Jar() { 157 super(); 158 archiveType = "jar"; 159 emptyBehavior = "create"; 160 setEncoding("UTF8"); 161 rootEntries = new Vector (); 162 } 163 164 169 public void setWhenempty(WhenEmpty we) { 170 log("JARs are never empty, they contain at least a manifest file", 171 Project.MSG_WARN); 172 } 173 174 184 public void setWhenmanifestonly(WhenEmpty we) { 185 emptyBehavior = we.getValue(); 186 } 187 188 194 public void setJarfile(File jarFile) { 195 setDestFile(jarFile); 196 } 197 198 203 public void setIndex(boolean flag) { 204 index = flag; 205 } 206 207 212 public void setManifestEncoding(String manifestEncoding) { 213 this.manifestEncoding = manifestEncoding; 214 } 215 216 223 public void addConfiguredManifest(Manifest newManifest) 224 throws ManifestException { 225 if (configuredManifest == null) { 226 configuredManifest = newManifest; 227 } else { 228 configuredManifest.merge(newManifest); 229 } 230 savedConfiguredManifest = configuredManifest; 231 } 232 233 240 public void setManifest(File manifestFile) { 241 if (!manifestFile.exists()) { 242 throw new BuildException("Manifest file: " + manifestFile 243 + " does not exist.", getLocation()); 244 } 245 246 this.manifestFile = manifestFile; 247 } 248 249 private Manifest getManifest(File manifestFile) { 250 251 Manifest newManifest = null; 252 FileInputStream fis = null; 253 InputStreamReader isr = null; 254 try { 255 fis = new FileInputStream (manifestFile); 256 if (manifestEncoding == null) { 257 isr = new InputStreamReader (fis); 258 } else { 259 isr = new InputStreamReader (fis, manifestEncoding); 260 } 261 newManifest = getManifest(isr); 262 } catch (UnsupportedEncodingException e) { 263 throw new BuildException("Unsupported encoding while reading manifest: " 264 + e.getMessage(), e); 265 } catch (IOException e) { 266 throw new BuildException("Unable to read manifest file: " 267 + manifestFile 268 + " (" + e.getMessage() + ")", e); 269 } finally { 270 if (isr != null) { 271 try { 272 isr.close(); 273 } catch (IOException e) { 274 } 276 } 277 } 278 return newManifest; 279 } 280 281 286 private Manifest getManifestFromJar(File jarFile) throws IOException { 287 ZipFile zf = null; 288 try { 289 zf = new ZipFile (jarFile); 290 291 Enumeration e = zf.entries(); 294 while (e.hasMoreElements()) { 295 ZipEntry ze = (ZipEntry ) e.nextElement(); 296 if (ze.getName().equalsIgnoreCase(MANIFEST_NAME)) { 297 InputStreamReader isr = 298 new InputStreamReader (zf.getInputStream(ze), "UTF-8"); 299 return getManifest(isr); 300 } 301 } 302 return null; 303 } finally { 304 if (zf != null) { 305 try { 306 zf.close(); 307 } catch (IOException e) { 308 } 310 } 311 } 312 } 313 314 private Manifest getManifest(Reader r) { 315 316 Manifest newManifest = null; 317 try { 318 newManifest = new Manifest(r); 319 } catch (ManifestException e) { 320 log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR); 321 throw new BuildException("Invalid Manifest: " + manifestFile, 322 e, getLocation()); 323 } catch (IOException e) { 324 throw new BuildException("Unable to read manifest file" 325 + " (" + e.getMessage() + ")", e); 326 } 327 return newManifest; 328 } 329 330 343 public void setFilesetmanifest(FilesetManifestConfig config) { 344 filesetManifestConfig = config; 345 mergeManifestsMain = "merge".equals(config.getValue()); 346 347 if (filesetManifestConfig != null 348 && !filesetManifestConfig.getValue().equals("skip")) { 349 350 doubleFilePass = true; 351 } 352 } 353 354 359 public void addMetainf(ZipFileSet fs) { 360 fs.setPrefix("META-INF/"); 362 super.addFileset(fs); 363 } 364 365 370 public void addConfiguredIndexJars(Path p) { 371 if (indexJars == null) { 372 indexJars = new Path(getProject()); 373 } 374 indexJars.append(p); 375 } 376 377 382 public void addConfiguredService(Service service) { 383 service.check(); 385 serviceList.add(service); 386 } 387 388 391 private void writeServices(ZipOutputStream zOut) throws IOException { 392 Iterator serviceIterator; 393 Service service; 394 395 serviceIterator = serviceList.iterator(); 396 while (serviceIterator.hasNext()) { 397 service = (Service) serviceIterator.next(); 398 super.zipFile(service.getAsStream(), zOut, 400 "META-INF/service/" + service.getType(), 401 System.currentTimeMillis(), null, 402 ZipFileSet.DEFAULT_FILE_MODE); 403 } 404 } 405 406 407 413 protected void initZipOutputStream(ZipOutputStream zOut) 414 throws IOException , BuildException { 415 416 if (!skipWriting) { 417 Manifest jarManifest = createManifest(); 418 writeManifest(zOut, jarManifest); 419 writeServices(zOut); 420 } 421 } 422 423 private Manifest createManifest() 424 throws BuildException { 425 try { 426 Manifest finalManifest = Manifest.getDefaultManifest(); 427 428 if (manifest == null) { 429 if (manifestFile != null) { 430 manifest = getManifest(manifestFile); 433 } 434 } 435 436 443 444 if (isInUpdateMode()) { 445 finalManifest.merge(originalManifest); 446 } 447 finalManifest.merge(filesetManifest); 448 finalManifest.merge(configuredManifest); 449 finalManifest.merge(manifest, !mergeManifestsMain); 450 451 return finalManifest; 452 453 } catch (ManifestException e) { 454 log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR); 455 throw new BuildException("Invalid Manifest", e, getLocation()); 456 } 457 } 458 459 private void writeManifest(ZipOutputStream zOut, Manifest manifest) 460 throws IOException { 461 for (Enumeration e = manifest.getWarnings(); 462 e.hasMoreElements();) { 463 log("Manifest warning: " + (String ) e.nextElement(), 464 Project.MSG_WARN); 465 } 466 467 zipDir(null, zOut, "META-INF/", ZipFileSet.DEFAULT_DIR_MODE, 468 JAR_MARKER); 469 ByteArrayOutputStream baos = new ByteArrayOutputStream (); 471 OutputStreamWriter osw = new OutputStreamWriter (baos, Manifest.JAR_ENCODING); 472 PrintWriter writer = new PrintWriter (osw); 473 manifest.write(writer); 474 writer.flush(); 475 476 ByteArrayInputStream bais = 477 new ByteArrayInputStream (baos.toByteArray()); 478 super.zipFile(bais, zOut, MANIFEST_NAME, 479 System.currentTimeMillis(), null, 480 ZipFileSet.DEFAULT_FILE_MODE); 481 super.initZipOutputStream(zOut); 482 } 483 484 491 protected void finalizeZipOutputStream(ZipOutputStream zOut) 492 throws IOException , BuildException { 493 494 if (index) { 495 createIndexList(zOut); 496 } 497 } 498 499 509 private void createIndexList(ZipOutputStream zOut) throws IOException { 510 ByteArrayOutputStream baos = new ByteArrayOutputStream (); 511 PrintWriter writer = new PrintWriter (new OutputStreamWriter (baos, 513 "UTF8")); 514 515 writer.println("JarIndex-Version: 1.0"); 517 writer.println(); 518 519 writer.println(zipFile.getName()); 521 522 writeIndexLikeList(new ArrayList (addedDirs.keySet()), 523 rootEntries, writer); 524 writer.println(); 525 526 if (indexJars != null) { 527 Manifest mf = createManifest(); 528 Manifest.Attribute classpath = 529 mf.getMainSection().getAttribute(Manifest.ATTRIBUTE_CLASSPATH); 530 String [] cpEntries = null; 531 if (classpath != null && classpath.getValue() != null) { 532 StringTokenizer tok = new StringTokenizer (classpath.getValue(), 533 " "); 534 cpEntries = new String [tok.countTokens()]; 535 int c = 0; 536 while (tok.hasMoreTokens()) { 537 cpEntries[c++] = tok.nextToken(); 538 } 539 } 540 String [] indexJarEntries = indexJars.list(); 541 for (int i = 0; i < indexJarEntries.length; i++) { 542 String name = findJarName(indexJarEntries[i], cpEntries); 543 if (name != null) { 544 ArrayList dirs = new ArrayList (); 545 ArrayList files = new ArrayList (); 546 grabFilesAndDirs(indexJarEntries[i], dirs, files); 547 if (dirs.size() + files.size() > 0) { 548 writer.println(name); 549 writeIndexLikeList(dirs, files, writer); 550 writer.println(); 551 } 552 } 553 } 554 } 555 556 writer.flush(); 557 ByteArrayInputStream bais = 558 new ByteArrayInputStream (baos.toByteArray()); 559 super.zipFile(bais, zOut, INDEX_NAME, System.currentTimeMillis(), null, 560 ZipFileSet.DEFAULT_FILE_MODE); 561 } 562 563 574 protected void zipFile(InputStream is, ZipOutputStream zOut, String vPath, 575 long lastModified, File fromArchive, int mode) 576 throws IOException { 577 if (MANIFEST_NAME.equalsIgnoreCase(vPath)) { 578 if (!doubleFilePass || (doubleFilePass && skipWriting)) { 579 filesetManifest(fromArchive, is); 580 } 581 } else if (INDEX_NAME.equalsIgnoreCase(vPath) && index) { 582 log("Warning: selected " + archiveType 583 + " files include a META-INF/INDEX.LIST which will" 584 + " be replaced by a newly generated one.", Project.MSG_WARN); 585 } else { 586 if (index && vPath.indexOf("/") == -1) { 587 rootEntries.addElement(vPath); 588 } 589 super.zipFile(is, zOut, vPath, lastModified, fromArchive, mode); 590 } 591 } 592 593 private void filesetManifest(File file, InputStream is) throws IOException { 594 if (manifestFile != null && manifestFile.equals(file)) { 595 log("Found manifest " + file, Project.MSG_VERBOSE); 598 try { 599 if (is != null) { 600 InputStreamReader isr; 601 if (manifestEncoding == null) { 602 isr = new InputStreamReader (is); 603 } else { 604 isr = new InputStreamReader (is, manifestEncoding); 605 } 606 manifest = getManifest(isr); 607 } else { 608 manifest = getManifest(file); 609 } 610 } catch (UnsupportedEncodingException e) { 611 throw new BuildException("Unsupported encoding while reading " 612 + "manifest: " + e.getMessage(), e); 613 } 614 } else if (filesetManifestConfig != null 615 && !filesetManifestConfig.getValue().equals("skip")) { 616 log("Found manifest to merge in file " + file, 618 Project.MSG_VERBOSE); 619 620 try { 621 Manifest newManifest = null; 622 if (is != null) { 623 InputStreamReader isr; 624 if (manifestEncoding == null) { 625 isr = new InputStreamReader (is); 626 } else { 627 isr = new InputStreamReader (is, manifestEncoding); 628 } 629 newManifest = getManifest(isr); 630 } else { 631 newManifest = getManifest(file); 632 } 633 634 if (filesetManifest == null) { 635 filesetManifest = newManifest; 636 } else { 637 filesetManifest.merge(newManifest); 638 } 639 } catch (UnsupportedEncodingException e) { 640 throw new BuildException("Unsupported encoding while reading " 641 + "manifest: " + e.getMessage(), e); 642 } catch (ManifestException e) { 643 log("Manifest in file " + file + " is invalid: " 644 + e.getMessage(), Project.MSG_ERR); 645 throw new BuildException("Invalid Manifest", e, getLocation()); 646 } 647 } else { 648 652 656 } 663 } 664 665 687 protected ArchiveState getResourcesToAdd(ResourceCollection[] rcs, 688 File zipFile, 689 boolean needsUpdate) 690 throws BuildException { 691 692 if (zipFile.exists()) { 694 697 try { 698 originalManifest = getManifestFromJar(zipFile); 699 if (originalManifest == null) { 700 log("Updating jar since the current jar has no manifest", 701 Project.MSG_VERBOSE); 702 needsUpdate = true; 703 } else { 704 Manifest mf = createManifest(); 705 if (!mf.equals(originalManifest)) { 706 log("Updating jar since jar manifest has changed", 707 Project.MSG_VERBOSE); 708 needsUpdate = true; 709 } 710 } 711 } catch (Throwable t) { 712 log("error while reading original manifest in file: " 713 + zipFile.toString() + t.getMessage(), 714 Project.MSG_WARN); 715 needsUpdate = true; 716 } 717 718 } else { 719 needsUpdate = true; 721 } 722 723 createEmpty = needsUpdate; 724 return super.getResourcesToAdd(rcs, zipFile, needsUpdate); 725 } 726 727 733 protected boolean createEmptyZip(File zipFile) throws BuildException { 734 if (!createEmpty) { 735 return true; 736 } 737 738 if (emptyBehavior.equals("skip")) { 739 log("Warning: skipping " + archiveType + " archive " 740 + zipFile + " because no files were included.", 741 Project.MSG_WARN); 742 return true; 743 } else if (emptyBehavior.equals("fail")) { 744 throw new BuildException("Cannot create " + archiveType 745 + " archive " + zipFile 746 + ": no files were included.", 747 getLocation()); 748 } 749 750 ZipOutputStream zOut = null; 751 try { 752 log("Building MANIFEST-only jar: " 753 + getDestFile().getAbsolutePath()); 754 zOut = new ZipOutputStream(new FileOutputStream (getDestFile())); 755 756 zOut.setEncoding(getEncoding()); 757 if (isCompress()) { 758 zOut.setMethod(ZipOutputStream.DEFLATED); 759 } else { 760 zOut.setMethod(ZipOutputStream.STORED); 761 } 762 initZipOutputStream(zOut); 763 finalizeZipOutputStream(zOut); 764 } catch (IOException ioe) { 765 throw new BuildException("Could not create almost empty JAR archive" 766 + " (" + ioe.getMessage() + ")", ioe, 767 getLocation()); 768 } finally { 769 try { 771 if (zOut != null) { 772 zOut.close(); 773 } 774 } catch (IOException ex) { 775 } 777 createEmpty = false; 778 } 779 return true; 780 } 781 782 788 protected void cleanUp() { 789 super.cleanUp(); 790 791 if (!doubleFilePass || (doubleFilePass && !skipWriting)) { 793 manifest = null; 794 configuredManifest = savedConfiguredManifest; 795 filesetManifest = null; 796 originalManifest = null; 797 } 798 rootEntries.removeAllElements(); 799 } 800 801 808 public void reset() { 809 super.reset(); 810 emptyBehavior = "create"; 811 configuredManifest = null; 812 filesetManifestConfig = null; 813 mergeManifestsMain = false; 814 manifestFile = null; 815 index = false; 816 } 817 818 821 public static class FilesetManifestConfig extends EnumeratedAttribute { 822 826 public String [] getValues() { 827 return new String [] {"skip", "merge", "mergewithoutmain"}; 828 } 829 } 830 831 841 protected final void writeIndexLikeList(List dirs, List files, 842 PrintWriter writer) 843 throws IOException { 844 Collections.sort(dirs); 848 Collections.sort(files); 849 Iterator iter = dirs.iterator(); 850 while (iter.hasNext()) { 851 String dir = (String ) iter.next(); 852 853 dir = dir.replace('\\', '/'); 855 if (dir.startsWith("./")) { 856 dir = dir.substring(2); 857 } 858 while (dir.startsWith("/")) { 859 dir = dir.substring(1); 860 } 861 int pos = dir.lastIndexOf('/'); 862 if (pos != -1) { 863 dir = dir.substring(0, pos); 864 } 865 866 if (dir.startsWith("META-INF")) { 870 continue; 871 } 872 writer.println(dir); 874 } 875 876 iter = files.iterator(); 877 while (iter.hasNext()) { 878 writer.println(iter.next()); 879 } 880 } 881 882 902 protected static final String findJarName(String fileName, 903 String [] classpath) { 904 if (classpath == null) { 905 return (new File (fileName)).getName(); 906 } 907 fileName = fileName.replace(File.separatorChar, '/'); 908 TreeMap matches = new TreeMap (new Comparator () { 909 public int compare(Object o1, Object o2) { 911 if (o1 instanceof String && o2 instanceof String ) { 912 return ((String ) o2).length() 913 - ((String ) o1).length(); 914 } 915 return 0; 916 } 917 }); 918 919 for (int i = 0; i < classpath.length; i++) { 920 if (fileName.endsWith(classpath[i])) { 921 matches.put(classpath[i], classpath[i]); 922 } else { 923 int slash = classpath[i].indexOf("/"); 924 String candidate = classpath[i]; 925 while (slash > -1) { 926 candidate = candidate.substring(slash + 1); 927 if (fileName.endsWith(candidate)) { 928 matches.put(candidate, classpath[i]); 929 break; 930 } 931 slash = candidate.indexOf("/"); 932 } 933 } 934 } 935 936 return matches.size() == 0 937 ? null : (String ) matches.get(matches.firstKey()); 938 } 939 940 949 protected static final void grabFilesAndDirs(String file, List dirs, 950 List files) 951 throws IOException { 952 org.apache.tools.zip.ZipFile zf = null; 953 try { 954 zf = new org.apache.tools.zip.ZipFile(file, "utf-8"); 955 Enumeration entries = zf.getEntries(); 956 HashSet dirSet = new HashSet (); 957 while (entries.hasMoreElements()) { 958 org.apache.tools.zip.ZipEntry ze = 959 (org.apache.tools.zip.ZipEntry) entries.nextElement(); 960 String name = ze.getName(); 961 if (!name.startsWith("META-INF/")) { 964 if (ze.isDirectory()) { 965 dirSet.add(name); 966 } else if (name.indexOf("/") == -1) { 967 files.add(name); 968 } else { 969 dirSet.add(name.substring(0, 974 name.lastIndexOf("/") + 1)); 975 } 976 } 977 } 978 dirs.addAll(dirSet); 979 } finally { 980 if (zf != null) { 981 zf.close(); 982 } 983 } 984 } 985 } 986 | Popular Tags |