blob: 5f06a538158f9d604c79d95508116a457ebb0567 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.tools.jar;
27
28import java.io.*;
29import java.util.*;
30import java.util.zip.*;
31import java.util.jar.*;
32import java.util.jar.Manifest;
33import java.text.MessageFormat;
34import 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 */
41public
42class 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;
72
73 /**
74 * If true, maintain compatibility with JDK releases prior to 6.0 by
75 * timestamping extracted files with the time at which they are extracted.
76 * Default is to use the time given in the archive.
77 */
78 private static final boolean useExtractionTime =
79 Boolean.getBoolean("sun.tools.jar.useExtractionTime");
80
81 /**
82 * Initialize ResourceBundle
83 */
84 static {
85 try {
86 rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar");
87 } catch (MissingResourceException e) {
88 throw new Error("Fatal: Resource for jar is missing");
89 }
90 }
91
92 private String getMsg(String key) {
93 try {
94 return (rsrc.getString(key));
95 } catch (MissingResourceException e) {
96 throw new Error("Error in message file");
97 }
98 }
99
100 private String formatMsg(String key, String arg) {
101 String msg = getMsg(key);
102 String[] args = new String[1];
103 args[0] = arg;
104 return MessageFormat.format(msg, (Object[]) args);
105 }
106
107 private String formatMsg2(String key, String arg, String arg1) {
108 String msg = getMsg(key);
109 String[] args = new String[2];
110 args[0] = arg;
111 args[1] = arg1;
112 return MessageFormat.format(msg, (Object[]) args);
113 }
114
115 public Main(PrintStream out, PrintStream err, String program) {
116 this.out = out;
117 this.err = err;
118 this.program = program;
119 }
120
121 private boolean ok;
122
123 /*
124 * Starts main program with the specified arguments.
125 */
126 public synchronized boolean run(String args[]) {
127 ok = true;
128 if (!parseArgs(args)) {
129 return false;
130 }
131 try {
132 if (cflag || uflag) {
133 if (fname != null) {
134 // The name of the zip file as it would appear as its own
135 // zip file entry. We use this to make sure that we don't
136 // add the zip file to itself.
137 zname = fname.replace(File.separatorChar, '/');
138 if (zname.startsWith("./")) {
139 zname = zname.substring(2);
140 }
141 }
142 }
143 if (cflag) {
144 Manifest manifest = null;
145 InputStream in = null;
146
147 if (!Mflag) {
148 if (mname != null) {
149 in = new FileInputStream(mname);
150 manifest = new Manifest(new BufferedInputStream(in));
151 } else {
152 manifest = new Manifest();
153 }
154 addVersion(manifest);
155 addCreatedBy(manifest);
156 if (isAmbigousMainClass(manifest)) {
157 if (in != null) {
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) {
223 in = new FileInputStream(fname);
224 } else {
225 in = new FileInputStream(FileDescriptor.in);
226 }
227 if (xflag) {
228 extract(new BufferedInputStream(in), files);
229 } else {
230 list(new BufferedInputStream(in), files);
231 }
232 in.close();
233 } else if (iflag) {
234 genIndex(rootjar, files);
235 }
236 } catch (IOException e) {
237 fatalError(e);
238 ok = false;
239 } catch (Error ee) {
240 ee.printStackTrace();
241 ok = false;
242 } catch (Throwable t) {
243 t.printStackTrace();
244 ok = false;
245 }
246 out.flush();
247 err.flush();
248 return ok;
249 }
250
251 /*
252 * Parse command line arguments.
253 */
254 boolean parseArgs(String args[]) {
255 /* Preprocess and expand @file arguments */
256 try {
257 args = CommandLine.parse(args);
258 } catch (FileNotFoundException e) {
259 fatalError(formatMsg("error.cant.open", e.getMessage()));
260 return false;
261 } catch (IOException e) {
262 fatalError(e);
263 return false;
264 }
265 /* parse flags */
266 int count = 1;
267 try {
268 String flags = args[0];
269 if (flags.startsWith("-")) {
270 flags = flags.substring(1);
271 }
272 for (int i = 0; i < flags.length(); i++) {
273 switch (flags.charAt(i)) {
274 case 'c':
275 if (xflag || tflag || uflag) {
276 usageError();
277 return false;
278 }
279 cflag = true;
280 break;
281 case 'u':
282 if (cflag || xflag || tflag) {
283 usageError();
284 return false;
285 }
286 uflag = true;
287 break;
288 case 'x':
289 if (cflag || uflag || tflag) {
290 usageError();
291 return false;
292 }
293 xflag = true;
294 break;
295 case 't':
296 if (cflag || uflag || xflag) {
297 usageError();
298 return false;
299 }
300 tflag = true;
301 break;
302 case 'M':
303 Mflag = true;
304 break;
305 case 'v':
306 vflag = true;
307 break;
308 case 'f':
309 fname = args[count++];
310 break;
311 case 'm':
312 mname = args[count++];
313 break;
314 case '0':
315 flag0 = true;
316 break;
317 case 'i':
318 // do not increase the counter, files will contain rootjar
319 rootjar = args[count++];
320 iflag = true;
321 break;
322 case 'e':
323 ename = args[count++];
324 break;
325 default:
326 error(formatMsg("error.illegal.option",
327 String.valueOf(flags.charAt(i))));
328 usageError();
329 return false;
330 }
331 }
332 } catch (ArrayIndexOutOfBoundsException e) {
333 usageError();
334 return false;
335 }
336 if (!cflag && !tflag && !xflag && !uflag && !iflag) {
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 {
570 ZipEntry e = new ZipEntry(INDEX);
571 e.setTime(System.currentTimeMillis());
572 if (flag0) {
573 e.setMethod(ZipEntry.STORED);
574 File ifile = File.createTempFile("index", null, new File("."));
575 BufferedOutputStream bos = new BufferedOutputStream
576 (new FileOutputStream(ifile));
577 index.write(bos);
578 crc32File(e, ifile);
579 bos.close();
580 ifile.delete();
581 }
582 zos.putNextEntry(e);
583 index.write(zos);
584 if (vflag) {
585 // output(getMsg("out.update.manifest"));
586 }
587 }
588
589 private void updateManifest(Manifest m, ZipOutputStream zos)
590 throws IOException
591 {
592 addVersion(m);
593 addCreatedBy(m);
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 }
636
637 private void addCreatedBy(Manifest m) {
638 Attributes global = m.getMainAttributes();
639 if (global.getValue(new Attributes.Name("Created-By")) == null) {
640 String javaVendor = System.getProperty("java.vendor");
641 String jdkVersion = System.getProperty("java.version");
642 global.put(new Attributes.Name("Created-By"), jdkVersion + " (" +
643 javaVendor + ")");
644 }
645 }
646
647 private void addMainClass(Manifest m, String mainApp) {
648 Attributes global = m.getMainAttributes();
649
650 // overrides any existing Main-Class attribute
651 global.put(Attributes.Name.MAIN_CLASS, mainApp);
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)));
728 } else {
729 output(getMsg("out.stored"));
730 }
731 }
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 }
796 }
797
798 // Update timestamps of directories specified in archive with their
799 // timestamps as given in the archive. We do this after extraction,
800 // instead of during, because creating a file in a directory changes
801 // that directory's timestamp.
802 for (ZipEntry dirEntry : dirs) {
803 long lastModified = dirEntry.getTime();
804 if (lastModified != -1) {
805 File dir = new File(dirEntry.getName().replace('/', File.separatorChar));
806 dir.setLastModified(lastModified);
807 }
808 }
809 }
810
811 /*
812 * Extracts next entry from JAR file, creating directories as needed. If
813 * the entry is for a directory which doesn't exist prior to this
814 * invocation, returns that entry, otherwise returns null.
815 */
816 ZipEntry extractFile(ZipInputStream zis, ZipEntry e) throws IOException {
817 ZipEntry rc = null;
818 String name = e.getName();
819 File f = new File(e.getName().replace('/', File.separatorChar));
820 if (e.isDirectory()) {
821 if (f.exists()) {
822 if (!f.isDirectory()) {
823 throw new IOException(formatMsg("error.create.dir",
824 f.getPath()));
825 }
826 } else {
827 if (!f.mkdirs()) {
828 throw new IOException(formatMsg("error.create.dir",
829 f.getPath()));
830 } else {
831 rc = e;
832 }
833 }
834
835 if (vflag) {
836 output(formatMsg("out.create", name));
837 }
838 } else {
839 if (f.getParent() != null) {
840 File d = new File(f.getParent());
841 if (!d.exists() && !d.mkdirs() || !d.isDirectory()) {
842 throw new IOException(formatMsg(
843 "error.create.dir", d.getPath()));
844 }
845 }
846 OutputStream os = new FileOutputStream(f);
847 byte[] b = new byte[512];
848 int len;
849 while ((len = zis.read(b, 0, b.length)) != -1) {
850 os.write(b, 0, len);
851 }
852 zis.closeEntry();
853 os.close();
854 if (vflag) {
855 if (e.getMethod() == ZipEntry.DEFLATED) {
856 output(formatMsg("out.inflated", name));
857 } else {
858 output(formatMsg("out.extracted", name));
859 }
860 }
861 }
862 if (!useExtractionTime) {
863 long lastModified = e.getTime();
864 if (lastModified != -1) {
865 f.setLastModified(lastModified);
866 }
867 }
868 return rc;
869 }
870
871 /*
872 * Lists contents of JAR file.
873 */
874 void list(InputStream in, String files[]) throws IOException {
875 ZipInputStream zis = new ZipInputStream(in);
876 ZipEntry e;
877 while ((e = zis.getNextEntry()) != null) {
878 String name = e.getName();
879 /*
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));
931
932 // class path attribute will give us jar file name with
933 // '/' as separators, so we need to change them to the
934 // appropriate one before we open the jar file.
935 JarFile rf = new JarFile(jar.replace('/', File.separatorChar));
936
937 if (rf != null) {
938 Manifest man = rf.getManifest();
939 if (man != null) {
940 Attributes attr = man.getMainAttributes();
941 if (attr != null) {
942 String value = attr.getValue(Attributes.Name.CLASS_PATH);
943 if (value != null) {
944 StringTokenizer st = new StringTokenizer(value);
945 while (st.hasMoreTokens()) {
946 String ajar = st.nextToken();
947 if (!ajar.endsWith("/")) { // it is a jar file
948 ajar = path.concat(ajar);
949 /* check on cyclic dependency */
950 if (jarTable.get(ajar) == null) {
951 files.addAll(getJarPath(ajar));
952 }
953 }
954 }
955 }
956 }
957 }
958 }
959 rf.close();
960 return files;
961 }
962
963 /**
964 * Generate class index file for the specified root jar file.
965 */
966 void genIndex(String rootjar, String[] files) throws IOException {
967 Vector jars = getJarPath(rootjar);
968 int njars = jars.size();
969 String[] jarfiles;
970
971 if (njars == 1 && files != null) {
972 // no class-path attribute defined in rootjar, will
973 // use command line specified list of jars
974 for (int i = 0; i < files.length; i++) {
975 jars.addAll(getJarPath(files[i]));
976 }
977 njars = jars.size();
978 }
979 jarfiles = (String[])jars.toArray(new String[njars]);
980 JarIndex index = new JarIndex(jarfiles);
981 dumpIndex(rootjar, index);
982 }
983
984
985 /*
986 * Prints entry information.
987 */
988 void printEntry(ZipEntry e) throws IOException {
989 if (vflag) {
990 StringBuffer sb = new StringBuffer();
991 String s = Long.toString(e.getSize());
992 for (int i = 6 - s.length(); i > 0; --i) {
993 sb.append(' ');
994 }
995 sb.append(s).append(' ').append(new Date(e.getTime()).toString());
996 sb.append(' ').append(e.getName());
997 output(sb.toString());
998 } else {
999 output(e.getName());
1000 }
1001 }
1002
1003 /*
1004 * Print usage message and die.
1005 */
1006 void usageError() {
1007 error(getMsg("usage"));
1008 }
1009
1010 /*
1011 * A fatal exception has been caught. No recovery possible
1012 */
1013 void fatalError(Exception e) {
1014 e.printStackTrace();
1015 }
1016
1017 /*
1018 * A fatal condition has been detected; message is "s".
1019 * No recovery possible
1020 */
1021 void fatalError(String s) {
1022 error(program + ": " + s);
1023 }
1024
1025 /**
1026 * Print an output message; like verbose output and the like
1027 */
1028 protected void output(String s) {
1029 out.println(s);
1030 }
1031
1032 /**
1033 * Print an error mesage; like something is broken
1034 */
1035 protected void error(String s) {
1036 err.println(s);
1037 }
1038
1039 /*
1040 * Main routine to start program.
1041 */
1042 public static void main(String args[]) {
1043 Main jartool = new Main(System.out, System.err, "jar");
1044 System.exit(jartool.run(args) ? 0 : 1);
1045 }
1046}
1047
1048/*
1049 * an OutputStream that doesn't send its output anywhere, (but could).
1050 * It's here to find the CRC32 of a manifest, necessary for STORED only
1051 * mode in ZIP.
1052 */
1053final class CRC32OutputStream extends java.io.OutputStream {
1054 CRC32 crc;
1055 int n = 0;
1056 CRC32OutputStream(CRC32 crc) {
1057 this.crc = crc;
1058 }
1059
1060 public void write(int r) throws IOException {
1061 crc.update(r);
1062 n++;
1063 }
1064
1065 public void write(byte[] b) throws IOException {
1066 crc.update(b, 0, b.length);
1067 n += b.length;
1068 }
1069
1070 public void write(byte[] b, int off, int len) throws IOException {
1071 crc.update(b, off, len);
1072 n += len - off;
1073 }
1074}