History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/tools/jar/Main.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/tools/jar/Main.java+1211−0
@@ -0,0 +1,1211 @@
1/*
2 * Copyright (c) 1996, 2011, Oracle and/or its affiliates. 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.tools.jar;
27
28import java.io.*;
29import java.nio.file.Path;
30import java.nio.file.Files;
31import java.util.*;
32import java.util.zip.*;
33import java.util.jar.*;
34import java.util.jar.Manifest;
35import java.text.MessageFormat;
36import sun.misc.JarIndex;
37import static sun.misc.JarIndex.INDEX_NAME;
38import static java.util.jar.JarFile.MANIFEST_NAME;
39import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
40
41/**
42 * This class implements a simple utility for creating files in the JAR
43 * (Java Archive) file format. The JAR format is based on the ZIP file
44 * format, with optional meta-information stored in a MANIFEST entry.
45 */
46public
47class Main {
48 String program;
49 PrintStream out, err;
50 String fname, mname, ename;
51 String zname = "";
52 String[] files;
53 String rootjar = null;
54
55 // An entryName(path)->File map generated during "expand", it helps to
56 // decide whether or not an existing entry in a jar file needs to be
57 // replaced, during the "update" operation.
58 Map<String, File> entryMap = new HashMap<String, File>();
59
60 // All files need to be added/updated.
61 Set<File> entries = new LinkedHashSet<File>();
62
63 // Directories specified by "-C" operation.
64 Set<String> paths = new HashSet<String>();
65
66 /*
67 * cflag: create
68 * uflag: update
69 * xflag: xtract
70 * tflag: table
71 * vflag: verbose
72 * flag0: no zip compression (store only)
73 * Mflag: DO NOT generate a manifest file (just ZIP)
74 * iflag: generate jar index
75 */
76 boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag;
77
78 static final String MANIFEST_DIR = "META-INF/";
79 static final String VERSION = "1.0";
80
81 private static ResourceBundle rsrc;
82
83 /**
84 * If true, maintain compatibility with JDK releases prior to 6.0 by
85 * timestamping extracted files with the time at which they are extracted.
86 * Default is to use the time given in the archive.
87 */
88 private static final boolean useExtractionTime =
89 Boolean.getBoolean("sun.tools.jar.useExtractionTime");
90
91 /**
92 * Initialize ResourceBundle
93 */
94 static {
95 try {
96 rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar");
97 } catch (MissingResourceException e) {
98 throw new Error("Fatal: Resource for jar is missing");
99 }
100 }
101
102 private String getMsg(String key) {
103 try {
104 return (rsrc.getString(key));
105 } catch (MissingResourceException e) {
106 throw new Error("Error in message file");
107 }
108 }
109
110 private String formatMsg(String key, String arg) {
111 String msg = getMsg(key);
112 String[] args = new String[1];
113 args[0] = arg;
114 return MessageFormat.format(msg, (Object[]) args);
115 }
116
117 private String formatMsg2(String key, String arg, String arg1) {
118 String msg = getMsg(key);
119 String[] args = new String[2];
120 args[0] = arg;
121 args[1] = arg1;
122 return MessageFormat.format(msg, (Object[]) args);
123 }
124
125 public Main(PrintStream out, PrintStream err, String program) {
126 this.out = out;
127 this.err = err;
128 this.program = program;
129 }
130
131 /**
132 * Creates a new empty temporary file in the same directory as the
133 * specified file. A variant of File.createTempFile.
134 */
135 private static File createTempFileInSameDirectoryAs(File file)
136 throws IOException {
137 File dir = file.getParentFile();
138 if (dir == null)
139 dir = new File(".");
140 return File.createTempFile("jartmp", null, dir);
141 }
142
143 private boolean ok;
144
145 /**
146 * Starts main program with the specified arguments.
147 */
148 public synchronized boolean run(String args[]) {
149 ok = true;
150 if (!parseArgs(args)) {
151 return false;
152 }
153 try {
154 if (cflag || uflag) {
155 if (fname != null) {
156 // The name of the zip file as it would appear as its own
157 // zip file entry. We use this to make sure that we don't
158 // add the zip file to itself.
159 zname = fname.replace(File.separatorChar, '/');
160 if (zname.startsWith("./")) {
161 zname = zname.substring(2);
162 }
163 }
164 }
165 if (cflag) {
166 Manifest manifest = null;
167 InputStream in = null;
168
169 if (!Mflag) {
170 if (mname != null) {
171 in = new FileInputStream(mname);
172 manifest = new Manifest(new BufferedInputStream(in));
173 } else {
174 manifest = new Manifest();
175 }
176 addVersion(manifest);
177 addCreatedBy(manifest);
178 if (isAmbiguousMainClass(manifest)) {
179 if (in != null) {
180 in.close();
181 }
182 return false;
183 }
184 if (ename != null) {
185 addMainClass(manifest, ename);
186 }
187 }
188 OutputStream out;
189 if (fname != null) {
190 out = new FileOutputStream(fname);
191 } else {
192 out = new FileOutputStream(FileDescriptor.out);
193 if (vflag) {
194 // Disable verbose output so that it does not appear
195 // on stdout along with file data
196 // error("Warning: -v option ignored");
197 vflag = false;
198 }
199 }
200 expand(null, files, false);
201 create(new BufferedOutputStream(out, 4096), manifest);
202 if (in != null) {
203 in.close();
204 }
205 out.close();
206 } else if (uflag) {
207 File inputFile = null, tmpFile = null;
208 FileInputStream in;
209 FileOutputStream out;
210 if (fname != null) {
211 inputFile = new File(fname);
212 tmpFile = createTempFileInSameDirectoryAs(inputFile);
213 in = new FileInputStream(inputFile);
214 out = new FileOutputStream(tmpFile);
215 } else {
216 in = new FileInputStream(FileDescriptor.in);
217 out = new FileOutputStream(FileDescriptor.out);
218 vflag = false;
219 }
220 InputStream manifest = (!Mflag && (mname != null)) ?
221 (new FileInputStream(mname)) : null;
222 expand(null, files, true);
223 boolean updateOk = update(in, new BufferedOutputStream(out),
224 manifest, null);
225 if (ok) {
226 ok = updateOk;
227 }
228 in.close();
229 out.close();
230 if (manifest != null) {
231 manifest.close();
232 }
233 if (fname != null) {
234 // on Win32, we need this delete
235 inputFile.delete();
236 if (!tmpFile.renameTo(inputFile)) {
237 tmpFile.delete();
238 throw new IOException(getMsg("error.write.file"));
239 }
240 tmpFile.delete();
241 }
242 } else if (tflag) {
243 replaceFSC(files);
244 if (fname != null) {
245 list(fname, files);
246 } else {
247 InputStream in = new FileInputStream(FileDescriptor.in);
248 try{
249 list(new BufferedInputStream(in), files);
250 } finally {
251 in.close();
252 }
253 }
254 } else if (xflag) {
255 replaceFSC(files);
256 if (fname != null && files != null) {
257 extract(fname, files);
258 } else {
259 InputStream in = (fname == null)
260 ? new FileInputStream(FileDescriptor.in)
261 : new FileInputStream(fname);
262 try {
263 extract(new BufferedInputStream(in), files);
264 } finally {
265 in.close();
266 }
267 }
268 } else if (iflag) {
269 genIndex(rootjar, files);
270 }
271 } catch (IOException e) {
272 fatalError(e);
273 ok = false;
274 } catch (Error ee) {
275 ee.printStackTrace();
276 ok = false;
277 } catch (Throwable t) {
278 t.printStackTrace();
279 ok = false;
280 }
281 out.flush();
282 err.flush();
283 return ok;
284 }
285
286 /**
287 * Parses command line arguments.
288 */
289 boolean parseArgs(String args[]) {
290 /* Preprocess and expand @file arguments */
291 try {
292 args = CommandLine.parse(args);
293 } catch (FileNotFoundException e) {
294 fatalError(formatMsg("error.cant.open", e.getMessage()));
295 return false;
296 } catch (IOException e) {
297 fatalError(e);
298 return false;
299 }
300 /* parse flags */
301 int count = 1;
302 try {
303 String flags = args[0];
304 if (flags.startsWith("-")) {
305 flags = flags.substring(1);
306 }
307 for (int i = 0; i < flags.length(); i++) {
308 switch (flags.charAt(i)) {
309 case 'c':
310 if (xflag || tflag || uflag || iflag) {
311 usageError();
312 return false;
313 }
314 cflag = true;
315 break;
316 case 'u':
317 if (cflag || xflag || tflag || iflag) {
318 usageError();
319 return false;
320 }
321 uflag = true;
322 break;
323 case 'x':
324 if (cflag || uflag || tflag || iflag) {
325 usageError();
326 return false;
327 }
328 xflag = true;
329 break;
330 case 't':
331 if (cflag || uflag || xflag || iflag) {
332 usageError();
333 return false;
334 }
335 tflag = true;
336 break;
337 case 'M':
338 Mflag = true;
339 break;
340 case 'v':
341 vflag = true;
342 break;
343 case 'f':
344 fname = args[count++];
345 break;
346 case 'm':
347 mname = args[count++];
348 break;
349 case '0':
350 flag0 = true;
351 break;
352 case 'i':
353 if (cflag || uflag || xflag || tflag) {
354 usageError();
355 return false;
356 }
357 // do not increase the counter, files will contain rootjar
358 rootjar = args[count++];
359 iflag = true;
360 break;
361 case 'e':
362 ename = args[count++];
363 break;
364 default:
365 error(formatMsg("error.illegal.option",
366 String.valueOf(flags.charAt(i))));
367 usageError();
368 return false;
369 }
370 }
371 } catch (ArrayIndexOutOfBoundsException e) {
372 usageError();
373 return false;
374 }
375 if (!cflag && !tflag && !xflag && !uflag && !iflag) {
376 error(getMsg("error.bad.option"));
377 usageError();
378 return false;
379 }
380 /* parse file arguments */
381 int n = args.length - count;
382 if (n > 0) {
383 int k = 0;
384 String[] nameBuf = new String[n];
385 try {
386 for (int i = count; i < args.length; i++) {
387 if (args[i].equals("-C")) {
388 /* change the directory */
389 String dir = args[++i];
390 dir = (dir.endsWith(File.separator) ?
391 dir : (dir + File.separator));
392 dir = dir.replace(File.separatorChar, '/');
393 while (dir.indexOf("//") > -1) {
394 dir = dir.replace("//", "/");
395 }
396 paths.add(dir.replace(File.separatorChar, '/'));
397 nameBuf[k++] = dir + args[++i];
398 } else {
399 nameBuf[k++] = args[i];
400 }
401 }
402 } catch (ArrayIndexOutOfBoundsException e) {
403 usageError();
404 return false;
405 }
406 files = new String[k];
407 System.arraycopy(nameBuf, 0, files, 0, k);
408 } else if (cflag && (mname == null)) {
409 error(getMsg("error.bad.cflag"));
410 usageError();
411 return false;
412 } else if (uflag) {
413 if ((mname != null) || (ename != null)) {
414 /* just want to update the manifest */
415 return true;
416 } else {
417 error(getMsg("error.bad.uflag"));
418 usageError();
419 return false;
420 }
421 }
422 return true;
423 }
424
425 /**
426 * Expands list of files to process into full list of all files that
427 * can be found by recursively descending directories.
428 */
429 void expand(File dir, String[] files, boolean isUpdate) {
430 if (files == null) {
431 return;
432 }
433 for (int i = 0; i < files.length; i++) {
434 File f;
435 if (dir == null) {
436 f = new File(files[i]);
437 } else {
438 f = new File(dir, files[i]);
439 }
440 if (f.isFile()) {
441 if (entries.add(f)) {
442 if (isUpdate)
443 entryMap.put(entryName(f.getPath()), f);
444 }
445 } else if (f.isDirectory()) {
446 if (entries.add(f)) {
447 if (isUpdate) {
448 String dirPath = f.getPath();
449 dirPath = (dirPath.endsWith(File.separator)) ? dirPath :
450 (dirPath + File.separator);
451 entryMap.put(entryName(dirPath), f);
452 }
453 expand(f, f.list(), isUpdate);
454 }
455 } else {
456 error(formatMsg("error.nosuch.fileordir", String.valueOf(f)));
457 ok = false;
458 }
459 }
460 }
461
462 /**
463 * Creates a new JAR file.
464 */
465 void create(OutputStream out, Manifest manifest)
466 throws IOException
467 {
468 ZipOutputStream zos = new JarOutputStream(out);
469 if (flag0) {
470 zos.setMethod(ZipOutputStream.STORED);
471 }
472 if (manifest != null) {
473 if (vflag) {
474 output(getMsg("out.added.manifest"));
475 }
476 ZipEntry e = new ZipEntry(MANIFEST_DIR);
477 e.setTime(System.currentTimeMillis());
478 e.setSize(0);
479 e.setCrc(0);
480 zos.putNextEntry(e);
481 e = new ZipEntry(MANIFEST_NAME);
482 e.setTime(System.currentTimeMillis());
483 if (flag0) {
484 crc32Manifest(e, manifest);
485 }
486 zos.putNextEntry(e);
487 manifest.write(zos);
488 zos.closeEntry();
489 }
490 for (File file: entries) {
491 addFile(zos, file);
492 }
493 zos.close();
494 }
495
496 private char toUpperCaseASCII(char c) {
497 return (c < 'a' || c > 'z') ? c : (char) (c + 'A' - 'a');
498 }
499
500 /**
501 * Compares two strings for equality, ignoring case. The second
502 * argument must contain only upper-case ASCII characters.
503 * We don't want case comparison to be locale-dependent (else we
504 * have the notorious "turkish i bug").
505 */
506 private boolean equalsIgnoreCase(String s, String upper) {
507 assert upper.toUpperCase(java.util.Locale.ENGLISH).equals(upper);
508 int len;
509 if ((len = s.length()) != upper.length())
510 return false;
511 for (int i = 0; i < len; i++) {
512 char c1 = s.charAt(i);
513 char c2 = upper.charAt(i);
514 if (c1 != c2 && toUpperCaseASCII(c1) != c2)
515 return false;
516 }
517 return true;
518 }
519
520 /**
521 * Updates an existing jar file.
522 */
523 boolean update(InputStream in, OutputStream out,
524 InputStream newManifest,
525 JarIndex jarIndex) throws IOException
526 {
527 ZipInputStream zis = new ZipInputStream(in);
528 ZipOutputStream zos = new JarOutputStream(out);
529 ZipEntry e = null;
530 boolean foundManifest = false;
531 boolean updateOk = true;
532
533 if (jarIndex != null) {
534 addIndex(jarIndex, zos);
535 }
536
537 // put the old entries first, replace if necessary
538 while ((e = zis.getNextEntry()) != null) {
539 String name = e.getName();
540
541 boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
542
543 if ((jarIndex != null && equalsIgnoreCase(name, INDEX_NAME))
544 || (Mflag && isManifestEntry)) {
545 continue;
546 } else if (isManifestEntry && ((newManifest != null) ||
547 (ename != null))) {
548 foundManifest = true;
549 if (newManifest != null) {
550 // Don't read from the newManifest InputStream, as we
551 // might need it below, and we can't re-read the same data
552 // twice.
553 FileInputStream fis = new FileInputStream(mname);
554 boolean ambiguous = isAmbiguousMainClass(new Manifest(fis));
555 fis.close();
556 if (ambiguous) {
557 return false;
558 }
559 }
560
561 // Update the manifest.
562 Manifest old = new Manifest(zis);
563 if (newManifest != null) {
564 old.read(newManifest);
565 }
566 updateManifest(old, zos);
567 } else {
568 if (!entryMap.containsKey(name)) { // copy the old stuff
569 // do our own compression
570 ZipEntry e2 = new ZipEntry(name);
571 e2.setMethod(e.getMethod());
572 e2.setTime(e.getTime());
573 e2.setComment(e.getComment());
574 e2.setExtra(e.getExtra());
575 if (e.getMethod() == ZipEntry.STORED) {
576 e2.setSize(e.getSize());
577 e2.setCrc(e.getCrc());
578 }
579 zos.putNextEntry(e2);
580 copy(zis, zos);
581 } else { // replace with the new files
582 File f = entryMap.get(name);
583 addFile(zos, f);
584 entryMap.remove(name);
585 entries.remove(f);
586 }
587 }
588 }
589
590 // add the remaining new files
591 for (File f: entries) {
592 addFile(zos, f);
593 }
594 if (!foundManifest) {
595 if (newManifest != null) {
596 Manifest m = new Manifest(newManifest);
597 updateOk = !isAmbiguousMainClass(m);
598 if (updateOk) {
599 updateManifest(m, zos);
600 }
601 } else if (ename != null) {
602 updateManifest(new Manifest(), zos);
603 }
604 }
605 zis.close();
606 zos.close();
607 return updateOk;
608 }
609
610
611 private void addIndex(JarIndex index, ZipOutputStream zos)
612 throws IOException
613 {
614 ZipEntry e = new ZipEntry(INDEX_NAME);
615 e.setTime(System.currentTimeMillis());
616 if (flag0) {
617 CRC32OutputStream os = new CRC32OutputStream();
618 index.write(os);
619 os.updateEntry(e);
620 }
621 zos.putNextEntry(e);
622 index.write(zos);
623 zos.closeEntry();
624 }
625
626 private void updateManifest(Manifest m, ZipOutputStream zos)
627 throws IOException
628 {
629 addVersion(m);
630 addCreatedBy(m);
631 if (ename != null) {
632 addMainClass(m, ename);
633 }
634 ZipEntry e = new ZipEntry(MANIFEST_NAME);
635 e.setTime(System.currentTimeMillis());
636 if (flag0) {
637 crc32Manifest(e, m);
638 }
639 zos.putNextEntry(e);
640 m.write(zos);
641 if (vflag) {
642 output(getMsg("out.update.manifest"));
643 }
644 }
645
646
647 private String entryName(String name) {
648 name = name.replace(File.separatorChar, '/');
649 String matchPath = "";
650 for (String path : paths) {
651 if (name.startsWith(path)
652 && (path.length() > matchPath.length())) {
653 matchPath = path;
654 }
655 }
656 name = name.substring(matchPath.length());
657
658 if (name.startsWith("/")) {
659 name = name.substring(1);
660 } else if (name.startsWith("./")) {
661 name = name.substring(2);
662 }
663 return name;
664 }
665
666 private void addVersion(Manifest m) {
667 Attributes global = m.getMainAttributes();
668 if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
669 global.put(Attributes.Name.MANIFEST_VERSION, VERSION);
670 }
671 }
672
673 private void addCreatedBy(Manifest m) {
674 Attributes global = m.getMainAttributes();
675 if (global.getValue(new Attributes.Name("Created-By")) == null) {
676 String javaVendor = System.getProperty("java.vendor");
677 String jdkVersion = System.getProperty("java.version");
678 global.put(new Attributes.Name("Created-By"), jdkVersion + " (" +
679 javaVendor + ")");
680 }
681 }
682
683 private void addMainClass(Manifest m, String mainApp) {
684 Attributes global = m.getMainAttributes();
685
686 // overrides any existing Main-Class attribute
687 global.put(Attributes.Name.MAIN_CLASS, mainApp);
688 }
689
690 private boolean isAmbiguousMainClass(Manifest m) {
691 if (ename != null) {
692 Attributes global = m.getMainAttributes();
693 if ((global.get(Attributes.Name.MAIN_CLASS) != null)) {
694 error(getMsg("error.bad.eflag"));
695 usageError();
696 return true;
697 }
698 }
699 return false;
700 }
701
702 /**
703 * Adds a new file entry to the ZIP output stream.
704 */
705 void addFile(ZipOutputStream zos, File file) throws IOException {
706 String name = file.getPath();
707 boolean isDir = file.isDirectory();
708 if (isDir) {
709 name = name.endsWith(File.separator) ? name :
710 (name + File.separator);
711 }
712 name = entryName(name);
713
714 if (name.equals("") || name.equals(".") || name.equals(zname)) {
715 return;
716 } else if ((name.equals(MANIFEST_DIR) || name.equals(MANIFEST_NAME))
717 && !Mflag) {
718 if (vflag) {
719 output(formatMsg("out.ignore.entry", name));
720 }
721 return;
722 }
723
724 long size = isDir ? 0 : file.length();
725
726 if (vflag) {
727 out.print(formatMsg("out.adding", name));
728 }
729 ZipEntry e = new ZipEntry(name);
730 e.setTime(file.lastModified());
731 if (size == 0) {
732 e.setMethod(ZipEntry.STORED);
733 e.setSize(0);
734 e.setCrc(0);
735 } else if (flag0) {
736 crc32File(e, file);
737 }
738 zos.putNextEntry(e);
739 if (!isDir) {
740 copy(file, zos);
741 }
742 zos.closeEntry();
743 /* report how much compression occurred. */
744 if (vflag) {
745 size = e.getSize();
746 long csize = e.getCompressedSize();
747 out.print(formatMsg2("out.size", String.valueOf(size),
748 String.valueOf(csize)));
749 if (e.getMethod() == ZipEntry.DEFLATED) {
750 long ratio = 0;
751 if (size != 0) {
752 ratio = ((size - csize) * 100) / size;
753 }
754 output(formatMsg("out.deflated", String.valueOf(ratio)));
755 } else {
756 output(getMsg("out.stored"));
757 }
758 }
759 }
760
761 /**
762 * A buffer for use only by copy(InputStream, OutputStream).
763 * Not as clean as allocating a new buffer as needed by copy,
764 * but significantly more efficient.
765 */
766 private byte[] copyBuf = new byte[8192];
767
768 /**
769 * Copies all bytes from the input stream to the output stream.
770 * Does not close or flush either stream.
771 *
772 * @param from the input stream to read from
773 * @param to the output stream to write to
774 * @throws IOException if an I/O error occurs
775 */
776 private void copy(InputStream from, OutputStream to) throws IOException {
777 int n;
778 while ((n = from.read(copyBuf)) != -1)
779 to.write(copyBuf, 0, n);
780 }
781
782 /**
783 * Copies all bytes from the input file to the output stream.
784 * Does not close or flush the output stream.
785 *
786 * @param from the input file to read from
787 * @param to the output stream to write to
788 * @throws IOException if an I/O error occurs
789 */
790 private void copy(File from, OutputStream to) throws IOException {
791 InputStream in = new FileInputStream(from);
792 try {
793 copy(in, to);
794 } finally {
795 in.close();
796 }
797 }
798
799 /**
800 * Copies all bytes from the input stream to the output file.
801 * Does not close the input stream.
802 *
803 * @param from the input stream to read from
804 * @param to the output file to write to
805 * @throws IOException if an I/O error occurs
806 */
807 private void copy(InputStream from, File to) throws IOException {
808 OutputStream out = new FileOutputStream(to);
809 try {
810 copy(from, out);
811 } finally {
812 out.close();
813 }
814 }
815
816 /**
817 * Computes the crc32 of a Manifest. This is necessary when the
818 * ZipOutputStream is in STORED mode.
819 */
820 private void crc32Manifest(ZipEntry e, Manifest m) throws IOException {
821 CRC32OutputStream os = new CRC32OutputStream();
822 m.write(os);
823 os.updateEntry(e);
824 }
825
826 /**
827 * Computes the crc32 of a File. This is necessary when the
828 * ZipOutputStream is in STORED mode.
829 */
830 private void crc32File(ZipEntry e, File f) throws IOException {
831 CRC32OutputStream os = new CRC32OutputStream();
832 copy(f, os);
833 if (os.n != f.length()) {
834 throw new JarException(formatMsg(
835 "error.incorrect.length", f.getPath()));
836 }
837 os.updateEntry(e);
838 }
839
840 void replaceFSC(String files[]) {
841 if (files != null) {
842 for (String file : files) {
843 file = file.replace(File.separatorChar, '/');
844 }
845 }
846 }
847
848 @SuppressWarnings("serial")
849 Set<ZipEntry> newDirSet() {
850 return new HashSet<ZipEntry>() {
851 public boolean add(ZipEntry e) {
852 return ((e == null || useExtractionTime) ? false : super.add(e));
853 }};
854 }
855
856 void updateLastModifiedTime(Set<ZipEntry> zes) throws IOException {
857 for (ZipEntry ze : zes) {
858 long lastModified = ze.getTime();
859 if (lastModified != -1) {
860 File f = new File(ze.getName().replace('/', File.separatorChar));
861 f.setLastModified(lastModified);
862 }
863 }
864 }
865
866 /**
867 * Extracts specified entries from JAR file.
868 */
869 void extract(InputStream in, String files[]) throws IOException {
870 ZipInputStream zis = new ZipInputStream(in);
871 ZipEntry e;
872 // Set of all directory entries specified in archive. Disallows
873 // null entries. Disallows all entries if using pre-6.0 behavior.
874 Set<ZipEntry> dirs = newDirSet();
875 while ((e = zis.getNextEntry()) != null) {
876 if (files == null) {
877 dirs.add(extractFile(zis, e));
878 } else {
879 String name = e.getName();
880 for (String file : files) {
881 if (name.startsWith(file)) {
882 dirs.add(extractFile(zis, e));
883 break;
884 }
885 }
886 }
887 }
888
889 // Update timestamps of directories specified in archive with their
890 // timestamps as given in the archive. We do this after extraction,
891 // instead of during, because creating a file in a directory changes
892 // that directory's timestamp.
893 updateLastModifiedTime(dirs);
894 }
895
896 /**
897 * Extracts specified entries from JAR file, via ZipFile.
898 */
899 void extract(String fname, String files[]) throws IOException {
900 ZipFile zf = new ZipFile(fname);
901 Set<ZipEntry> dirs = newDirSet();
902 Enumeration<? extends ZipEntry> zes = zf.entries();
903 while (zes.hasMoreElements()) {
904 ZipEntry e = zes.nextElement();
905 InputStream is;
906 if (files == null) {
907 dirs.add(extractFile(zf.getInputStream(e), e));
908 } else {
909 String name = e.getName();
910 for (String file : files) {
911 if (name.startsWith(file)) {
912 dirs.add(extractFile(zf.getInputStream(e), e));
913 break;
914 }
915 }
916 }
917 }
918 zf.close();
919 updateLastModifiedTime(dirs);
920 }
921
922 /**
923 * Extracts next entry from JAR file, creating directories as needed. If
924 * the entry is for a directory which doesn't exist prior to this
925 * invocation, returns that entry, otherwise returns null.
926 */
927 ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException {
928 ZipEntry rc = null;
929 String name = e.getName();
930 File f = new File(e.getName().replace('/', File.separatorChar));
931 if (e.isDirectory()) {
932 if (f.exists()) {
933 if (!f.isDirectory()) {
934 throw new IOException(formatMsg("error.create.dir",
935 f.getPath()));
936 }
937 } else {
938 if (!f.mkdirs()) {
939 throw new IOException(formatMsg("error.create.dir",
940 f.getPath()));
941 } else {
942 rc = e;
943 }
944 }
945
946 if (vflag) {
947 output(formatMsg("out.create", name));
948 }
949 } else {
950 if (f.getParent() != null) {
951 File d = new File(f.getParent());
952 if (!d.exists() && !d.mkdirs() || !d.isDirectory()) {
953 throw new IOException(formatMsg(
954 "error.create.dir", d.getPath()));
955 }
956 }
957 try {
958 copy(is, f);
959 } finally {
960 if (is instanceof ZipInputStream)
961 ((ZipInputStream)is).closeEntry();
962 else
963 is.close();
964 }
965 if (vflag) {
966 if (e.getMethod() == ZipEntry.DEFLATED) {
967 output(formatMsg("out.inflated", name));
968 } else {
969 output(formatMsg("out.extracted", name));
970 }
971 }
972 }
973 if (!useExtractionTime) {
974 long lastModified = e.getTime();
975 if (lastModified != -1) {
976 f.setLastModified(lastModified);
977 }
978 }
979 return rc;
980 }
981
982 /**
983 * Lists contents of JAR file.
984 */
985 void list(InputStream in, String files[]) throws IOException {
986 ZipInputStream zis = new ZipInputStream(in);
987 ZipEntry e;
988 while ((e = zis.getNextEntry()) != null) {
989 /*
990 * In the case of a compressed (deflated) entry, the entry size
991 * is stored immediately following the entry data and cannot be
992 * determined until the entry is fully read. Therefore, we close
993 * the entry first before printing out its attributes.
994 */
995 zis.closeEntry();
996 printEntry(e, files);
997 }
998 }
999
1000 /**
1001 * Lists contents of JAR file, via ZipFile.
1002 */
1003 void list(String fname, String files[]) throws IOException {
1004 ZipFile zf = new ZipFile(fname);
1005 Enumeration<? extends ZipEntry> zes = zf.entries();
1006 while (zes.hasMoreElements()) {
1007 printEntry(zes.nextElement(), files);
1008 }
1009 zf.close();
1010 }
1011
1012 /**
1013 * Outputs the class index table to the INDEX.LIST file of the
1014 * root jar file.
1015 */
1016 void dumpIndex(String rootjar, JarIndex index) throws IOException {
1017 File jarFile = new File(rootjar);
1018 Path jarPath = jarFile.toPath();
1019 Path tmpPath = createTempFileInSameDirectoryAs(jarFile).toPath();
1020 try {
1021 if (update(Files.newInputStream(jarPath),
1022 Files.newOutputStream(tmpPath),
1023 null, index)) {
1024 try {
1025 Files.move(tmpPath, jarPath, REPLACE_EXISTING);
1026 } catch (IOException e) {
1027 throw new IOException(getMsg("error.write.file"), e);
1028 }
1029 }
1030 } finally {
1031 Files.deleteIfExists(tmpPath);
1032 }
1033 }
1034
1035 private HashSet<String> jarPaths = new HashSet<String>();
1036
1037 /**
1038 * Generates the transitive closure of the Class-Path attribute for
1039 * the specified jar file.
1040 */
1041 List<String> getJarPath(String jar) throws IOException {
1042 List<String> files = new ArrayList<String>();
1043 files.add(jar);
1044 jarPaths.add(jar);
1045
1046 // take out the current path
1047 String path = jar.substring(0, Math.max(0, jar.lastIndexOf('/') + 1));
1048
1049 // class path attribute will give us jar file name with
1050 // '/' as separators, so we need to change them to the
1051 // appropriate one before we open the jar file.
1052 JarFile rf = new JarFile(jar.replace('/', File.separatorChar));
1053
1054 if (rf != null) {
1055 Manifest man = rf.getManifest();
1056 if (man != null) {
1057 Attributes attr = man.getMainAttributes();
1058 if (attr != null) {
1059 String value = attr.getValue(Attributes.Name.CLASS_PATH);
1060 if (value != null) {
1061 StringTokenizer st = new StringTokenizer(value);
1062 while (st.hasMoreTokens()) {
1063 String ajar = st.nextToken();
1064 if (!ajar.endsWith("/")) { // it is a jar file
1065 ajar = path.concat(ajar);
1066 /* check on cyclic dependency */
1067 if (! jarPaths.contains(ajar)) {
1068 files.addAll(getJarPath(ajar));
1069 }
1070 }
1071 }
1072 }
1073 }
1074 }
1075 }
1076 rf.close();
1077 return files;
1078 }
1079
1080 /**
1081 * Generates class index file for the specified root jar file.
1082 */
1083 void genIndex(String rootjar, String[] files) throws IOException {
1084 List<String> jars = getJarPath(rootjar);
1085 int njars = jars.size();
1086 String[] jarfiles;
1087
1088 if (njars == 1 && files != null) {
1089 // no class-path attribute defined in rootjar, will
1090 // use command line specified list of jars
1091 for (int i = 0; i < files.length; i++) {
1092 jars.addAll(getJarPath(files[i]));
1093 }
1094 njars = jars.size();
1095 }
1096 jarfiles = jars.toArray(new String[njars]);
1097 JarIndex index = new JarIndex(jarfiles);
1098 dumpIndex(rootjar, index);
1099 }
1100
1101 /**
1102 * Prints entry information, if requested.
1103 */
1104 void printEntry(ZipEntry e, String[] files) throws IOException {
1105 if (files == null) {
1106 printEntry(e);
1107 } else {
1108 String name = e.getName();
1109 for (String file : files) {
1110 if (name.startsWith(file)) {
1111 printEntry(e);
1112 return;
1113 }
1114 }
1115 }
1116 }
1117
1118 /**
1119 * Prints entry information.
1120 */
1121 void printEntry(ZipEntry e) throws IOException {
1122 if (vflag) {
1123 StringBuilder sb = new StringBuilder();
1124 String s = Long.toString(e.getSize());
1125 for (int i = 6 - s.length(); i > 0; --i) {
1126 sb.append(' ');
1127 }
1128 sb.append(s).append(' ').append(new Date(e.getTime()).toString());
1129 sb.append(' ').append(e.getName());
1130 output(sb.toString());
1131 } else {
1132 output(e.getName());
1133 }
1134 }
1135
1136 /**
1137 * Prints usage message.
1138 */
1139 void usageError() {
1140 error(getMsg("usage"));
1141 }
1142
1143 /**
1144 * A fatal exception has been caught. No recovery possible
1145 */
1146 void fatalError(Exception e) {
1147 e.printStackTrace();
1148 }
1149
1150 /**
1151 * A fatal condition has been detected; message is "s".
1152 * No recovery possible
1153 */
1154 void fatalError(String s) {
1155 error(program + ": " + s);
1156 }
1157
1158 /**
1159 * Print an output message; like verbose output and the like
1160 */
1161 protected void output(String s) {
1162 out.println(s);
1163 }
1164
1165 /**
1166 * Print an error mesage; like something is broken
1167 */
1168 protected void error(String s) {
1169 err.println(s);
1170 }
1171
1172 /**
1173 * Main routine to start program.
1174 */
1175 public static void main(String args[]) {
1176 Main jartool = new Main(System.out, System.err, "jar");
1177 System.exit(jartool.run(args) ? 0 : 1);
1178 }
1179
1180 /**
1181 * An OutputStream that doesn't send its output anywhere, (but could).
1182 * It's here to find the CRC32 of an input file, necessary for STORED
1183 * mode in ZIP.
1184 */
1185 private static class CRC32OutputStream extends java.io.OutputStream {
1186 final CRC32 crc = new CRC32();
1187 long n = 0;
1188
1189 CRC32OutputStream() {}
1190
1191 public void write(int r) throws IOException {
1192 crc.update(r);
1193 n++;
1194 }
1195
1196 public void write(byte[] b, int off, int len) throws IOException {
1197 crc.update(b, off, len);
1198 n += len;
1199 }
1200
1201 /**
1202 * Updates a ZipEntry which describes the data read by this
1203 * output stream, in STORED mode.
1204 */
1205 public void updateEntry(ZipEntry e) {
1206 e.setMethod(ZipEntry.STORED);
1207 e.setSize(n);
1208 e.setCrc(crc.getValue());
1209 }
1210 }
1211}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t