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 } |