src/share/classes/sun/tools/jar/Main.java

Print this page




  29 import java.util.*;
  30 import java.util.zip.*;
  31 import java.util.jar.*;
  32 import java.util.jar.Manifest;
  33 import java.text.MessageFormat;
  34 import sun.misc.JarIndex;
  35 
  36 /**
  37  * This class implements a simple utility for creating files in the JAR
  38  * (Java Archive) file format. The JAR format is based on the ZIP file
  39  * format, with optional meta-information stored in a MANIFEST entry.
  40  */
  41 public
  42 class Main {
  43     String program;
  44     PrintStream out, err;
  45     String fname, mname, ename;
  46     String zname = "";
  47     String[] files;
  48     String rootjar = null;
  49     Hashtable filesTable = new Hashtable();
  50     Vector paths = new Vector();
  51     Vector v;









  52     CRC32 crc32 = new CRC32();
  53     /*
  54      * cflag: create
  55      * uflag: update
  56      * xflag: xtract
  57      * tflag: table
  58      * vflag: verbose
  59      * flag0: no zip compression (store only)
  60      * Mflag: DO NOT generate a manifest file (just ZIP)
  61      * iflag: generate jar index
  62      */
  63     boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag;
  64 
  65     static final String MANIFEST = JarFile.MANIFEST_NAME;
  66     static final String MANIFEST_DIR = "META-INF/";
  67     static final String VERSION = "1.0";
  68     static final char SEPARATOR = File.separatorChar;
  69     static final String INDEX = JarIndex.INDEX_NAME;
  70 
  71     private static ResourceBundle rsrc;


 158                             in.close();
 159                         }
 160                         return false;
 161                     }
 162                     if (ename != null) {
 163                         addMainClass(manifest, ename);
 164                     }
 165                 }
 166                 OutputStream out;
 167                 if (fname != null) {
 168                     out = new FileOutputStream(fname);
 169                 } else {
 170                     out = new FileOutputStream(FileDescriptor.out);
 171                     if (vflag) {
 172                         // Disable verbose output so that it does not appear
 173                         // on stdout along with file data
 174                         // error("Warning: -v option ignored");
 175                         vflag = false;
 176                     }
 177                 }
 178                 create(new BufferedOutputStream(out), expand(files), manifest);

 179                 if (in != null) {
 180                     in.close();
 181                 }
 182                 out.close();
 183             } else if (uflag) {
 184                 File inputFile = null, tmpFile = null;
 185                 FileInputStream in;
 186                 FileOutputStream out;
 187                 if (fname != null) {
 188                     inputFile = new File(fname);
 189                     String path = inputFile.getParent();
 190                     tmpFile = File.createTempFile("tmp", null,
 191                               new File((path == null) ? "." : path));
 192                     in = new FileInputStream(inputFile);
 193                     out = new FileOutputStream(tmpFile);
 194                 } else {
 195                     in = new FileInputStream(FileDescriptor.in);
 196                     out = new FileOutputStream(FileDescriptor.out);
 197                     vflag = false;
 198                 }
 199                 InputStream manifest = (!Mflag && (mname != null)) ?
 200                     (new FileInputStream(mname)) : null;
 201                 expand(files);
 202                 boolean updateOk = update(in, new BufferedOutputStream(out), manifest);
 203                 if (ok) {
 204                     ok = updateOk;
 205                 }
 206                 in.close();
 207                 out.close();
 208                 if (manifest != null) {
 209                     manifest.close();
 210                 }
 211                 if (fname != null) {
 212                     // on Win32, we need this delete
 213                     inputFile.delete();
 214                     if (!tmpFile.renameTo(inputFile)) {
 215                         tmpFile.delete();
 216                         throw new IOException(getMsg("error.write.file"));
 217                     }
 218                     tmpFile.delete();
 219                 }
 220             } else if (xflag || tflag) {
 221                 InputStream in;
 222                 if (fname != null) {


 337             error(getMsg("error.bad.option"));
 338             usageError();
 339             return false;
 340         }
 341         /* parse file arguments */
 342         int n = args.length - count;
 343         if (n > 0) {
 344             int k = 0;
 345             String[] nameBuf = new String[n];
 346             try {
 347                 for (int i = count; i < args.length; i++) {
 348                     if (args[i].equals("-C")) {
 349                         /* change the directory */
 350                         String dir = args[++i];
 351                         dir = (dir.endsWith(File.separator) ?
 352                                dir : (dir + File.separator));
 353                         dir = dir.replace(File.separatorChar, '/');
 354                         while (dir.indexOf("//") > -1) {
 355                             dir = dir.replace("//", "/");
 356                         }
 357                         paths.addElement(dir.replace(File.separatorChar, '/'));
 358                         nameBuf[k++] = dir + args[++i];
 359                     } else {
 360                         nameBuf[k++] = args[i];
 361                     }
 362                 }
 363             } catch (ArrayIndexOutOfBoundsException e) {
 364                 usageError();
 365                 return false;
 366             }
 367             files = new String[k];
 368             System.arraycopy(nameBuf, 0, files, 0, k);
 369         } else if (cflag && (mname == null)) {
 370             error(getMsg("error.bad.cflag"));
 371             usageError();
 372             return false;
 373         } else if (uflag) {
 374             if ((mname != null) || (ename != null)) {
 375                 /* just want to update the manifest */
 376                 return true;
 377             } else {
 378                 error(getMsg("error.bad.uflag"));
 379                 usageError();
 380                 return false;
 381             }
 382         }
 383         return true;
 384     }
 385 
 386     /*
 387      * Expands list of files to process into full list of all files that
 388      * can be found by recursively descending directories.
 389      */
 390     String[] expand(String[] files) {
 391         v = new Vector();
 392         expand(null, files, v, filesTable);
 393         files = new String[v.size()];
 394         for (int i = 0; i < files.length; i++) {
 395             files[i] = ((File)v.elementAt(i)).getPath();
 396         }
 397         return files;
 398     }
 399 
 400     void expand(File dir, String[] files, Vector v, Hashtable t) {
 401         if (files == null) {
 402             return;
 403         }
 404         for (int i = 0; i < files.length; i++) {
 405             File f;
 406             if (dir == null) {
 407                 f = new File(files[i]);
 408             } else {
 409                 f = new File(dir, files[i]);
 410             }
 411             if (f.isFile()) {
 412                 if (!t.contains(f)) {
 413                     t.put(entryName(f.getPath()), f);
 414                     v.addElement(f);
 415                 }
 416             } else if (f.isDirectory()) {


 417                 String dirPath = f.getPath();
 418                 dirPath = (dirPath.endsWith(File.separator)) ? dirPath :
 419                     (dirPath + File.separator);
 420                 t.put(entryName(dirPath), f);
 421                 v.addElement(f);
 422                 expand(f, f.list(), v, t);

 423             } else {
 424                 error(formatMsg("error.nosuch.fileordir", String.valueOf(f)));
 425                 ok = false;
 426             }
 427         }
 428     }
 429 
 430     /*
 431      * Creates a new JAR file.
 432      */
 433     void create(OutputStream out, String[] files, Manifest manifest)
 434         throws IOException
 435     {
 436         ZipOutputStream zos = new JarOutputStream(out);
 437         if (flag0) {
 438             zos.setMethod(ZipOutputStream.STORED);
 439         }
 440         if (manifest != null) {
 441             if (vflag) {
 442                 output(getMsg("out.added.manifest"));
 443             }
 444             ZipEntry e = new ZipEntry(MANIFEST_DIR);
 445             e.setTime(System.currentTimeMillis());
 446             e.setSize(0);
 447             e.setCrc(0);
 448             zos.putNextEntry(e);
 449             e = new ZipEntry(MANIFEST);
 450             e.setTime(System.currentTimeMillis());
 451             if (flag0) {
 452                 crc32Manifest(e, manifest);
 453             }
 454             zos.putNextEntry(e);
 455             manifest.write(zos);
 456             zos.closeEntry();
 457         }
 458         for (int i = 0; i < files.length; i++) {
 459             addFile(zos, new File(files[i]));
 460         }
 461         zos.close();
 462     }
 463 
 464     /*
 465      * update an existing jar file.
 466      */
 467     boolean update(InputStream in, OutputStream out,
 468                 InputStream newManifest) throws IOException

 469     {
 470         Hashtable t = filesTable;
 471         Vector v = this.v;
 472         ZipInputStream zis = new ZipInputStream(in);
 473         ZipOutputStream zos = new JarOutputStream(out);
 474         ZipEntry e = null;
 475         boolean foundManifest = false;
 476         byte[] buf = new byte[1024];
 477         int n = 0;
 478         boolean updateOk = true;
 479 
 480         if (t.containsKey(INDEX)) {
 481             addIndex((JarIndex)t.get(INDEX), zos);
 482         }
 483 
 484         // put the old entries first, replace if necessary
 485         while ((e = zis.getNextEntry()) != null) {
 486             String name = e.getName();
 487 
 488             boolean isManifestEntry = name.toUpperCase(
 489                                             java.util.Locale.ENGLISH).
 490                                         equals(MANIFEST);
 491             if ((name.toUpperCase().equals(INDEX)
 492                     && t.containsKey(INDEX))
 493                     || (Mflag && isManifestEntry)) {
 494                 continue;
 495             } else if (isManifestEntry && ((newManifest != null) ||
 496                         (ename != null))) {
 497                 foundManifest = true;
 498                 if (newManifest != null) {
 499                     // Don't read from the newManifest InputStream, as we
 500                     // might need it below, and we can't re-read the same data
 501                     // twice.
 502                     FileInputStream fis = new FileInputStream(mname);
 503                     boolean ambigous = isAmbigousMainClass(new Manifest(fis));
 504                     fis.close();
 505                     if (ambigous) {
 506                         return false;
 507                     }
 508                 }
 509 
 510                 // Update the manifest.
 511                 Manifest old = new Manifest(zis);
 512                 if (newManifest != null) {
 513                     old.read(newManifest);
 514                 }
 515                 updateManifest(old, zos);
 516             } else {
 517                 if (!t.containsKey(name)) { // copy the old stuff
 518 
 519                     // do our own compression
 520                     ZipEntry e2 = new ZipEntry(name);
 521                     e2.setMethod(e.getMethod());
 522                     e2.setTime(e.getTime());
 523                     e2.setComment(e.getComment());
 524                     e2.setExtra(e.getExtra());
 525                     if (e.getMethod() == ZipEntry.STORED) {
 526                         e2.setSize(e.getSize());
 527                         e2.setCrc(e.getCrc());
 528                     }
 529                     zos.putNextEntry(e2);
 530                     while ((n = zis.read(buf, 0, buf.length)) != -1) {
 531                         zos.write(buf, 0, n);
 532                     }
 533                 } else { // replace with the new files
 534                     addFile(zos, (File)(t.get(name)));
 535                     t.remove(name);


 536                 }
 537             }
 538         }
 539         t.remove(INDEX);
 540 
 541         // add the remaining new files
 542         if (!t.isEmpty()) {
 543             for (int i = 0; i < v.size(); i++) {
 544                 File f = (File)v.elementAt(i);
 545                 if (t.containsValue(f)) {
 546                     addFile(zos, f);
 547                 }
 548             }
 549         }
 550         if (!foundManifest) {
 551             if (newManifest != null) {
 552                 Manifest m = new Manifest(newManifest);
 553                 updateOk = !isAmbigousMainClass(m);
 554                 if (updateOk) {
 555                     updateManifest(m, zos);
 556                 }
 557             } else if (ename != null) {
 558                 updateManifest(new Manifest(), zos);
 559             }
 560         }
 561         zis.close();
 562         zos.close();
 563         return updateOk;
 564     }
 565 
 566 
 567     private void addIndex(JarIndex index, ZipOutputStream zos)
 568         throws IOException
 569     {


 594         if (ename != null) {
 595             addMainClass(m, ename);
 596         }
 597         ZipEntry e = new ZipEntry(MANIFEST);
 598         e.setTime(System.currentTimeMillis());
 599         if (flag0) {
 600             e.setMethod(ZipEntry.STORED);
 601             crc32Manifest(e, m);
 602         }
 603         zos.putNextEntry(e);
 604         m.write(zos);
 605         if (vflag) {
 606             output(getMsg("out.update.manifest"));
 607         }
 608     }
 609 
 610 
 611     private String entryName(String name) {
 612         name = name.replace(File.separatorChar, '/');
 613         String matchPath = "";
 614         for (int i = 0; i < paths.size(); i++) {
 615             String path = (String)paths.elementAt(i);
 616             if (name.startsWith(path) && (path.length() > matchPath.length())) {
 617                 matchPath = path;
 618             }
 619         }
 620         name = name.substring(matchPath.length());
 621 
 622         if (name.startsWith("/")) {
 623             name = name.substring(1);
 624         } else if (name.startsWith("./")) {
 625             name = name.substring(2);
 626         }
 627         return name;
 628     }
 629 
 630     private void addVersion(Manifest m) {
 631         Attributes global = m.getMainAttributes();
 632         if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
 633             global.put(Attributes.Name.MANIFEST_VERSION, VERSION);
 634         }
 635     }


 652     }
 653 
 654     private boolean isAmbigousMainClass(Manifest m) {
 655         if (ename != null) {
 656             Attributes global = m.getMainAttributes();
 657             if ((global.get(Attributes.Name.MAIN_CLASS) != null)) {
 658                 error(getMsg("error.bad.eflag"));
 659                 usageError();
 660                 return true;
 661             }
 662         }
 663         return false;
 664     }
 665 
 666     /*
 667      * Adds a new file entry to the ZIP output stream.
 668      */
 669     void addFile(ZipOutputStream zos, File file) throws IOException {
 670         String name = file.getPath();
 671         boolean isDir = file.isDirectory();
 672 
 673         if (isDir) {
 674             name = name.endsWith(File.separator) ? name :
 675                 (name + File.separator);
 676         }
 677         name = entryName(name);
 678 
 679         if (name.equals("") || name.equals(".") || name.equals(zname)) {
 680             return;
 681         } else if ((name.equals(MANIFEST_DIR) || name.equals(MANIFEST))
 682                    && !Mflag) {
 683             if (vflag) {
 684                 output(formatMsg("out.ignore.entry", name));
 685             }
 686             return;
 687         }
 688 
 689         long size = isDir ? 0 : file.length();
 690 
 691         if (vflag) {
 692             out.print(formatMsg("out.adding", name));
 693         }
 694         ZipEntry e = new ZipEntry(name);
 695         e.setTime(file.lastModified());
 696         if (size == 0) {
 697             e.setMethod(ZipEntry.STORED);
 698             e.setSize(0);
 699             e.setCrc(0);
 700         } else if (flag0) {
 701             e.setSize(size);
 702             e.setMethod(ZipEntry.STORED);
 703             crc32File(e, file);
 704         }
 705         zos.putNextEntry(e);
 706         if (!isDir) {
 707             byte[] buf = new byte[1024];
 708             int len;
 709             InputStream is = new BufferedInputStream(new FileInputStream(file));
 710             while ((len = is.read(buf, 0, buf.length)) != -1) {
 711                 zos.write(buf, 0, len);
 712             }
 713             is.close();
 714         }
 715         zos.closeEntry();
 716         /* report how much compression occurred. */
 717         if (vflag) {
 718             size = e.getSize();
 719             long csize = e.getCompressedSize();
 720             out.print(formatMsg2("out.size", String.valueOf(size),
 721                         String.valueOf(csize)));
 722             if (e.getMethod() == ZipEntry.DEFLATED) {
 723                 long ratio = 0;
 724                 if (size != 0) {
 725                     ratio = ((size - csize) * 100) / size;
 726                 }
 727                 output(formatMsg("out.deflated", String.valueOf(ratio)));


 732     }
 733 
 734     /*
 735      * compute the crc32 of a file.  This is necessary when the ZipOutputStream
 736      * is in STORED mode.
 737      */
 738     private void crc32Manifest(ZipEntry e, Manifest m) throws IOException {
 739         crc32.reset();
 740         CRC32OutputStream os = new CRC32OutputStream(crc32);
 741         m.write(os);
 742         e.setSize((long) os.n);
 743         e.setCrc(crc32.getValue());
 744     }
 745 
 746     /*
 747      * compute the crc32 of a file.  This is necessary when the ZipOutputStream
 748      * is in STORED mode.
 749      */
 750     private void crc32File(ZipEntry e, File f) throws IOException {
 751         InputStream is = new BufferedInputStream(new FileInputStream(f));
 752         byte[] buf = new byte[1024];
 753         crc32.reset();
 754         int r = 0;
 755         int nread = 0;
 756         long len = f.length();
 757         while ((r = is.read(buf)) != -1) {
 758             nread += r;
 759             crc32.update(buf, 0, r);
 760         }
 761         is.close();
 762         if (nread != (int) len) {
 763             throw new JarException(formatMsg(
 764                         "error.incorrect.length", f.getPath()));
 765         }
 766         e.setCrc(crc32.getValue());
 767     }
 768 
 769     /*
 770      * Extracts specified entries from JAR file.
 771      */
 772     void extract(InputStream in, String files[]) throws IOException {
 773         ZipInputStream zis = new ZipInputStream(in);
 774         ZipEntry e;
 775         // Set of all directory entries specified in archive.  Dissallows
 776         // null entries.  Disallows all entries if using pre-6.0 behavior.
 777         Set<ZipEntry> dirs = new HashSet<ZipEntry>() {
 778             public boolean add(ZipEntry e) {
 779                 return ((e == null || useExtractionTime) ? false : super.add(e));
 780             }};
 781 
 782         while ((e = zis.getNextEntry()) != null) {
 783             if (files == null) {
 784                 dirs.add(extractFile(zis, e));
 785 
 786             } else {
 787                 String name = e.getName();
 788                 for (int i = 0; i < files.length; i++) {
 789                     String file = files[i].replace(File.separatorChar, '/');
 790                     if (name.startsWith(file)) {
 791                         dirs.add(extractFile(zis, e));
 792                         break;
 793                     }
 794                 }
 795             }


 880              * In the case of a compressed (deflated) entry, the entry size
 881              * is stored immediately following the entry data and cannot be
 882              * determined until the entry is fully read. Therefore, we close
 883              * the entry first before printing out its attributes.
 884              */
 885             zis.closeEntry();
 886             if (files == null) {
 887                 printEntry(e);
 888             } else {
 889                 for (int i = 0; i < files.length; i++) {
 890                     String file = files[i].replace(File.separatorChar, '/');
 891                     if (name.startsWith(file)) {
 892                         printEntry(e);
 893                         break;
 894                     }
 895                 }
 896             }
 897         }
 898     }
 899 
 900 
 901     /**
 902      * Output the class index table to the INDEX.LIST file of the
 903      * root jar file.
 904      */
 905     void dumpIndex(String rootjar, JarIndex index) throws IOException {
 906         filesTable.put(INDEX, index);
 907         File scratchFile = File.createTempFile("scratch", null, new File("."));
 908         File jarFile = new File(rootjar);
 909         boolean updateOk = update(new FileInputStream(jarFile),
 910                 new FileOutputStream(scratchFile), null);

 911         jarFile.delete();
 912         if (!scratchFile.renameTo(jarFile)) {
 913             scratchFile.delete();
 914             throw new IOException(getMsg("error.write.file"));
 915         }
 916         scratchFile.delete();
 917     }
 918 
 919     private Hashtable jarTable = new Hashtable();
 920     /*
 921      * Generate the transitive closure of the Class-Path attribute for
 922      * the specified jar file.
 923      */
 924     Vector getJarPath(String jar) throws IOException {
 925         Vector files = new Vector();
 926         files.add(jar);
 927         jarTable.put(jar, jar);
 928 
 929         // take out the current path
 930         String path = jar.substring(0, Math.max(0, jar.lastIndexOf('/') + 1));




  29 import java.util.*;
  30 import java.util.zip.*;
  31 import java.util.jar.*;
  32 import java.util.jar.Manifest;
  33 import java.text.MessageFormat;
  34 import sun.misc.JarIndex;
  35 
  36 /**
  37  * This class implements a simple utility for creating files in the JAR
  38  * (Java Archive) file format. The JAR format is based on the ZIP file
  39  * format, with optional meta-information stored in a MANIFEST entry.
  40  */
  41 public
  42 class Main {
  43     String program;
  44     PrintStream out, err;
  45     String fname, mname, ename;
  46     String zname = "";
  47     String[] files;
  48     String rootjar = null;
  49 
  50     // An entryName(path)->File map generated during "expand", it helps to
  51     // decide whether or not an existing entry in a jar file needs to be
  52     // replaced, during the "update" operation.
  53     Map<String, File> entryMap = new HashMap<String, File>();
  54 
  55     // All files need to be added/updated.
  56     Set<File> entries = new LinkedHashSet<File>();
  57 
  58     // Directories specified by "-C" operation.
  59     List<String> paths = new ArrayList<String>();
  60 
  61     CRC32 crc32 = new CRC32();
  62     /*
  63      * cflag: create
  64      * uflag: update
  65      * xflag: xtract
  66      * tflag: table
  67      * vflag: verbose
  68      * flag0: no zip compression (store only)
  69      * Mflag: DO NOT generate a manifest file (just ZIP)
  70      * iflag: generate jar index
  71      */
  72     boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag;
  73 
  74     static final String MANIFEST = JarFile.MANIFEST_NAME;
  75     static final String MANIFEST_DIR = "META-INF/";
  76     static final String VERSION = "1.0";
  77     static final char SEPARATOR = File.separatorChar;
  78     static final String INDEX = JarIndex.INDEX_NAME;
  79 
  80     private static ResourceBundle rsrc;


 167                             in.close();
 168                         }
 169                         return false;
 170                     }
 171                     if (ename != null) {
 172                         addMainClass(manifest, ename);
 173                     }
 174                 }
 175                 OutputStream out;
 176                 if (fname != null) {
 177                     out = new FileOutputStream(fname);
 178                 } else {
 179                     out = new FileOutputStream(FileDescriptor.out);
 180                     if (vflag) {
 181                         // Disable verbose output so that it does not appear
 182                         // on stdout along with file data
 183                         // error("Warning: -v option ignored");
 184                         vflag = false;
 185                     }
 186                 }
 187                 expand(null, files, false);
 188                 create(new BufferedOutputStream(out, 4096), manifest);
 189                 if (in != null) {
 190                     in.close();
 191                 }
 192                 out.close();
 193             } else if (uflag) {
 194                 File inputFile = null, tmpFile = null;
 195                 FileInputStream in;
 196                 FileOutputStream out;
 197                 if (fname != null) {
 198                     inputFile = new File(fname);
 199                     String path = inputFile.getParent();
 200                     tmpFile = File.createTempFile("tmp", null,
 201                               new File((path == null) ? "." : path));
 202                     in = new FileInputStream(inputFile);
 203                     out = new FileOutputStream(tmpFile);
 204                 } else {
 205                     in = new FileInputStream(FileDescriptor.in);
 206                     out = new FileOutputStream(FileDescriptor.out);
 207                     vflag = false;
 208                 }
 209                 InputStream manifest = (!Mflag && (mname != null)) ?
 210                     (new FileInputStream(mname)) : null;
 211                 expand(null, files, true);
 212                 boolean updateOk = update(in, new BufferedOutputStream(out), manifest, null);
 213                 if (ok) {
 214                     ok = updateOk;
 215                 }
 216                 in.close();
 217                 out.close();
 218                 if (manifest != null) {
 219                     manifest.close();
 220                 }
 221                 if (fname != null) {
 222                     // on Win32, we need this delete
 223                     inputFile.delete();
 224                     if (!tmpFile.renameTo(inputFile)) {
 225                         tmpFile.delete();
 226                         throw new IOException(getMsg("error.write.file"));
 227                     }
 228                     tmpFile.delete();
 229                 }
 230             } else if (xflag || tflag) {
 231                 InputStream in;
 232                 if (fname != null) {


 347             error(getMsg("error.bad.option"));
 348             usageError();
 349             return false;
 350         }
 351         /* parse file arguments */
 352         int n = args.length - count;
 353         if (n > 0) {
 354             int k = 0;
 355             String[] nameBuf = new String[n];
 356             try {
 357                 for (int i = count; i < args.length; i++) {
 358                     if (args[i].equals("-C")) {
 359                         /* change the directory */
 360                         String dir = args[++i];
 361                         dir = (dir.endsWith(File.separator) ?
 362                                dir : (dir + File.separator));
 363                         dir = dir.replace(File.separatorChar, '/');
 364                         while (dir.indexOf("//") > -1) {
 365                             dir = dir.replace("//", "/");
 366                         }
 367                         paths.add(dir.replace(File.separatorChar, '/'));
 368                         nameBuf[k++] = dir + args[++i];
 369                     } else {
 370                         nameBuf[k++] = args[i];
 371                     }
 372                 }
 373             } catch (ArrayIndexOutOfBoundsException e) {
 374                 usageError();
 375                 return false;
 376             }
 377             files = new String[k];
 378             System.arraycopy(nameBuf, 0, files, 0, k);
 379         } else if (cflag && (mname == null)) {
 380             error(getMsg("error.bad.cflag"));
 381             usageError();
 382             return false;
 383         } else if (uflag) {
 384             if ((mname != null) || (ename != null)) {
 385                 /* just want to update the manifest */
 386                 return true;
 387             } else {
 388                 error(getMsg("error.bad.uflag"));
 389                 usageError();
 390                 return false;
 391             }
 392         }
 393         return true;
 394     }
 395 
 396     /*
 397      * Expands list of files to process into full list of all files that
 398      * can be found by recursively descending directories.
 399      */
 400     void expand(File dir, String[] files, boolean isUpdate) {










 401         if (files == null) {
 402             return;
 403         }
 404         for (int i = 0; i < files.length; i++) {
 405             File f;
 406             if (dir == null) {
 407                 f = new File(files[i]);
 408             } else {
 409                 f = new File(dir, files[i]);
 410             }
 411             if (f.isFile()) {
 412                 if (entries.add(f)) {
 413                     if (isUpdate)
 414                         entryMap.put(entryName(f.getPath()), f);
 415                 }
 416             } else if (f.isDirectory()) {
 417                 if (entries.add(f)) {
 418                     if (isUpdate) {
 419                         String dirPath = f.getPath();
 420                         dirPath = (dirPath.endsWith(File.separator)) ? dirPath :
 421                             (dirPath + File.separator);
 422                         entryMap.put(entryName(dirPath), f);
 423                     }
 424                     expand(f, f.list(), isUpdate);
 425                 }
 426             } else {
 427                 error(formatMsg("error.nosuch.fileordir", String.valueOf(f)));
 428                 ok = false;
 429             }
 430         }
 431     }
 432 
 433     /*
 434      * Creates a new JAR file.
 435      */
 436     void create(OutputStream out, Manifest manifest)
 437         throws IOException
 438     {
 439         ZipOutputStream zos = new JarOutputStream(out);
 440         if (flag0) {
 441             zos.setMethod(ZipOutputStream.STORED);
 442         }
 443         if (manifest != null) {
 444             if (vflag) {
 445                 output(getMsg("out.added.manifest"));
 446             }
 447             ZipEntry e = new ZipEntry(MANIFEST_DIR);
 448             e.setTime(System.currentTimeMillis());
 449             e.setSize(0);
 450             e.setCrc(0);
 451             zos.putNextEntry(e);
 452             e = new ZipEntry(MANIFEST);
 453             e.setTime(System.currentTimeMillis());
 454             if (flag0) {
 455                 crc32Manifest(e, manifest);
 456             }
 457             zos.putNextEntry(e);
 458             manifest.write(zos);
 459             zos.closeEntry();
 460         }
 461         for (File file: entries) {
 462             addFile(zos, file);
 463         }
 464         zos.close();
 465     }
 466 
 467     /*
 468      * update an existing jar file.
 469      */
 470     boolean update(InputStream in, OutputStream out,
 471                    InputStream newManifest,
 472                    JarIndex jarIndex) throws IOException
 473     {


 474         ZipInputStream zis = new ZipInputStream(in);
 475         ZipOutputStream zos = new JarOutputStream(out);
 476         ZipEntry e = null;
 477         boolean foundManifest = false;
 478         byte[] buf = new byte[1024];
 479         int n = 0;
 480         boolean updateOk = true;
 481 
 482         if (jarIndex != null) {
 483             addIndex(jarIndex, zos);
 484         }
 485 
 486         // put the old entries first, replace if necessary
 487         while ((e = zis.getNextEntry()) != null) {
 488             String name = e.getName();
 489 
 490             boolean isManifestEntry = name.toUpperCase(
 491                                             java.util.Locale.ENGLISH).
 492                                         equals(MANIFEST);
 493             if ((name.toUpperCase().equals(INDEX) && jarIndex != null)

 494                 || (Mflag && isManifestEntry)) {
 495                 continue;
 496             } else if (isManifestEntry && ((newManifest != null) ||
 497                         (ename != null))) {
 498                 foundManifest = true;
 499                 if (newManifest != null) {
 500                     // Don't read from the newManifest InputStream, as we
 501                     // might need it below, and we can't re-read the same data
 502                     // twice.
 503                     FileInputStream fis = new FileInputStream(mname);
 504                     boolean ambigous = isAmbigousMainClass(new Manifest(fis));
 505                     fis.close();
 506                     if (ambigous) {
 507                         return false;
 508                     }
 509                 }
 510 
 511                 // Update the manifest.
 512                 Manifest old = new Manifest(zis);
 513                 if (newManifest != null) {
 514                     old.read(newManifest);
 515                 }
 516                 updateManifest(old, zos);
 517             } else {
 518                 if (!entryMap.containsKey(name)) { // copy the old stuff

 519                     // do our own compression
 520                     ZipEntry e2 = new ZipEntry(name);
 521                     e2.setMethod(e.getMethod());
 522                     e2.setTime(e.getTime());
 523                     e2.setComment(e.getComment());
 524                     e2.setExtra(e.getExtra());
 525                     if (e.getMethod() == ZipEntry.STORED) {
 526                         e2.setSize(e.getSize());
 527                         e2.setCrc(e.getCrc());
 528                     }
 529                     zos.putNextEntry(e2);
 530                     while ((n = zis.read(buf, 0, buf.length)) != -1) {
 531                         zos.write(buf, 0, n);
 532                     }
 533                 } else { // replace with the new files
 534                     File f = entryMap.get(name);
 535                     addFile(zos, f);
 536                     entryMap.remove(name);
 537                     entries.remove(f);
 538                 }
 539             }
 540         }

 541 
 542         // add the remaining new files
 543         for (File f: entries) {



 544             addFile(zos, f);
 545         }


 546         if (!foundManifest) {
 547             if (newManifest != null) {
 548                 Manifest m = new Manifest(newManifest);
 549                 updateOk = !isAmbigousMainClass(m);
 550                 if (updateOk) {
 551                     updateManifest(m, zos);
 552                 }
 553             } else if (ename != null) {
 554                 updateManifest(new Manifest(), zos);
 555             }
 556         }
 557         zis.close();
 558         zos.close();
 559         return updateOk;
 560     }
 561 
 562 
 563     private void addIndex(JarIndex index, ZipOutputStream zos)
 564         throws IOException
 565     {


 590         if (ename != null) {
 591             addMainClass(m, ename);
 592         }
 593         ZipEntry e = new ZipEntry(MANIFEST);
 594         e.setTime(System.currentTimeMillis());
 595         if (flag0) {
 596             e.setMethod(ZipEntry.STORED);
 597             crc32Manifest(e, m);
 598         }
 599         zos.putNextEntry(e);
 600         m.write(zos);
 601         if (vflag) {
 602             output(getMsg("out.update.manifest"));
 603         }
 604     }
 605 
 606 
 607     private String entryName(String name) {
 608         name = name.replace(File.separatorChar, '/');
 609         String matchPath = "";
 610         for (String path : paths) {

 611             if (name.startsWith(path) && (path.length() > matchPath.length())) {
 612                 matchPath = path;
 613             }
 614         }
 615         name = name.substring(matchPath.length());
 616 
 617         if (name.startsWith("/")) {
 618             name = name.substring(1);
 619         } else if (name.startsWith("./")) {
 620             name = name.substring(2);
 621         }
 622         return name;
 623     }
 624 
 625     private void addVersion(Manifest m) {
 626         Attributes global = m.getMainAttributes();
 627         if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
 628             global.put(Attributes.Name.MANIFEST_VERSION, VERSION);
 629         }
 630     }


 647     }
 648 
 649     private boolean isAmbigousMainClass(Manifest m) {
 650         if (ename != null) {
 651             Attributes global = m.getMainAttributes();
 652             if ((global.get(Attributes.Name.MAIN_CLASS) != null)) {
 653                 error(getMsg("error.bad.eflag"));
 654                 usageError();
 655                 return true;
 656             }
 657         }
 658         return false;
 659     }
 660 
 661     /*
 662      * Adds a new file entry to the ZIP output stream.
 663      */
 664     void addFile(ZipOutputStream zos, File file) throws IOException {
 665         String name = file.getPath();
 666         boolean isDir = file.isDirectory();

 667         if (isDir) {
 668             name = name.endsWith(File.separator) ? name :
 669                 (name + File.separator);
 670         }
 671         name = entryName(name);
 672 
 673         if (name.equals("") || name.equals(".") || name.equals(zname)) {
 674             return;
 675         } else if ((name.equals(MANIFEST_DIR) || name.equals(MANIFEST))
 676                    && !Mflag) {
 677             if (vflag) {
 678                 output(formatMsg("out.ignore.entry", name));
 679             }
 680             return;
 681         }
 682 
 683         long size = isDir ? 0 : file.length();
 684 
 685         if (vflag) {
 686             out.print(formatMsg("out.adding", name));
 687         }
 688         ZipEntry e = new ZipEntry(name);
 689         e.setTime(file.lastModified());
 690         if (size == 0) {
 691             e.setMethod(ZipEntry.STORED);
 692             e.setSize(0);
 693             e.setCrc(0);
 694         } else if (flag0) {
 695             e.setSize(size);
 696             e.setMethod(ZipEntry.STORED);
 697             crc32File(e, file);
 698         }
 699         zos.putNextEntry(e);
 700         if (!isDir) {
 701             byte[] buf = new byte[8192];
 702             int len;
 703             InputStream is = new BufferedInputStream(new FileInputStream(file));
 704             while ((len = is.read(buf, 0, buf.length)) != -1) {
 705                 zos.write(buf, 0, len);
 706             }
 707             is.close();
 708         }
 709         zos.closeEntry();
 710         /* report how much compression occurred. */
 711         if (vflag) {
 712             size = e.getSize();
 713             long csize = e.getCompressedSize();
 714             out.print(formatMsg2("out.size", String.valueOf(size),
 715                         String.valueOf(csize)));
 716             if (e.getMethod() == ZipEntry.DEFLATED) {
 717                 long ratio = 0;
 718                 if (size != 0) {
 719                     ratio = ((size - csize) * 100) / size;
 720                 }
 721                 output(formatMsg("out.deflated", String.valueOf(ratio)));


 726     }
 727 
 728     /*
 729      * compute the crc32 of a file.  This is necessary when the ZipOutputStream
 730      * is in STORED mode.
 731      */
 732     private void crc32Manifest(ZipEntry e, Manifest m) throws IOException {
 733         crc32.reset();
 734         CRC32OutputStream os = new CRC32OutputStream(crc32);
 735         m.write(os);
 736         e.setSize((long) os.n);
 737         e.setCrc(crc32.getValue());
 738     }
 739 
 740     /*
 741      * compute the crc32 of a file.  This is necessary when the ZipOutputStream
 742      * is in STORED mode.
 743      */
 744     private void crc32File(ZipEntry e, File f) throws IOException {
 745         InputStream is = new BufferedInputStream(new FileInputStream(f));
 746         byte[] buf = new byte[8192];
 747         crc32.reset();
 748         int r = 0;
 749         int nread = 0;
 750         long len = f.length();
 751         while ((r = is.read(buf)) != -1) {
 752             nread += r;
 753             crc32.update(buf, 0, r);
 754         }
 755         is.close();
 756         if (nread != (int) len) {
 757             throw new JarException(formatMsg(
 758                         "error.incorrect.length", f.getPath()));
 759         }
 760         e.setCrc(crc32.getValue());
 761     }
 762 
 763     /*
 764      * Extracts specified entries from JAR file.
 765      */
 766     void extract(InputStream in, String files[]) throws IOException {
 767         ZipInputStream zis = new ZipInputStream(in);
 768         ZipEntry e;
 769         // Set of all directory entries specified in archive.  Disallows
 770         // null entries.  Disallows all entries if using pre-6.0 behavior.
 771         Set<ZipEntry> dirs = new HashSet<ZipEntry>() {
 772             public boolean add(ZipEntry e) {
 773                 return ((e == null || useExtractionTime) ? false : super.add(e));
 774             }};
 775 
 776         while ((e = zis.getNextEntry()) != null) {
 777             if (files == null) {
 778                 dirs.add(extractFile(zis, e));
 779 
 780             } else {
 781                 String name = e.getName();
 782                 for (int i = 0; i < files.length; i++) {
 783                     String file = files[i].replace(File.separatorChar, '/');
 784                     if (name.startsWith(file)) {
 785                         dirs.add(extractFile(zis, e));
 786                         break;
 787                     }
 788                 }
 789             }


 874              * In the case of a compressed (deflated) entry, the entry size
 875              * is stored immediately following the entry data and cannot be
 876              * determined until the entry is fully read. Therefore, we close
 877              * the entry first before printing out its attributes.
 878              */
 879             zis.closeEntry();
 880             if (files == null) {
 881                 printEntry(e);
 882             } else {
 883                 for (int i = 0; i < files.length; i++) {
 884                     String file = files[i].replace(File.separatorChar, '/');
 885                     if (name.startsWith(file)) {
 886                         printEntry(e);
 887                         break;
 888                     }
 889                 }
 890             }
 891         }
 892     }
 893 

 894     /**
 895      * Output the class index table to the INDEX.LIST file of the
 896      * root jar file.
 897      */
 898     void dumpIndex(String rootjar, JarIndex index) throws IOException {

 899         File scratchFile = File.createTempFile("scratch", null, new File("."));
 900         File jarFile = new File(rootjar);
 901         boolean updateOk = update(new FileInputStream(jarFile),
 902                                   new FileOutputStream(scratchFile),
 903                                   null, index);
 904         jarFile.delete();
 905         if (!scratchFile.renameTo(jarFile)) {
 906             scratchFile.delete();
 907             throw new IOException(getMsg("error.write.file"));
 908         }
 909         scratchFile.delete();
 910     }
 911 
 912     private Hashtable jarTable = new Hashtable();
 913     /*
 914      * Generate the transitive closure of the Class-Path attribute for
 915      * the specified jar file.
 916      */
 917     Vector getJarPath(String jar) throws IOException {
 918         Vector files = new Vector();
 919         files.add(jar);
 920         jarTable.put(jar, jar);
 921 
 922         // take out the current path
 923         String path = jar.substring(0, Math.max(0, jar.lastIndexOf('/') + 1));