src/share/classes/sun/tools/jar/Main.java

Print this page



  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.lang.module.ModuleId;
  29 import java.io.*;
  30 import java.nio.file.Path;
  31 import java.nio.file.Files;

  32 import java.util.*;
  33 import java.util.zip.*;
  34 import java.util.jar.*;
  35 import java.util.jar.Manifest;
  36 import java.text.MessageFormat;
  37 import sun.misc.JarIndex;
  38 import static sun.misc.JarIndex.INDEX_NAME;
  39 import static java.util.jar.JarFile.MANIFEST_NAME;
  40 import static java.util.jar.JarFile.MODULEINFO_NAME;
  41 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
  42 
  43 /**
  44  * This class implements a simple utility for creating files in the JAR
  45  * (Java Archive) file format. The JAR format is based on the ZIP file
  46  * format, with optional meta-information stored in a MANIFEST entry.
  47  */
  48 public
  49 class Main {
  50     String program;
  51     PrintStream out, err;


 589         if (e.getMethod() == ZipEntry.STORED) {
 590             e2.setSize(e.getSize());
 591             e2.setCrc(e.getCrc());
 592         }
 593         zos.putNextEntry(e2);
 594     }
 595 
 596     /**
 597      * Updates an existing jar file.
 598      */
 599     boolean update(InputStream in, OutputStream out,
 600                    InputStream newManifest,
 601                    JarIndex jarIndex) throws IOException
 602     {
 603         ModuleInfo minfo = moduleid != null
 604                                ? new ModuleInfo(moduleid) : null;
 605         try (ZipInputStream zis = new ZipInputStream(in);
 606              ZipOutputStream zos = new JarOutputStream(out)) {
 607             ZipEntry e = null;
 608             Manifest mf = null;

 609 
 610             if (entryMap.containsKey(MODULEINFO_NAME) && moduleid != null) {
 611                 error(getMsg("error.bad.moduleid"));
 612                 return false;
 613             } 
 614 
 615             if (jarIndex != null) {
 616                 addIndex(jarIndex, zos);
 617             }
 618 
 619             // put the old entries first, replace if necessary
 620             while ((e = zis.getNextEntry()) != null) {
 621                 String name = e.getName();
 622 
 623                 boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
 624 







 625                 if (jarIndex != null && equalsIgnoreCase(name, INDEX_NAME)) {
 626                     continue;
 627                 }
 628 
 629                 if (isManifestEntry) {
 630                     mf = new Manifest(zis);
 631                     if (Mflag) {
 632                         continue;
 633                     }
 634 
 635                     if (newManifest != null) {
 636                         // Don't read from the newManifest InputStream, as we
 637                         // might need it below, and we can't re-read the same data
 638                         // twice.
 639                         try (FileInputStream fis = new FileInputStream(mname)) {
 640                             if (isAmbiguousMainClass(new Manifest(fis))) {
 641                                 return false;
 642                             }
 643                         }
 644                         mf.read(newManifest);
 645                     }
 646 
 647                     if (ename != null) {
 648                         addMainClass(mf, ename);
 649                     }
 650 
 651                     // retain the manifest in the same location as
 652                     // the existing manifest entry.  JarInputStream
 653                     // only creates a Manifest if it's the first entry;
 654                     // otherwise, it treats it as an ordinary ZipEntry.
 655                     if (newManifest != null || ename != null) {
 656                         updateManifest(mf, zos);
 657                     } else {
 658                         writeManifest(mf, zos, e.getTime());
 659                     }
 660                     continue;
 661                 }
 662 
 663                 if (!entryMap.containsKey(name)) { // copy the old stuff
 664                     if (!name.equals(MODULEINFO_NAME) || moduleid == null) {
 665                         // add exports in the generated module-info.java
 666                         copyZipEntry(e, zos);









 667                         copy(e, zis, zos, minfo);   // copy the content

 668                         zos.closeEntry();
 669                     }
 670                 } else { // replace with the new files
 671                     File f = entryMap.get(name);
 672                     addFile(zos, f, minfo);
 673                     entryMap.remove(name);
 674                     entries.remove(f);
 675                 }
 676             }
 677 
 678             // add the remaining new files
 679             for (File f : entries) {
 680                 addFile(zos, f, minfo);
 681             }
 682 
 683             if (mf == null) {
 684                 // META-INF/MANIFEST.MF not exist
 685                 if (newManifest != null) {
 686                     mf = new Manifest(newManifest);
 687                     if (isAmbiguousMainClass(mf))
 688                         return false;
 689                 } else if (ename != null) {
 690                     mf = new Manifest();
 691                 }
 692             }
 693 
 694             if (minfo != null) {
 695                 // -I is specified
 696                 minfo.setMainClass(getMainClass(mf));
 697                 addModuleRequires(minfo, getClassPath(mf));









 698                 writeModuleInfo(minfo, zos, System.currentTimeMillis());
 699                 if (vflag) {
 700                     output(getMsg("out.update.moduleinfo"));
 701                 }
 702             }
 703             return true;
 704         }
 705     }
 706 
 707     private void addIndex(JarIndex index, ZipOutputStream zos)
 708         throws IOException
 709     {
 710         ZipEntry e = new ZipEntry(INDEX_NAME);
 711         e.setTime(System.currentTimeMillis());
 712         if (flag0) {
 713             CRC32OutputStream os = new CRC32OutputStream();
 714             index.write(os);
 715             os.updateEntry(e);
 716         }
 717         zos.putNextEntry(e);


 731         }
 732         zos.putNextEntry(e);
 733         m.write(zos);
 734         zos.closeEntry();
 735     }
 736 
 737     private void updateManifest(Manifest m, ZipOutputStream zos)
 738         throws IOException
 739     {
 740         addVersion(m);
 741         addCreatedBy(m);
 742         if (ename != null) {
 743             addMainClass(m, ename);
 744         }
 745         writeManifest(m, zos, System.currentTimeMillis());
 746         if (vflag) {
 747             output(getMsg("out.update.manifest"));
 748         }
 749     }
 750 


















 751     private void writeModuleInfo(ModuleInfo minfo, ZipOutputStream zos, long ts)
 752         throws IOException
 753     {
 754         ZipEntry e = new ZipEntry(MODULEINFO_NAME);
 755         e.setTime(ts);
 756         if (flag0) {
 757             CRC32OutputStream cos = new CRC32OutputStream();
 758             minfo.write(cos);
 759             cos.updateEntry(e);
 760         }
 761         zos.putNextEntry(e);
 762         minfo.write(zos);
 763         zos.closeEntry();
 764     }
 765 
 766     private String entryName(String name) {
 767         name = name.replace(File.separatorChar, '/');
 768         String matchPath = "";
 769         for (String path : paths) {
 770             if (name.startsWith(path)


 921      * Copies all bytes from the input stream to the output stream.
 922      * Does not close or flush either stream.  Also, add exports
 923      * to the given ModuleInfo.
 924      */
 925     void copy(ZipEntry e, InputStream from, OutputStream to,
 926               ModuleInfo minfo) throws IOException {
 927         String path = e.getName();
 928         if (minfo == null || !path.endsWith(".class")) {
 929             copy(from, to);
 930         } else {
 931             // avoid reading the input stream twice
 932             // the ByteStreamHelper saves the bytes in a buffer
 933             // for writing to the output stream and also loaded
 934             // to determine if it's a public class and get its classname.
 935             helper.copyFrom(from);
 936             helper.copyTo(to);
 937             helper.addExports(minfo, e);
 938         }
 939     }
 940     































 941     private ByteStreamHelper helper = new ByteStreamHelper();
 942     class ByteStreamHelper extends ByteArrayOutputStream {
 943         void copyFrom(InputStream from) throws IOException {
 944             reset();
 945             
 946             int n;
 947             while ((n = from.read(copyBuf)) != -1) {
 948                 this.write(copyBuf, 0, n);
 949             }
 950         }
 951         void copyTo(OutputStream to) throws IOException {
 952             to.write(buf);
 953         }
 954         
 955         void addExports(ModuleInfo minfo, ZipEntry e) throws IOException {
 956             minfo.addExports(new ByteArrayInputStream(buf), e.getSize(), e.getName());
 957         }
 958     }
 959     /**
 960      * Copies all bytes from the input file to the output stream.
 961      * Does not close or flush the output stream.
 962      *
 963      * @param from the input file to read from
 964      * @param to the output stream to write to
 965      * @throws IOException if an I/O error occurs
 966      */
 967     private void copy(File from, OutputStream to) throws IOException {
 968         InputStream in = new FileInputStream(from);
 969         try {
 970             copy(in, to);
 971         } finally {
 972             in.close();
 973         }
 974     }
 975 
 976     /**
 977      * Copies all bytes from the input stream to the output file.
 978      * Does not close the input stream.
 979      *
 980      * @param from the input stream to read from
 981      * @param to the output file to write to
 982      * @throws IOException if an I/O error occurs
 983      */
 984     private void copy(InputStream from, File to) throws IOException {
 985         OutputStream out = new FileOutputStream(to);
 986         try {
 987             copy(from, out);
 988         } finally {
 989             out.close();
 990         }
 991     }
 992 
 993     /**
 994      * Computes the crc32 of a File.  This is necessary when the
 995      * ZipOutputStream is in STORED mode.
 996      */
 997     private void crc32File(ZipEntry e, File f) throws IOException {
 998         CRC32OutputStream os = new CRC32OutputStream();
 999         copy(f, os);
1000         if (os.n != f.length()) {
1001             throw new JarException(formatMsg(
1002                         "error.incorrect.length", f.getPath()));
1003         }
1004         os.updateEntry(e);
1005     }
1006 
1007     void replaceFSC(String files[]) {
1008         if (files != null) {
1009             for (String file : files) {



  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.lang.module.ModuleId;
  29 import java.io.*;
  30 import java.nio.file.Path;
  31 import java.nio.file.Files;
  32 import java.nio.charset.Charset;
  33 import java.util.*;
  34 import java.util.zip.*;
  35 import java.util.jar.*;
  36 import java.util.jar.Manifest;
  37 import java.text.MessageFormat;
  38 import sun.misc.JarIndex;
  39 import static sun.misc.JarIndex.INDEX_NAME;
  40 import static java.util.jar.JarFile.MANIFEST_NAME;
  41 import static java.util.jar.JarFile.MODULEINFO_NAME;
  42 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
  43 
  44 /**
  45  * This class implements a simple utility for creating files in the JAR
  46  * (Java Archive) file format. The JAR format is based on the ZIP file
  47  * format, with optional meta-information stored in a MANIFEST entry.
  48  */
  49 public
  50 class Main {
  51     String program;
  52     PrintStream out, err;


 590         if (e.getMethod() == ZipEntry.STORED) {
 591             e2.setSize(e.getSize());
 592             e2.setCrc(e.getCrc());
 593         }
 594         zos.putNextEntry(e2);
 595     }
 596 
 597     /**
 598      * Updates an existing jar file.
 599      */
 600     boolean update(InputStream in, OutputStream out,
 601                    InputStream newManifest,
 602                    JarIndex jarIndex) throws IOException
 603     {
 604         ModuleInfo minfo = moduleid != null
 605                                ? new ModuleInfo(moduleid) : null;
 606         try (ZipInputStream zis = new ZipInputStream(in);
 607              ZipOutputStream zos = new JarOutputStream(out)) {
 608             ZipEntry e = null;
 609             Manifest mf = null;
 610             Map<String,Set<String>> providers = new HashMap<>();
 611 
 612             if (entryMap.containsKey(MODULEINFO_NAME) && moduleid != null) {
 613                 error(getMsg("error.bad.moduleid"));
 614                 return false;
 615             } 
 616 
 617             if (jarIndex != null) {
 618                 addIndex(jarIndex, zos);
 619             }
 620 
 621             // put the old entries first, replace if necessary
 622             while ((e = zis.getNextEntry()) != null) {
 623                 String name = e.getName();
 624 
 625                 boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
 626 
 627                 String service = null;
 628                 if (moduleid != null && !e.isDirectory()
 629                         && name.startsWith("META-INF/services/")) {
 630                     int last = name.lastIndexOf("/");
 631                     service = name.substring(last + 1);
 632                 }
 633 
 634                 if (jarIndex != null && equalsIgnoreCase(name, INDEX_NAME)) {
 635                     continue;
 636                 }
 637 
 638                 if (isManifestEntry) {
 639                     mf = new Manifest(zis);
 640                     if (Mflag) {
 641                         continue;
 642                     }
 643 
 644                     if (newManifest != null) {
 645                         // Don't read from the newManifest InputStream, as we
 646                         // might need it below, and we can't re-read the same data
 647                         // twice.
 648                         try (FileInputStream fis = new FileInputStream(mname)) {
 649                             if (isAmbiguousMainClass(new Manifest(fis))) {
 650                                 return false;
 651                             }
 652                         }
 653                         mf.read(newManifest);
 654                     }
 655 
 656                     if (ename != null) {
 657                         addMainClass(mf, ename);
 658                     }
 659 
 660                     // retain the manifest in the same location as
 661                     // the existing manifest entry.  JarInputStream
 662                     // only creates a Manifest if it's the first entry;
 663                     // otherwise, it treats it as an ordinary ZipEntry.
 664                     if (newManifest != null || ename != null) {
 665                         updateManifest(mf, zos);
 666                     } else {
 667                         writeManifest(mf, zos, e.getTime());
 668                     }
 669                     continue;
 670                 }
 671 
 672                 if (!entryMap.containsKey(name)) { // copy the old stuff
 673                     if (!name.equals(MODULEINFO_NAME) || moduleid == null) {

 674                         copyZipEntry(e, zos);
 675 
 676                         // when generating a module-info file and this entry is 
 677                         // a service configuration file then parse it to get the
 678                         // names of the service implementations.
 679                         if (service != null) {
 680                             byte[] bytes = readAllBytes(zis);
 681                             providers.put(service, parseServicesEntry(bytes));
 682                             zos.write(bytes);   // copy the content
 683                         } else {
 684                             copy(e, zis, zos, minfo);   // copy the content
 685                         }     
 686                         zos.closeEntry();
 687                     }
 688                 } else { // replace with the new files
 689                     File f = entryMap.get(name);
 690                     addFile(zos, f, minfo);
 691                     entryMap.remove(name);
 692                     entries.remove(f);
 693                 }
 694             }
 695 
 696             // add the remaining new files
 697             for (File f : entries) {
 698                 addFile(zos, f, minfo);
 699             }
 700 
 701             if (mf == null) {
 702                 // META-INF/MANIFEST.MF not exist
 703                 if (newManifest != null) {
 704                     mf = new Manifest(newManifest);
 705                     if (isAmbiguousMainClass(mf))
 706                         return false;
 707                 } else if (ename != null) {
 708                     mf = new Manifest();
 709                 }
 710             }
 711 
 712             if (minfo != null) {
 713                 // -I is specified
 714                 minfo.setMainClass(getMainClass(mf));
 715                 addModuleRequires(minfo, getClassPath(mf));
 716                 
 717                 // service providers
 718                 for (Map.Entry<String,Set<String>> entry: providers.entrySet()) {
 719                     String service = entry.getKey();
 720                     for (String impl: entry.getValue()) {
 721                         minfo.addProvidesService(service, impl);
 722                     }                    
 723                 }
 724                 
 725                 writeModuleInfo(minfo, zos, System.currentTimeMillis());
 726                 if (vflag) {
 727                     output(getMsg("out.update.moduleinfo"));
 728                 }
 729             }
 730             return true;
 731         }
 732     }
 733 
 734     private void addIndex(JarIndex index, ZipOutputStream zos)
 735         throws IOException
 736     {
 737         ZipEntry e = new ZipEntry(INDEX_NAME);
 738         e.setTime(System.currentTimeMillis());
 739         if (flag0) {
 740             CRC32OutputStream os = new CRC32OutputStream();
 741             index.write(os);
 742             os.updateEntry(e);
 743         }
 744         zos.putNextEntry(e);


 758         }
 759         zos.putNextEntry(e);
 760         m.write(zos);
 761         zos.closeEntry();
 762     }
 763 
 764     private void updateManifest(Manifest m, ZipOutputStream zos)
 765         throws IOException
 766     {
 767         addVersion(m);
 768         addCreatedBy(m);
 769         if (ename != null) {
 770             addMainClass(m, ename);
 771         }
 772         writeManifest(m, zos, System.currentTimeMillis());
 773         if (vflag) {
 774             output(getMsg("out.update.manifest"));
 775         }
 776     }
 777     
 778     /**
 779      * Parse the given byte array as the raw bytes of a services configuration
 780      * file, returning the names of the entries.
 781      */
 782     private Set<String> parseServicesEntry(byte[] bytes) throws IOException {
 783         ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
 784         BufferedReader reader =
 785             new BufferedReader(new InputStreamReader(bis, Charset.forName("UTF-8")));
 786         Set<String> result = new HashSet<>();
 787         String s;
 788         while ((s = reader.readLine()) != null) {
 789             s = s.trim();
 790             if (s.length() > 0 && !s.startsWith("#"))
 791                 result.add(s);
 792         }
 793         return result;
 794     }    
 795 
 796     private void writeModuleInfo(ModuleInfo minfo, ZipOutputStream zos, long ts)
 797         throws IOException
 798     {
 799         ZipEntry e = new ZipEntry(MODULEINFO_NAME);
 800         e.setTime(ts);
 801         if (flag0) {
 802             CRC32OutputStream cos = new CRC32OutputStream();
 803             minfo.write(cos);
 804             cos.updateEntry(e);
 805         }
 806         zos.putNextEntry(e);
 807         minfo.write(zos);
 808         zos.closeEntry();
 809     }
 810 
 811     private String entryName(String name) {
 812         name = name.replace(File.separatorChar, '/');
 813         String matchPath = "";
 814         for (String path : paths) {
 815             if (name.startsWith(path)


 966      * Copies all bytes from the input stream to the output stream.
 967      * Does not close or flush either stream.  Also, add exports
 968      * to the given ModuleInfo.
 969      */
 970     void copy(ZipEntry e, InputStream from, OutputStream to,
 971               ModuleInfo minfo) throws IOException {
 972         String path = e.getName();
 973         if (minfo == null || !path.endsWith(".class")) {
 974             copy(from, to);
 975         } else {
 976             // avoid reading the input stream twice
 977             // the ByteStreamHelper saves the bytes in a buffer
 978             // for writing to the output stream and also loaded
 979             // to determine if it's a public class and get its classname.
 980             helper.copyFrom(from);
 981             helper.copyTo(to);
 982             helper.addExports(minfo, e);
 983         }
 984     }
 985     
 986     /**
 987      * Read all bytes from the input stream, returning them in a byte array.
 988      */
 989     private byte[] readAllBytes(InputStream from) throws IOException {
 990         int capacity = 8192;
 991         byte[] buf = new byte[capacity];
 992         int nread = 0;
 993         int rem = buf.length;
 994         int n;
 995         // read to EOF which may read more or less than initialSize (eg: file
 996         // is truncated while we are reading)
 997         while ((n = from.read(buf, nread, rem)) > 0) {
 998             nread += n;
 999             rem -= n;
1000             assert rem >= 0;
1001             if (rem == 0) {
1002                 // need larger buffer
1003                 int newCapacity = capacity << 1;
1004                 if (newCapacity < 0) {
1005                     if (capacity == Integer.MAX_VALUE)
1006                         throw new OutOfMemoryError("Required array size too large");
1007                     newCapacity = Integer.MAX_VALUE;
1008                 }
1009                 rem = newCapacity - capacity;
1010                 buf = Arrays.copyOf(buf, newCapacity);
1011                 capacity = newCapacity;
1012             }
1013         }
1014         return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
1015     }    
1016     
1017     private ByteStreamHelper helper = new ByteStreamHelper();
1018     class ByteStreamHelper extends ByteArrayOutputStream {
1019         void copyFrom(InputStream from) throws IOException {
1020             reset();
1021             
1022             int n;
1023             while ((n = from.read(copyBuf)) != -1) {
1024                 this.write(copyBuf, 0, n);
1025             }
1026         }
1027         void copyTo(OutputStream to) throws IOException {
1028             to.write(buf);
1029         }
1030         
1031         void addExports(ModuleInfo minfo, ZipEntry e) throws IOException {
1032             minfo.addExports(new ByteArrayInputStream(buf), e.getSize(), e.getName());
1033         }
1034     }
1035     /**
1036      * Copies all bytes from the input file to the output stream.
1037      * Does not close or flush the output stream.
1038      *
1039      * @param from the input file to read from
1040      * @param to the output stream to write to
1041      * @throws IOException if an I/O error occurs
1042      */
1043     private void copy(File from, OutputStream to) throws IOException {
1044         try (InputStream in = new FileInputStream(from)) {

1045             copy(in, to);


1046         }
1047     }
1048 
1049     /**
1050      * Copies all bytes from the input stream to the output file.
1051      * Does not close the input stream.
1052      *
1053      * @param from the input stream to read from
1054      * @param to the output file to write to
1055      * @throws IOException if an I/O error occurs
1056      */
1057     private void copy(InputStream from, File to) throws IOException {
1058         try (OutputStream out = new FileOutputStream(to)) {

1059             copy(from, out);


1060         }
1061     }
1062 
1063     /**
1064      * Computes the crc32 of a File.  This is necessary when the
1065      * ZipOutputStream is in STORED mode.
1066      */
1067     private void crc32File(ZipEntry e, File f) throws IOException {
1068         CRC32OutputStream os = new CRC32OutputStream();
1069         copy(f, os);
1070         if (os.n != f.length()) {
1071             throw new JarException(formatMsg(
1072                         "error.incorrect.length", f.getPath()));
1073         }
1074         os.updateEntry(e);
1075     }
1076 
1077     void replaceFSC(String files[]) {
1078         if (files != null) {
1079             for (String file : files) {