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+
26+package sun.tools.jar;
27+
28+import java.io.*;
29+import java.nio.file.Path;
30+import java.nio.file.Files;
31+import java.util.*;
32+import java.util.zip.*;
33+import java.util.jar.*;
34+import java.util.jar.Manifest;
35+import java.text.MessageFormat;
36+import sun.misc.JarIndex;
37+import static sun.misc.JarIndex.INDEX_NAME;
38+import static java.util.jar.JarFile.MANIFEST_NAME;
39+import 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+ */
46+public
47+class 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