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
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) {
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;
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++) {
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 {
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;
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();
|
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, pname;
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
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 if (pname != null) {
188 addProfileName(manifest, pname);
189 }
190 }
191 OutputStream out;
192 if (fname != null) {
193 out = new FileOutputStream(fname);
194 } else {
195 out = new FileOutputStream(FileDescriptor.out);
196 if (vflag) {
197 // Disable verbose output so that it does not appear
198 // on stdout along with file data
199 // error("Warning: -v option ignored");
200 vflag = false;
201 }
202 }
203 expand(null, files, false);
204 create(new BufferedOutputStream(out, 4096), manifest);
205 if (in != null) {
206 in.close();
207 }
208 out.close();
209 } else if (uflag) {
347 fname = args[count++];
348 break;
349 case 'm':
350 mname = args[count++];
351 break;
352 case '0':
353 flag0 = true;
354 break;
355 case 'i':
356 if (cflag || uflag || xflag || tflag) {
357 usageError();
358 return false;
359 }
360 // do not increase the counter, files will contain rootjar
361 rootjar = args[count++];
362 iflag = true;
363 break;
364 case 'e':
365 ename = args[count++];
366 break;
367 case 'p':
368 pname = args[count++];
369 break;
370 default:
371 error(formatMsg("error.illegal.option",
372 String.valueOf(flags.charAt(i))));
373 usageError();
374 return false;
375 }
376 }
377 } catch (ArrayIndexOutOfBoundsException e) {
378 usageError();
379 return false;
380 }
381 if (!cflag && !tflag && !xflag && !uflag && !iflag) {
382 error(getMsg("error.bad.option"));
383 usageError();
384 return false;
385 }
386 /* parse file arguments */
387 int n = args.length - count;
388 if (n > 0) {
389 int k = 0;
399 while (dir.indexOf("//") > -1) {
400 dir = dir.replace("//", "/");
401 }
402 paths.add(dir.replace(File.separatorChar, '/'));
403 nameBuf[k++] = dir + args[++i];
404 } else {
405 nameBuf[k++] = args[i];
406 }
407 }
408 } catch (ArrayIndexOutOfBoundsException e) {
409 usageError();
410 return false;
411 }
412 files = new String[k];
413 System.arraycopy(nameBuf, 0, files, 0, k);
414 } else if (cflag && (mname == null)) {
415 error(getMsg("error.bad.cflag"));
416 usageError();
417 return false;
418 } else if (uflag) {
419 if ((mname != null) || (ename != null) || (pname != null)) {
420 /* just want to update the manifest */
421 return true;
422 } else {
423 error(getMsg("error.bad.uflag"));
424 usageError();
425 return false;
426 }
427 }
428 return true;
429 }
430
431 /**
432 * Expands list of files to process into full list of all files that
433 * can be found by recursively descending directories.
434 */
435 void expand(File dir, String[] files, boolean isUpdate) {
436 if (files == null) {
437 return;
438 }
439 for (int i = 0; i < files.length; i++) {
533 ZipInputStream zis = new ZipInputStream(in);
534 ZipOutputStream zos = new JarOutputStream(out);
535 ZipEntry e = null;
536 boolean foundManifest = false;
537 boolean updateOk = true;
538
539 if (jarIndex != null) {
540 addIndex(jarIndex, zos);
541 }
542
543 // put the old entries first, replace if necessary
544 while ((e = zis.getNextEntry()) != null) {
545 String name = e.getName();
546
547 boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
548
549 if ((jarIndex != null && equalsIgnoreCase(name, INDEX_NAME))
550 || (Mflag && isManifestEntry)) {
551 continue;
552 } else if (isManifestEntry && ((newManifest != null) ||
553 (ename != null) || (pname != null))) {
554 foundManifest = true;
555 if (newManifest != null) {
556 // Don't read from the newManifest InputStream, as we
557 // might need it below, and we can't re-read the same data
558 // twice.
559 FileInputStream fis = new FileInputStream(mname);
560 boolean ambiguous = isAmbiguousMainClass(new Manifest(fis));
561 fis.close();
562 if (ambiguous) {
563 return false;
564 }
565 }
566
567 // Update the manifest.
568 Manifest old = new Manifest(zis);
569 if (newManifest != null) {
570 old.read(newManifest);
571 }
572 updateManifest(old, zos);
573 } else {
587 } else { // replace with the new files
588 File f = entryMap.get(name);
589 addFile(zos, f);
590 entryMap.remove(name);
591 entries.remove(f);
592 }
593 }
594 }
595
596 // add the remaining new files
597 for (File f: entries) {
598 addFile(zos, f);
599 }
600 if (!foundManifest) {
601 if (newManifest != null) {
602 Manifest m = new Manifest(newManifest);
603 updateOk = !isAmbiguousMainClass(m);
604 if (updateOk) {
605 updateManifest(m, zos);
606 }
607 } else if (ename != null || pname != null) {
608 updateManifest(new Manifest(), zos);
609 }
610 }
611 zis.close();
612 zos.close();
613 return updateOk;
614 }
615
616
617 private void addIndex(JarIndex index, ZipOutputStream zos)
618 throws IOException
619 {
620 ZipEntry e = new ZipEntry(INDEX_NAME);
621 e.setTime(System.currentTimeMillis());
622 if (flag0) {
623 CRC32OutputStream os = new CRC32OutputStream();
624 index.write(os);
625 os.updateEntry(e);
626 }
627 zos.putNextEntry(e);
628 index.write(zos);
629 zos.closeEntry();
630 }
631
632 private void updateManifest(Manifest m, ZipOutputStream zos)
633 throws IOException
634 {
635 addVersion(m);
636 addCreatedBy(m);
637 if (ename != null) {
638 addMainClass(m, ename);
639 }
640 if (pname != null) {
641 addProfileName(m, pname);
642 }
643 ZipEntry e = new ZipEntry(MANIFEST_NAME);
644 e.setTime(System.currentTimeMillis());
645 if (flag0) {
646 crc32Manifest(e, m);
647 }
648 zos.putNextEntry(e);
649 m.write(zos);
650 if (vflag) {
651 output(getMsg("out.update.manifest"));
652 }
653 }
654
655
656 private String entryName(String name) {
657 name = name.replace(File.separatorChar, '/');
658 String matchPath = "";
659 for (String path : paths) {
660 if (name.startsWith(path)
661 && (path.length() > matchPath.length())) {
662 matchPath = path;
677 if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
678 global.put(Attributes.Name.MANIFEST_VERSION, VERSION);
679 }
680 }
681
682 private void addCreatedBy(Manifest m) {
683 Attributes global = m.getMainAttributes();
684 if (global.getValue(new Attributes.Name("Created-By")) == null) {
685 String javaVendor = System.getProperty("java.vendor");
686 String jdkVersion = System.getProperty("java.version");
687 global.put(new Attributes.Name("Created-By"), jdkVersion + " (" +
688 javaVendor + ")");
689 }
690 }
691
692 private void addMainClass(Manifest m, String mainApp) {
693 Attributes global = m.getMainAttributes();
694
695 // overrides any existing Main-Class attribute
696 global.put(Attributes.Name.MAIN_CLASS, mainApp);
697 }
698
699 private void addProfileName(Manifest m, String profile) {
700 Attributes global = m.getMainAttributes();
701
702 // overrides any existing Profile attribute
703 global.put(Attributes.Name.PROFILE, profile);
704 }
705
706 private boolean isAmbiguousMainClass(Manifest m) {
707 if (ename != null) {
708 Attributes global = m.getMainAttributes();
709 if ((global.get(Attributes.Name.MAIN_CLASS) != null)) {
710 error(getMsg("error.bad.eflag"));
711 usageError();
712 return true;
713 }
714 }
715 return false;
716 }
717
718 /**
719 * Adds a new file entry to the ZIP output stream.
720 */
721 void addFile(ZipOutputStream zos, File file) throws IOException {
722 String name = file.getPath();
723 boolean isDir = file.isDirectory();
|