276 if (zname.startsWith("./")) { |
277 if (zname.startsWith("./")) { |
277 zname = zname.substring(2); |
278 zname = zname.substring(2); |
278 } |
279 } |
279 } |
280 } |
280 } |
281 } |
|
282 |
281 if (cflag) { |
283 if (cflag) { |
282 Manifest manifest = null; |
284 Manifest manifest = null; |
283 InputStream in = null; |
|
284 |
|
285 if (!Mflag) { |
285 if (!Mflag) { |
286 if (mname != null) { |
286 if (mname != null) { |
287 in = new FileInputStream(mname); |
287 try (InputStream in = new FileInputStream(mname)) { |
288 manifest = new Manifest(new BufferedInputStream(in)); |
288 manifest = new Manifest(new BufferedInputStream(in)); |
|
289 } |
289 } else { |
290 } else { |
290 manifest = new Manifest(); |
291 manifest = new Manifest(); |
291 } |
292 } |
292 addVersion(manifest); |
293 addVersion(manifest); |
293 addCreatedBy(manifest); |
294 addCreatedBy(manifest); |
294 if (isAmbiguousMainClass(manifest)) { |
295 if (isAmbiguousMainClass(manifest)) { |
295 if (in != null) { |
|
296 in.close(); |
|
297 } |
|
298 return false; |
296 return false; |
299 } |
297 } |
300 if (ename != null) { |
298 if (ename != null) { |
301 addMainClass(manifest, ename); |
299 addMainClass(manifest, ename); |
302 } |
300 } |
303 if (isMultiRelease) { |
301 if (isMultiRelease) { |
304 addMultiRelease(manifest); |
302 addMultiRelease(manifest); |
305 } |
303 } |
306 } |
304 } |
|
305 |
307 Map<String,Path> moduleInfoPaths = new HashMap<>(); |
306 Map<String,Path> moduleInfoPaths = new HashMap<>(); |
308 for (int version : filesMap.keySet()) { |
307 for (int version : filesMap.keySet()) { |
309 String[] files = filesMap.get(version); |
308 String[] files = filesMap.get(version); |
310 expand(null, files, false, moduleInfoPaths, version); |
309 expand(null, files, false, moduleInfoPaths, version); |
311 } |
310 } |
|
311 |
312 Map<String,byte[]> moduleInfos = new LinkedHashMap<>(); |
312 Map<String,byte[]> moduleInfos = new LinkedHashMap<>(); |
313 if (!moduleInfoPaths.isEmpty()) { |
313 if (!moduleInfoPaths.isEmpty()) { |
314 if (!checkModuleInfos(moduleInfoPaths)) |
314 if (!checkModuleInfos(moduleInfoPaths)) |
315 return false; |
315 return false; |
316 |
316 |
330 } else if (moduleVersion != null || modulesToHash != null) { |
330 } else if (moduleVersion != null || modulesToHash != null) { |
331 error(getMsg("error.module.options.without.info")); |
331 error(getMsg("error.module.options.without.info")); |
332 return false; |
332 return false; |
333 } |
333 } |
334 |
334 |
335 OutputStream out; |
335 if (vflag && fname == null) { |
336 if (fname != null) { |
336 // Disable verbose output so that it does not appear |
337 out = new FileOutputStream(fname); |
337 // on stdout along with file data |
338 } else { |
338 // error("Warning: -v option ignored"); |
339 out = new FileOutputStream(FileDescriptor.out); |
339 vflag = false; |
340 if (vflag) { |
340 } |
341 // Disable verbose output so that it does not appear |
341 |
342 // on stdout along with file data |
|
343 // error("Warning: -v option ignored"); |
|
344 vflag = false; |
|
345 } |
|
346 } |
|
347 File tmpfile = null; |
|
348 final OutputStream finalout = out; |
|
349 final String tmpbase = (fname == null) |
342 final String tmpbase = (fname == null) |
350 ? "tmpjar" |
343 ? "tmpjar" |
351 : fname.substring(fname.indexOf(File.separatorChar) + 1); |
344 : fname.substring(fname.indexOf(File.separatorChar) + 1); |
|
345 File tmpfile = createTemporaryFile(tmpbase, ".jar"); |
|
346 |
|
347 try (OutputStream out = new FileOutputStream(tmpfile)) { |
|
348 create(new BufferedOutputStream(out, 4096), manifest, moduleInfos); |
|
349 } |
|
350 |
352 if (nflag) { |
351 if (nflag) { |
353 tmpfile = createTemporaryFile(tmpbase, ".jar"); |
352 File packFile = createTemporaryFile(tmpbase, ".pack"); |
354 out = new FileOutputStream(tmpfile); |
|
355 } |
|
356 create(new BufferedOutputStream(out, 4096), manifest, moduleInfos); |
|
357 |
|
358 if (in != null) { |
|
359 in.close(); |
|
360 } |
|
361 out.close(); |
|
362 if (nflag) { |
|
363 JarFile jarFile = null; |
|
364 File packFile = null; |
|
365 JarOutputStream jos = null; |
|
366 try { |
353 try { |
367 Packer packer = Pack200.newPacker(); |
354 Packer packer = Pack200.newPacker(); |
368 Map<String, String> p = packer.properties(); |
355 Map<String, String> p = packer.properties(); |
369 p.put(Packer.EFFORT, "1"); // Minimal effort to conserve CPU |
356 p.put(Packer.EFFORT, "1"); // Minimal effort to conserve CPU |
370 jarFile = new JarFile(tmpfile.getCanonicalPath()); |
357 try ( |
371 packFile = createTemporaryFile(tmpbase, ".pack"); |
358 JarFile jarFile = new JarFile(tmpfile.getCanonicalPath()); |
372 out = new FileOutputStream(packFile); |
359 OutputStream pack = new FileOutputStream(packFile) |
373 packer.pack(jarFile, out); |
360 ) { |
374 jos = new JarOutputStream(finalout); |
361 packer.pack(jarFile, pack); |
375 Unpacker unpacker = Pack200.newUnpacker(); |
|
376 unpacker.unpack(packFile, jos); |
|
377 } catch (IOException ioe) { |
|
378 fatalError(ioe); |
|
379 } finally { |
|
380 if (jarFile != null) { |
|
381 jarFile.close(); |
|
382 } |
362 } |
383 if (out != null) { |
363 if (tmpfile.exists()) { |
384 out.close(); |
|
385 } |
|
386 if (jos != null) { |
|
387 jos.close(); |
|
388 } |
|
389 if (tmpfile != null && tmpfile.exists()) { |
|
390 tmpfile.delete(); |
364 tmpfile.delete(); |
391 } |
365 } |
392 if (packFile != null && packFile.exists()) { |
366 tmpfile = createTemporaryFile(tmpbase, ".jar"); |
393 packFile.delete(); |
367 try ( |
|
368 OutputStream out = new FileOutputStream(tmpfile); |
|
369 JarOutputStream jos = new JarOutputStream(out) |
|
370 ) { |
|
371 Unpacker unpacker = Pack200.newUnpacker(); |
|
372 unpacker.unpack(packFile, jos); |
394 } |
373 } |
395 } |
374 } finally { |
396 } |
375 Files.deleteIfExists(packFile.toPath()); |
|
376 } |
|
377 } |
|
378 |
|
379 validateAndClose(tmpfile); |
|
380 |
397 } else if (uflag) { |
381 } else if (uflag) { |
398 File inputFile = null, tmpFile = null; |
382 File inputFile = null, tmpFile = null; |
399 FileInputStream in; |
|
400 FileOutputStream out; |
|
401 if (fname != null) { |
383 if (fname != null) { |
402 inputFile = new File(fname); |
384 inputFile = new File(fname); |
403 tmpFile = createTempFileInSameDirectoryAs(inputFile); |
385 tmpFile = createTempFileInSameDirectoryAs(inputFile); |
404 in = new FileInputStream(inputFile); |
|
405 out = new FileOutputStream(tmpFile); |
|
406 } else { |
386 } else { |
407 in = new FileInputStream(FileDescriptor.in); |
|
408 out = new FileOutputStream(FileDescriptor.out); |
|
409 vflag = false; |
387 vflag = false; |
410 } |
388 tmpFile = createTemporaryFile("tmpjar", ".jar"); |
411 InputStream manifest = (!Mflag && (mname != null)) ? |
389 } |
412 (new FileInputStream(mname)) : null; |
|
413 |
390 |
414 Map<String,Path> moduleInfoPaths = new HashMap<>(); |
391 Map<String,Path> moduleInfoPaths = new HashMap<>(); |
415 for (int version : filesMap.keySet()) { |
392 for (int version : filesMap.keySet()) { |
416 String[] files = filesMap.get(version); |
393 String[] files = filesMap.get(version); |
417 expand(null, files, true, moduleInfoPaths, version); |
394 expand(null, files, true, moduleInfoPaths, version); |
419 |
396 |
420 Map<String,byte[]> moduleInfos = new HashMap<>(); |
397 Map<String,byte[]> moduleInfos = new HashMap<>(); |
421 for (Map.Entry<String,Path> e : moduleInfoPaths.entrySet()) |
398 for (Map.Entry<String,Path> e : moduleInfoPaths.entrySet()) |
422 moduleInfos.put(e.getKey(), readModuleInfo(e.getValue())); |
399 moduleInfos.put(e.getKey(), readModuleInfo(e.getValue())); |
423 |
400 |
424 boolean updateOk = update(in, new BufferedOutputStream(out), |
401 try ( |
425 manifest, moduleInfos, null); |
402 FileInputStream in = (fname != null) ? new FileInputStream(inputFile) |
|
403 : new FileInputStream(FileDescriptor.in); |
|
404 FileOutputStream out = new FileOutputStream(tmpFile); |
|
405 InputStream manifest = (!Mflag && (mname != null)) ? |
|
406 (new FileInputStream(mname)) : null; |
|
407 ) { |
|
408 boolean updateOk = update(in, new BufferedOutputStream(out), |
|
409 manifest, moduleInfos, null); |
|
410 if (ok) { |
|
411 ok = updateOk; |
|
412 } |
|
413 } |
426 |
414 |
427 // Consistency checks for modular jars. |
415 // Consistency checks for modular jars. |
428 if (!moduleInfos.isEmpty()) { |
416 if (!moduleInfos.isEmpty()) { |
429 if(!checkServices(moduleInfos.get(MODULE_INFO))) |
417 if(!checkServices(moduleInfos.get(MODULE_INFO))) |
430 return false; |
418 return false; |
431 } |
419 } |
432 |
420 |
433 if (ok) { |
421 validateAndClose(tmpFile); |
434 ok = updateOk; |
422 |
435 } |
|
436 in.close(); |
|
437 out.close(); |
|
438 if (manifest != null) { |
|
439 manifest.close(); |
|
440 } |
|
441 if (ok && fname != null) { |
|
442 // on Win32, we need this delete |
|
443 inputFile.delete(); |
|
444 if (!tmpFile.renameTo(inputFile)) { |
|
445 tmpFile.delete(); |
|
446 throw new IOException(getMsg("error.write.file")); |
|
447 } |
|
448 tmpFile.delete(); |
|
449 } |
|
450 } else if (tflag) { |
423 } else if (tflag) { |
451 replaceFSC(filesMap); |
424 replaceFSC(filesMap); |
452 // For the "list table contents" action, access using the |
425 // For the "list table contents" action, access using the |
453 // ZipFile class is always most efficient since only a |
426 // ZipFile class is always most efficient since only a |
454 // "one-finger" scan through the central directory is required. |
427 // "one-finger" scan through the central directory is required. |
530 |
525 |
531 Stream<String> filesToEntryNames(Map.Entry<Integer,String[]> fileEntries) { |
526 Stream<String> filesToEntryNames(Map.Entry<Integer,String[]> fileEntries) { |
532 int version = fileEntries.getKey(); |
527 int version = fileEntries.getKey(); |
533 return Stream.of(fileEntries.getValue()) |
528 return Stream.of(fileEntries.getValue()) |
534 .map(f -> (new EntryName(f, version)).entryName); |
529 .map(f -> (new EntryName(f, version)).entryName); |
|
530 } |
|
531 |
|
532 // sort base entries before versioned entries, and sort entry classes with |
|
533 // nested classes so that the top level class appears before the associated |
|
534 // nested class |
|
535 private Comparator<JarEntry> entryComparator = (je1, je2) -> { |
|
536 String s1 = je1.getName(); |
|
537 String s2 = je2.getName(); |
|
538 if (s1.equals(s2)) return 0; |
|
539 boolean b1 = s1.startsWith(VERSIONS_DIR); |
|
540 boolean b2 = s2.startsWith(VERSIONS_DIR); |
|
541 if (b1 && !b2) return 1; |
|
542 if (!b1 && b2) return -1; |
|
543 int n = 0; // starting char for String compare |
|
544 if (b1 && b2) { |
|
545 // normally strings would be sorted so "10" goes before "9", but |
|
546 // version number strings need to be sorted numerically |
|
547 n = VERSIONS_DIR.length(); // skip the common prefix |
|
548 int i1 = s1.indexOf('/', n); |
|
549 int i2 = s1.indexOf('/', n); |
|
550 if (i1 == -1) throw new InvalidJarException(s1); |
|
551 if (i2 == -1) throw new InvalidJarException(s2); |
|
552 // shorter version numbers go first |
|
553 if (i1 != i2) return i1 - i2; |
|
554 // otherwise, handle equal length numbers below |
|
555 } |
|
556 int l1 = s1.length(); |
|
557 int l2 = s2.length(); |
|
558 int lim = Math.min(l1, l2); |
|
559 for (int k = n; k < lim; k++) { |
|
560 char c1 = s1.charAt(k); |
|
561 char c2 = s2.charAt(k); |
|
562 if (c1 != c2) { |
|
563 // change natural ordering so '.' comes before '$' |
|
564 // i.e. top level classes come before nested classes |
|
565 if (c1 == '$' && c2 == '.') return 1; |
|
566 if (c1 == '.' && c2 == '$') return -1; |
|
567 return c1 - c2; |
|
568 } |
|
569 } |
|
570 return l1 - l2; |
|
571 }; |
|
572 |
|
573 private boolean validate(String fname) { |
|
574 boolean valid; |
|
575 |
|
576 try (JarFile jf = new JarFile(fname)) { |
|
577 Validator validator = new Validator(this, jf); |
|
578 jf.stream() |
|
579 .filter(e -> !e.isDirectory()) |
|
580 .filter(e -> !e.getName().equals(MANIFEST_NAME)) |
|
581 .filter(e -> !e.getName().endsWith(MODULE_INFO)) |
|
582 .sorted(entryComparator) |
|
583 .forEachOrdered(validator); |
|
584 valid = validator.isValid(); |
|
585 } catch (IOException e) { |
|
586 error(formatMsg2("error.validator.jarfile.exception", fname, e.getMessage())); |
|
587 valid = false; |
|
588 } catch (InvalidJarException e) { |
|
589 error(formatMsg("error.validator.bad.entry.name", e.getMessage())); |
|
590 valid = false; |
|
591 } |
|
592 return valid; |
|
593 } |
|
594 |
|
595 private static class InvalidJarException extends RuntimeException { |
|
596 private static final long serialVersionUID = -3642329147299217726L; |
|
597 InvalidJarException(String msg) { |
|
598 super(msg); |
|
599 } |
535 } |
600 } |
536 |
601 |
537 /** |
602 /** |
538 * Parses command line arguments. |
603 * Parses command line arguments. |
539 */ |
604 */ |