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));
|