src/share/classes/org/openjdk/jigsaw/ModuleFileWriter.java

Print this page




  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 org.openjdk.jigsaw;
  27 
  28 import java.nio.file.attribute.BasicFileAttributes;
  29 import java.io.*;
  30 import java.nio.ByteBuffer;
  31 import java.nio.channels.*;
  32 import java.nio.file.*;
  33 import java.nio.file.Files;
  34 import java.security.MessageDigest;
  35 import java.util.*;
  36 import java.util.jar.*;
  37 import java.util.zip.*;

  38 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
  39 import static org.openjdk.jigsaw.ModuleFile.*;
  40 
  41 
  42 /**
  43  * <p> A writer of module files </p>
  44  */
  45 
  46 public class ModuleFileWriter {
  47 
  48     private final File outfile;
  49     private final HashType hashtype;
  50     private final boolean fastestCompression;
  51     private long usize;

  52 
  53     public ModuleFileWriter(File outfile) {
  54         this(outfile, false);
  55     }
  56 
  57     public ModuleFileWriter(File outfile, boolean useFastestCompression) {
  58         this.outfile = outfile;
  59         this.hashtype = HashType.SHA256;
  60         this.fastestCompression = useFastestCompression;
  61     }
  62 
  63     /**
  64      * Generates an unsigned module file.
  65      */
  66     public void writeModule(File mdir,
  67                             File nativelibs,
  68                             File nativecmds,
  69                             File config)
  70             throws IOException
  71     {
  72         if (!mdir.isDirectory()) {
  73             throw new IOException("Not a directory: " + mdir);
  74         }
  75 
  76         try (RandomAccessFile file = new RandomAccessFile(outfile, "rw")) {
  77             // Truncate the file if it already exists
  78             file.setLength(0);
  79 







  80             // Reset module file to right after module header
  81             file.seek(ModuleFileHeader.LENGTH);
  82 
  83             // TODO: Why was this after the module info???
  84             long remainderStart = file.getFilePointer();
  85 
  86             // Write out the Module-Info Section
  87             File miclass = new File(mdir, "module-info.class");
  88             if (!miclass.exists()) {
  89                 throw new IOException(miclass + " does not exist");
  90             }
  91             writeSection(file,
  92                          SectionType.MODULE_INFO,
  93                          mdir,
  94                          Collections.singletonList(miclass.toPath()),
  95                          Compressor.NONE);
  96 
  97             // Write out the optional file sections
  98             writeOptionalSections(file, mdir, nativelibs, nativecmds, config);
  99 
 100             // Write out the module file header
 101             writeModuleFileHeader(file, remainderStart);
 102         }
 103     }
 104 
 105     /*
 106      * Write a section to the given module file.
 107      *
 108      * @params file RandomAccessFile for the resulting jmod file
 109      * @params type section type
 110      * @params sourcedir the directory containing the files to be written
 111      * @params files list of files to be written
 112      * @params compressor compression type
 113      */
 114     private void writeSection(RandomAccessFile file,
 115                               SectionType type,
 116                               File sourcedir,
 117                               List<Path> files,
 118                               Compressor compressor) throws IOException {


 119         // Start of section header
 120         final long start = file.getFilePointer();
 121 
 122         // Start of section content
 123         final long cstart = start + SectionHeader.LENGTH;
 124         // Seek to start of section content
 125         file.seek(cstart);
 126 
 127         writeSectionContent(file, type, sourcedir, files, compressor);
 128 
 129         // End of section
 130         final long end = file.getFilePointer();
 131         final int csize = (int) (end - cstart);
 132 
 133         // A section type that has no files has a section count of 0.
 134         int count = type.hasFiles() ? files.size() : 0;
 135         if (count > Short.MAX_VALUE) {
 136             throw new IOException("Too many files: " + count);
 137         }
 138         writeSectionHeader(file, type, compressor, start, csize, (short) count);
 139     }
 140 
 141     private void writeSectionContent(RandomAccessFile file,
 142                                      SectionType type,
 143                                      File sourcedir,
 144                                      List<Path> files,
 145                                      Compressor compressor) throws IOException {
 146 

 147         if (type.hasFiles()) {
 148             for (Path p : files) {
 149                 writeSubSection(file, sourcedir, p, compressor);
 150             }
 151         } else if (type == SectionType.CLASSES) {
 152             writeClassesContent(file, sourcedir, files, compressor);
 153         } else if (files.size() == 1) {
 154             writeFileContent(file, files.get(0), compressor);
 155         } else {
 156             throw new IllegalArgumentException("Section type: " + type
 157                     + " can only have one single file but given " + files);
 158         }
 159     }
 160 
 161     private void writeSectionHeader(RandomAccessFile file,
 162                                     SectionType type,
 163                                     Compressor compressor,
 164                                     long start, int csize,
 165                                     short subsections) throws IOException {
 166 

 167         // Compute hash of content
 168         MessageDigest md = getHashInstance(hashtype);
 169         FileChannel channel = file.getChannel();
 170         ByteBuffer content = ByteBuffer.allocate(csize);
 171 
 172         final long cstart = start + SectionHeader.LENGTH;
 173         int n = channel.read(content, cstart);
 174         if (n != csize) {
 175             throw new IOException("too few bytes read");
 176         }
 177         content.position(0);
 178         md.update(content);
 179         final byte[] hash = md.digest();
 180 
 181         // Write section header at section header start,
 182         // and seek to end of section.
 183         SectionHeader header =
 184                 new SectionHeader(type, compressor, csize, subsections, hash);
 185         file.seek(start);
 186         header.write(file);
 187         file.seek(cstart + csize);
 188     }
 189 
 190     private void writeClassesContent(DataOutput out,
 191                                      File sourcedir,
 192                                      List<Path> files,
 193                                      Compressor compressor) throws IOException {


 194         CompressedClassOutputStream cos =
 195             CompressedClassOutputStream.newInstance(sourcedir.toPath(),
 196                                                     files,
 197                                                     compressor,
 198                                                     fastestCompression);
 199         usize += cos.getUSize();
 200         cos.writeTo(out);
 201     }
 202 
 203     private void writeFileContent(DataOutput out,
 204                                   Path p,
 205                                   Compressor compressor) throws IOException {


 206         CompressedOutputStream cos = CompressedOutputStream.newInstance(p, compressor);
 207         usize += cos.getUSize();
 208         cos.writeTo(out);
 209     }
 210 
 211     /*
 212      * Write a subsection to the given module file.
 213      *
 214      * @params file RandomAccessFile for the resulting jmod file
 215      * @params sourcedir the directory containing the file to be written
 216      * @params p Path of a file to be written
 217      * @params compressor compression type
 218      */
 219     private void writeSubSection(RandomAccessFile file,
 220                                  File sourcedir,
 221                                  Path p,
 222                                  Compressor compressor) throws IOException {


 223         CompressedOutputStream cos = CompressedOutputStream.newInstance(p, compressor);
 224         usize += cos.getUSize();
 225 
 226         String storedpath = relativePath(sourcedir.toPath(), p);
 227         SubSectionFileHeader header = new SubSectionFileHeader((int)cos.getCSize(), storedpath);
 228         header.write(file);
 229         cos.writeTo(file);
 230     }
 231 
 232 
 233     /*
 234      * Processes each of the optional file sections.
 235      */
 236     private void writeOptionalSections(RandomAccessFile file,
 237                                        File mdir,
 238                                        File nativelibs,
 239                                        File nativecmds,
 240                                        File config)
 241             throws IOException
 242    {


 269         if (nativecmds != null && directoryIsNotEmpty(nativecmds)) {
 270             writeSection(file,
 271                          SectionType.NATIVE_CMDS,
 272                          nativecmds,
 273                          listFiles(nativecmds.toPath()),
 274                          Compressor.GZIP);
 275         }
 276         if (config != null && directoryIsNotEmpty(config)) {
 277             writeSection(file,
 278                          SectionType.CONFIG,
 279                          config,
 280                          listFiles(config.toPath()),
 281                          Compressor.GZIP);
 282         }
 283     }
 284 
 285     /*
 286      * Writes out the module file header.
 287      */
 288     private void writeModuleFileHeader(RandomAccessFile file,
 289                                        long remainderStart)

 290             throws IOException
 291     {

 292 
 293         long csize = file.length() - remainderStart;
 294 
 295         // Header Step 1
 296         // Write out the module file header (using a dummy file hash value)
 297         ModuleFileHeader header =
 298                 new ModuleFileHeader(csize, usize, hashtype,
 299                                      new byte[hashtype.length()]);






 300         file.seek(0);
 301         header.write(file);
 302 
 303         // Generate the module file hash
 304         byte[] fileHash = generateFileHash(file);







 305 
 306         // Header Step 2
 307         // Write out the module file header (using correct file hash value)
 308         header = new ModuleFileHeader(csize, usize, hashtype, fileHash);
 309         file.seek(0);
 310         header.write(file);


 311     }
 312 
 313     private void listClassesResources(Path dir,
 314                                       final List<Path> classes,
 315                                       final List<Path> resources)
 316             throws IOException
 317     {
 318 
 319         Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
 320             @Override
 321             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
 322                     throws IOException {
 323                 if (!file.endsWith("module-info.class")) {
 324                     if (file.toFile().getName().endsWith(".class")) {
 325                         classes.add(file);
 326                     } else {
 327                         resources.add(file);
 328                     }
 329                 }
 330                 return FileVisitResult.CONTINUE;
 331             }
 332         });
 333     }
 334 
 335     // CompressedOutputStream and CompressedClassOutputStream are
 336     // subclass of ByteArrayOutputStream to avoid the array copy
 337     // from the compressed bytes and write buf directly to DataOutput
 338     static class CompressedOutputStream extends ByteArrayOutputStream {


 410                     pos.compress(sourcepath, classes);
 411                     return pos;
 412                 default:
 413                     throw new IllegalArgumentException("Unsupported type: " + type);
 414             }
 415         }
 416 
 417         static class Pack200GZipOutputStream extends CompressedClassOutputStream {
 418             final Pack200.Packer packer = Pack200.newPacker();
 419             Pack200GZipOutputStream(boolean fastestCompression) {
 420                 Map<String, String> p = packer.properties();
 421                 p.put(Pack200.Packer.SEGMENT_LIMIT, "-1");
 422                 if (fastestCompression) {
 423                     p.put(Pack200.Packer.EFFORT, "1");
 424                 }
 425                 p.put(Pack200.Packer.KEEP_FILE_ORDER, Pack200.Packer.FALSE);
 426                 p.put(Pack200.Packer.MODIFICATION_TIME, Pack200.Packer.LATEST);
 427                 p.put(Pack200.Packer.DEFLATE_HINT, Pack200.Packer.FALSE);
 428             }
 429 
 430             void compress(Path sourcepath, List<Path> classes) throws IOException {


 431                 ByteArrayOutputStream os = new ByteArrayOutputStream();
 432                 try (JarOutputStream jos = new JarOutputStream(os)) {
 433                     jos.setLevel(0);
 434                     for (Path file : classes) {
 435                         // write to the JarInputStream for later pack200 compression
 436                         String name = file.toFile().getName().toLowerCase();
 437                         String p = sourcepath.relativize(file).toString();
 438                         if (!p.equals("module-info.class")) {
 439                             JarEntry entry = new JarEntry(p);
 440                             jos.putNextEntry(entry);
 441                             Files.copy(file, jos);
 442                             jos.closeEntry();
 443                         }
 444                         size += file.toFile().length();
 445                     }
 446                 }
 447 
 448                 byte[] data = os.toByteArray();
 449                 try (JarInputStream jin =
 450                         new JarInputStream(new ByteArrayInputStream(data));
 451                      GZIPOutputStream gout = new GZIPOutputStream(this)) {
 452                     packer.pack(jin, gout);
 453                 }
 454             }
 455         }
 456     }
 457 
 458     private String relativePath(Path sourcepath, Path path) throws IOException {


 459         Path relativepath = sourcepath.relativize(path);
 460 
 461         // The '/' character separates nested directories in path names.
 462         String pathseparator = relativepath.getFileSystem().getSeparator();
 463         String stored = relativepath.toString().replace(pathseparator, "/");
 464 
 465         // The path names of native-code files
 466         // must not include more than one element.
 467         // ## Temporarily turn off this check until the location of
 468         // ## the native libraries in jdk modules are changed
 469         // ensureShortNativePath(realpath, stored);
 470         return stored;
 471     }
 472 
 473     private List<Path> listFiles(Path path) throws IOException {


 474         final List<Path> list = new ArrayList<>();
 475         Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
 476 
 477             @Override
 478             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
 479                     throws IOException {
 480                 list.add(file);
 481                 return FileVisitResult.CONTINUE;
 482             }
 483         });
 484         return list;
 485     }
 486 
 487     /*
 488      * Generates the hash value for a module file.
 489      * Excludes itself (the hash bytes in the module file header).
 490      */
 491     private byte[] generateFileHash(RandomAccessFile file)
 492             throws IOException
 493     {
 494         MessageDigest md = getHashInstance(hashtype);
 495 
 496         long remainderSize = file.length() - ModuleFileHeader.LENGTH;
 497         FileChannel channel = file.getChannel();
 498 
 499         // Module file header without the hash bytes
 500         ByteBuffer content = ByteBuffer.allocate(ModuleFileHeader.LENGTH_WITHOUT_HASH);
 501         int n = channel.read(content, 0);
 502         if (n != ModuleFileHeader.LENGTH_WITHOUT_HASH) {
 503             throw new IOException("too few bytes read");
 504         }
 505         content.position(0);
 506         md.update(content);
 507 
 508         // Remainder of file (read in chunks)
 509         content = ByteBuffer.allocate(8192);
 510         channel.position(ModuleFileHeader.LENGTH);
 511         n = channel.read(content);
 512         while (n != -1) {
 513             content.limit(n);
 514             content.position(0);
 515             md.update(content);
 516             content = ByteBuffer.allocate(8192);
 517             n = channel.read(content);
 518         }
 519 
 520         return md.digest();
 521     }
 522 
 523     /*
 524      * Check if a given directory is not empty.
 525      */
 526     private static boolean directoryIsNotEmpty(File dir)
 527             throws IOException {
 528         try (DirectoryStream<Path> ds =
 529                         Files.newDirectoryStream(dir.toPath())) {
 530             return ds.iterator().hasNext();
 531         }
 532     }
 533 
 534 }


  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 org.openjdk.jigsaw;
  27 
  28 import java.nio.file.attribute.BasicFileAttributes;
  29 import java.io.*;
  30 import java.nio.ByteBuffer;
  31 import java.nio.channels.*;
  32 import java.nio.file.*;
  33 import java.nio.file.Files;
  34 import java.security.MessageDigest;
  35 import java.util.*;
  36 import java.util.jar.*;
  37 import java.util.zip.*;
  38 import org.openjdk.jigsaw.ModuleFileParser.Event;
  39 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
  40 import static org.openjdk.jigsaw.ModuleFile.*;
  41 
  42 
  43 /**
  44  * <p> A writer of module files </p>
  45  */
  46 
  47 public class ModuleFileWriter {
  48 
  49     private final File outfile;
  50     private final HashType hashtype;
  51     private final boolean fastestCompression;
  52     private long usize;
  53     private int headerLength;
  54 
  55     public ModuleFileWriter(File outfile) {
  56         this(outfile, false);
  57     }
  58 
  59     public ModuleFileWriter(File outfile, boolean useFastestCompression) {
  60         this.outfile = outfile;
  61         this.hashtype = HashType.SHA256;
  62         this.fastestCompression = useFastestCompression;
  63     }
  64 
  65     /**
  66      * Generates an unsigned module file.
  67      */
  68     public void writeModule(File mdir, File nativelibs, File nativecmds,
  69                             File config, ModuleArchitecture modArch)


  70         throws IOException
  71     {
  72         if (!mdir.isDirectory()) {
  73             throw new IOException("Not a directory: " + mdir);
  74         }
  75 
  76         try (RandomAccessFile file = new RandomAccessFile(outfile, "rw")) {
  77             // Truncate the file if it already exists
  78             file.setLength(0);
  79 
  80             // set appropriate fields in the header before determining the length
  81             ModuleFileHeader dummyHeader = ModuleFileHeader.newBuilder()
  82                                            .setArchitecture(modArch)
  83                                            .setHashType(hashtype)
  84                                            .setHash(new byte[hashtype.length()])
  85                                            .build();
  86 
  87             // Reset module file to right after module header
  88             file.seek(headerLength = dummyHeader.getLength());
  89 



  90             // Write out the Module-Info Section
  91             File miclass = new File(mdir, "module-info.class");
  92             if (!miclass.exists())
  93                 throw new IOException(miclass + " does not exist");
  94 
  95             writeSection(file,
  96                          SectionType.MODULE_INFO,
  97                          mdir,
  98                          Collections.singletonList(miclass.toPath()),
  99                          Compressor.NONE);
 100 
 101             // Write out the optional file sections
 102             writeOptionalSections(file, mdir, nativelibs, nativecmds, config);
 103 
 104             // Write out the module file header
 105             writeModuleFileHeader(file, headerLength, modArch);
 106         }
 107     }
 108 
 109     /*
 110      * Write a section to the given module file.
 111      *
 112      * @params file RandomAccessFile for the resulting jmod file
 113      * @params type section type
 114      * @params sourcedir the directory containing the files to be written
 115      * @params files list of files to be written
 116      * @params compressor compression type
 117      */
 118     private void writeSection(RandomAccessFile file,
 119                               SectionType type,
 120                               File sourcedir,
 121                               List<Path> files,
 122                               Compressor compressor)
 123         throws IOException
 124     {
 125         // Start of section header
 126         final long start = file.getFilePointer();
 127 
 128         // Start of section content
 129         final long cstart = start + SectionHeader.LENGTH;
 130         // Seek to start of section content
 131         file.seek(cstart);
 132 
 133         writeSectionContent(file, type, sourcedir, files, compressor);
 134 
 135         // End of section
 136         final long end = file.getFilePointer();
 137         final int csize = (int) (end - cstart);
 138 
 139         // A section type that has no files has a section count of 0.
 140         int count = type.hasFiles() ? files.size() : 0;
 141         if (count > Short.MAX_VALUE) {
 142             throw new IOException("Too many files: " + count);
 143         }
 144         writeSectionHeader(file, type, compressor, start, csize, (short) count);
 145     }
 146 
 147     private void writeSectionContent(RandomAccessFile file,
 148                                      SectionType type,
 149                                      File sourcedir,
 150                                      List<Path> files,
 151                                      Compressor compressor)
 152         throws IOException
 153     {
 154         if (type.hasFiles()) {
 155             for (Path p : files) {
 156                 writeSubSection(file, sourcedir, p, compressor);
 157             }
 158         } else if (type == SectionType.CLASSES) {
 159             writeClassesContent(file, sourcedir, files, compressor);
 160         } else if (files.size() == 1) {
 161             writeFileContent(file, files.get(0), compressor);
 162         } else {
 163             throw new IllegalArgumentException("Section type: " + type
 164                     + " can only have one single file but given " + files);
 165         }
 166     }
 167 
 168     private void writeSectionHeader(RandomAccessFile file,
 169                                     SectionType type,
 170                                     Compressor compressor,
 171                                     long start, int csize,
 172                                     short subsections)
 173         throws IOException
 174     {
 175         // Compute hash of content
 176         MessageDigest md = getHashInstance(hashtype);
 177         FileChannel channel = file.getChannel();
 178         ByteBuffer content = ByteBuffer.allocate(csize);
 179 
 180         final long cstart = start + SectionHeader.LENGTH;
 181         int n = channel.read(content, cstart);
 182         if (n != csize) {
 183             throw new IOException("too few bytes read");
 184         }
 185         content.position(0);
 186         md.update(content);
 187         final byte[] hash = md.digest();
 188 
 189         // Write section header at section header start,
 190         // and seek to end of section.
 191         SectionHeader header =
 192                 new SectionHeader(type, compressor, csize, subsections, hash);
 193         file.seek(start);
 194         header.write(file);
 195         file.seek(cstart + csize);
 196     }
 197 
 198     private void writeClassesContent(DataOutput out,
 199                                      File sourcedir,
 200                                      List<Path> files,
 201                                      Compressor compressor)
 202         throws IOException
 203     {
 204         CompressedClassOutputStream cos =
 205             CompressedClassOutputStream.newInstance(sourcedir.toPath(),
 206                                                     files,
 207                                                     compressor,
 208                                                     fastestCompression);
 209         usize += cos.getUSize();
 210         cos.writeTo(out);
 211     }
 212 
 213     private void writeFileContent(DataOutput out,
 214                                   Path p,
 215                                   Compressor compressor)
 216         throws IOException
 217     {
 218         CompressedOutputStream cos = CompressedOutputStream.newInstance(p, compressor);
 219         usize += cos.getUSize();
 220         cos.writeTo(out);
 221     }
 222 
 223     /*
 224      * Write a subsection to the given module file.
 225      *
 226      * @params file RandomAccessFile for the resulting jmod file
 227      * @params sourcedir the directory containing the file to be written
 228      * @params p Path of a file to be written
 229      * @params compressor compression type
 230      */
 231     private void writeSubSection(RandomAccessFile file,
 232                                  File sourcedir,
 233                                  Path p,
 234                                  Compressor compressor)
 235         throws IOException
 236     {
 237         CompressedOutputStream cos = CompressedOutputStream.newInstance(p, compressor);
 238         usize += cos.getUSize();
 239 
 240         String storedpath = relativePath(sourcedir.toPath(), p);
 241         SubSectionFileHeader header = new SubSectionFileHeader((int)cos.getCSize(), storedpath);
 242         header.write(file);
 243         cos.writeTo(file);
 244     }
 245 
 246 
 247     /*
 248      * Processes each of the optional file sections.
 249      */
 250     private void writeOptionalSections(RandomAccessFile file,
 251                                        File mdir,
 252                                        File nativelibs,
 253                                        File nativecmds,
 254                                        File config)
 255         throws IOException
 256     {


 283         if (nativecmds != null && directoryIsNotEmpty(nativecmds)) {
 284             writeSection(file,
 285                          SectionType.NATIVE_CMDS,
 286                          nativecmds,
 287                          listFiles(nativecmds.toPath()),
 288                          Compressor.GZIP);
 289         }
 290         if (config != null && directoryIsNotEmpty(config)) {
 291             writeSection(file,
 292                          SectionType.CONFIG,
 293                          config,
 294                          listFiles(config.toPath()),
 295                          Compressor.GZIP);
 296         }
 297     }
 298 
 299     /*
 300      * Writes out the module file header.
 301      */
 302     private void writeModuleFileHeader(RandomAccessFile file,
 303                                        long headerLength,
 304                                        ModuleArchitecture modArch)
 305         throws IOException
 306     {
 307         long csize = file.length() - headerLength;
 308 


 309         // Header Step 1
 310         // Write out the module file header (using a dummy file hash value)
 311         ModuleFileHeader.Builder hBuilder = ModuleFileHeader.newBuilder()
 312                                             .setCSize(csize)
 313                                             .setUSize(usize)
 314                                             .setArchitecture(modArch)
 315                                             .setHashType(hashtype)
 316                                             .setHash(new byte[hashtype.length()]);
 317         ModuleFileHeader fileHeader = hBuilder.build();
 318         assert (fileHeader.getLength() == headerLength);
 319 
 320         file.seek(0);
 321         fileHeader.write(file);
 322 
 323         // Calculate the correct module file hash, using a non-validating parser
 324         file.seek(0);
 325         byte[] fileHash = null;
 326         ModuleFileParser parser =
 327                 ModuleFile.newParser(Channels.newInputStream(file.getChannel()));
 328         while (parser.hasNext()) {
 329             if (parser.next().equals(Event.END_FILE))
 330                 fileHash =  parser.getHash();
 331         }
 332 
 333         // Header Step 2
 334         // Write out the module file header (using correct file hash value)
 335         hBuilder.setHash(fileHash);
 336         file.seek(0);
 337         fileHeader = hBuilder.build();
 338         assert (fileHeader.getLength() == headerLength);
 339         fileHeader.write(file);
 340     }
 341 
 342     private void listClassesResources(Path dir,
 343                                       final List<Path> classes,
 344                                       final List<Path> resources)
 345         throws IOException
 346     {

 347         Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
 348             @Override
 349             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
 350                     throws IOException {
 351                 if (!file.endsWith("module-info.class")) {
 352                     if (file.toFile().getName().endsWith(".class")) {
 353                         classes.add(file);
 354                     } else {
 355                         resources.add(file);
 356                     }
 357                 }
 358                 return FileVisitResult.CONTINUE;
 359             }
 360         });
 361     }
 362 
 363     // CompressedOutputStream and CompressedClassOutputStream are
 364     // subclass of ByteArrayOutputStream to avoid the array copy
 365     // from the compressed bytes and write buf directly to DataOutput
 366     static class CompressedOutputStream extends ByteArrayOutputStream {


 438                     pos.compress(sourcepath, classes);
 439                     return pos;
 440                 default:
 441                     throw new IllegalArgumentException("Unsupported type: " + type);
 442             }
 443         }
 444 
 445         static class Pack200GZipOutputStream extends CompressedClassOutputStream {
 446             final Pack200.Packer packer = Pack200.newPacker();
 447             Pack200GZipOutputStream(boolean fastestCompression) {
 448                 Map<String, String> p = packer.properties();
 449                 p.put(Pack200.Packer.SEGMENT_LIMIT, "-1");
 450                 if (fastestCompression) {
 451                     p.put(Pack200.Packer.EFFORT, "1");
 452                 }
 453                 p.put(Pack200.Packer.KEEP_FILE_ORDER, Pack200.Packer.FALSE);
 454                 p.put(Pack200.Packer.MODIFICATION_TIME, Pack200.Packer.LATEST);
 455                 p.put(Pack200.Packer.DEFLATE_HINT, Pack200.Packer.FALSE);
 456             }
 457 
 458             void compress(Path sourcepath, List<Path> classes)
 459                 throws IOException
 460             {
 461                 ByteArrayOutputStream os = new ByteArrayOutputStream();
 462                 try (JarOutputStream jos = new JarOutputStream(os)) {
 463                     jos.setLevel(0);
 464                     for (Path file : classes) {
 465                         // write to the JarInputStream for later pack200 compression
 466                         String name = file.toFile().getName().toLowerCase();
 467                         String p = sourcepath.relativize(file).toString();
 468                         if (!p.equals("module-info.class")) {
 469                             JarEntry entry = new JarEntry(p);
 470                             jos.putNextEntry(entry);
 471                             Files.copy(file, jos);
 472                             jos.closeEntry();
 473                         }
 474                         size += file.toFile().length();
 475                     }
 476                 }
 477 
 478                 byte[] data = os.toByteArray();
 479                 try (JarInputStream jin =
 480                         new JarInputStream(new ByteArrayInputStream(data));
 481                      GZIPOutputStream gout = new GZIPOutputStream(this)) {
 482                     packer.pack(jin, gout);
 483                 }
 484             }
 485         }
 486     }
 487 
 488     private String relativePath(Path sourcepath, Path path)
 489         throws IOException
 490     {
 491         Path relativepath = sourcepath.relativize(path);
 492 
 493         // The '/' character separates nested directories in path names.
 494         String pathseparator = relativepath.getFileSystem().getSeparator();
 495         String stored = relativepath.toString().replace(pathseparator, "/");
 496 
 497         // The path names of native-code files
 498         // must not include more than one element.
 499         // ## Temporarily turn off this check until the location of
 500         // ## the native libraries in jdk modules are changed
 501         // ensureShortNativePath(realpath, stored);
 502         return stored;
 503     }
 504 
 505     private List<Path> listFiles(Path path)
 506         throws IOException
 507     {
 508         final List<Path> list = new ArrayList<>();
 509         Files.walkFileTree(path, new SimpleFileVisitor<Path>() {

 510             @Override
 511             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
 512                     throws IOException {
 513                 list.add(file);
 514                 return FileVisitResult.CONTINUE;
 515             }
 516         });
 517         return list;
 518     }
 519 
 520     /*




































 521      * Check if a given directory is not empty.
 522      */
 523     private static boolean directoryIsNotEmpty(File dir)
 524         throws IOException
 525     {
 526         try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir.toPath())) {
 527             return ds.iterator().hasNext();
 528         }
 529     }

 530 }