Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/jdk.jartool/share/classes/sun/tools/jar/Main.java
66646 views
1
/*
2
* Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
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.io.*;
29
import java.lang.module.Configuration;
30
import java.lang.module.FindException;
31
import java.lang.module.InvalidModuleDescriptorException;
32
import java.lang.module.ModuleDescriptor;
33
import java.lang.module.ModuleDescriptor.Exports;
34
import java.lang.module.ModuleDescriptor.Opens;
35
import java.lang.module.ModuleDescriptor.Provides;
36
import java.lang.module.ModuleDescriptor.Version;
37
import java.lang.module.ModuleFinder;
38
import java.lang.module.ModuleReader;
39
import java.lang.module.ModuleReference;
40
import java.lang.module.ResolvedModule;
41
import java.net.URI;
42
import java.nio.ByteBuffer;
43
import java.nio.file.Files;
44
import java.nio.file.Path;
45
import java.nio.file.Paths;
46
import java.nio.file.StandardCopyOption;
47
import java.text.MessageFormat;
48
import java.util.*;
49
import java.util.function.Consumer;
50
import java.util.jar.Attributes;
51
import java.util.jar.JarFile;
52
import java.util.jar.JarOutputStream;
53
import java.util.jar.Manifest;
54
import java.util.regex.Pattern;
55
import java.util.stream.Collectors;
56
import java.util.stream.Stream;
57
import java.util.zip.CRC32;
58
import java.util.zip.ZipEntry;
59
import java.util.zip.ZipFile;
60
import java.util.zip.ZipInputStream;
61
import java.util.zip.ZipOutputStream;
62
import java.util.concurrent.TimeUnit;
63
import jdk.internal.module.Checks;
64
import jdk.internal.module.ModuleHashes;
65
import jdk.internal.module.ModuleHashesBuilder;
66
import jdk.internal.module.ModuleInfo;
67
import jdk.internal.module.ModuleInfoExtender;
68
import jdk.internal.module.ModuleResolution;
69
import jdk.internal.module.ModuleTarget;
70
import jdk.internal.util.jar.JarIndex;
71
import java.time.LocalDateTime;
72
import java.time.ZoneOffset;
73
74
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
75
import static java.util.jar.JarFile.MANIFEST_NAME;
76
import static java.util.stream.Collectors.joining;
77
import static jdk.internal.util.jar.JarIndex.INDEX_NAME;
78
79
/**
80
* This class implements a simple utility for creating files in the JAR
81
* (Java Archive) file format. The JAR format is based on the ZIP file
82
* format, with optional meta-information stored in a MANIFEST entry.
83
*/
84
public class Main {
85
String program;
86
PrintWriter out, err;
87
String fname, mname, ename;
88
String zname = "";
89
String rootjar = null;
90
91
private static final int BASE_VERSION = 0;
92
93
private static class Entry {
94
final String name;
95
final File file;
96
final boolean isDir;
97
98
Entry(File file, String name, boolean isDir) {
99
this.file = file;
100
this.isDir = isDir;
101
this.name = name;
102
}
103
104
@Override
105
public boolean equals(Object o) {
106
if (this == o) return true;
107
if (!(o instanceof Entry)) return false;
108
return this.file.equals(((Entry)o).file);
109
}
110
111
@Override
112
public int hashCode() {
113
return file.hashCode();
114
}
115
}
116
117
// An entryName(path)->Entry map generated during "expand", it helps to
118
// decide whether or not an existing entry in a jar file needs to be
119
// replaced, during the "update" operation.
120
Map<String, Entry> entryMap = new HashMap<>();
121
122
// All entries need to be added/updated.
123
Set<Entry> entries = new LinkedHashSet<>();
124
125
// module-info.class entries need to be added/updated.
126
Map<String,byte[]> moduleInfos = new HashMap<>();
127
128
// A paths Set for each version, where each Set contains directories
129
// specified by the "-C" operation.
130
Map<Integer,Set<String>> pathsMap = new HashMap<>();
131
132
// There's also a files array per version
133
// base version is the first entry and then follow with the version given
134
// from the --release option in the command-line order.
135
// The value of each entry is the files given in the command-line order.
136
Map<Integer,String[]> filesMap = new LinkedHashMap<>();
137
138
// Do we think this is a multi-release jar? Set to true
139
// if --release option found followed by at least file
140
boolean isMultiRelease;
141
142
// The last parsed --release value, if any. Used in conjunction with
143
// "-d,--describe-module" to select the operative module descriptor.
144
int releaseValue = -1;
145
146
/*
147
* cflag: create
148
* uflag: update
149
* xflag: xtract
150
* tflag: table
151
* vflag: verbose
152
* flag0: no zip compression (store only)
153
* Mflag: DO NOT generate a manifest file (just ZIP)
154
* iflag: generate jar index
155
* nflag: Perform jar normalization at the end
156
* pflag: preserve/don't strip leading slash and .. component from file name
157
* dflag: print module descriptor
158
*/
159
boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag, pflag, dflag, validate;
160
161
boolean suppressDeprecateMsg = false;
162
163
/* To support additional GNU Style informational options */
164
Consumer<PrintWriter> info;
165
166
/* Modular jar related options */
167
Version moduleVersion;
168
Pattern modulesToHash;
169
ModuleResolution moduleResolution = ModuleResolution.empty();
170
ModuleFinder moduleFinder = ModuleFinder.of();
171
172
static final String MODULE_INFO = "module-info.class";
173
static final String MANIFEST_DIR = "META-INF/";
174
static final String VERSIONS_DIR = MANIFEST_DIR + "versions/";
175
static final String VERSION = "1.0";
176
static final int VERSIONS_DIR_LENGTH = VERSIONS_DIR.length();
177
private static ResourceBundle rsrc;
178
179
/* Date option for entry timestamps resolved to UTC Local time */
180
LocalDateTime date;
181
182
/**
183
* If true, maintain compatibility with JDK releases prior to 6.0 by
184
* timestamping extracted files with the time at which they are extracted.
185
* Default is to use the time given in the archive.
186
*/
187
private static final boolean useExtractionTime =
188
Boolean.getBoolean("sun.tools.jar.useExtractionTime");
189
190
/**
191
* Initialize ResourceBundle
192
*/
193
static {
194
try {
195
rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar");
196
} catch (MissingResourceException e) {
197
throw new Error("Fatal: Resource for jar is missing");
198
}
199
}
200
201
static String getMsg(String key) {
202
try {
203
return (rsrc.getString(key));
204
} catch (MissingResourceException e) {
205
throw new Error("Error in message file", e);
206
}
207
}
208
209
static String formatMsg(String key, String arg) {
210
String msg = getMsg(key);
211
String[] args = new String[1];
212
args[0] = arg;
213
return MessageFormat.format(msg, (Object[]) args);
214
}
215
216
static String formatMsg2(String key, String arg, String arg1) {
217
String msg = getMsg(key);
218
String[] args = new String[2];
219
args[0] = arg;
220
args[1] = arg1;
221
return MessageFormat.format(msg, (Object[]) args);
222
}
223
224
public Main(PrintStream out, PrintStream err, String program) {
225
this.out = new PrintWriter(out, true);
226
this.err = new PrintWriter(err, true);
227
this.program = program;
228
}
229
230
public Main(PrintWriter out, PrintWriter err, String program) {
231
this.out = out;
232
this.err = err;
233
this.program = program;
234
}
235
236
/**
237
* Creates a new empty temporary file in the same directory as the
238
* specified file. A variant of File.createTempFile.
239
*/
240
private static File createTempFileInSameDirectoryAs(File file)
241
throws IOException {
242
File dir = file.getParentFile();
243
if (dir == null)
244
dir = new File(".");
245
return File.createTempFile("jartmp", null, dir);
246
}
247
248
private boolean ok;
249
250
/**
251
* Starts main program with the specified arguments.
252
*/
253
@SuppressWarnings({"removal"})
254
public synchronized boolean run(String args[]) {
255
ok = true;
256
if (!parseArgs(args)) {
257
return false;
258
}
259
File tmpFile = null;
260
try {
261
if (cflag || uflag) {
262
if (fname != null) {
263
// The name of the zip file as it would appear as its own
264
// zip file entry. We use this to make sure that we don't
265
// add the zip file to itself.
266
zname = fname.replace(File.separatorChar, '/');
267
if (zname.startsWith("./")) {
268
zname = zname.substring(2);
269
}
270
}
271
}
272
if (cflag) {
273
Manifest manifest = null;
274
if (!Mflag) {
275
if (mname != null) {
276
try (InputStream in = new FileInputStream(mname)) {
277
manifest = new Manifest(new BufferedInputStream(in));
278
}
279
} else {
280
manifest = new Manifest();
281
}
282
addVersion(manifest);
283
addCreatedBy(manifest);
284
if (isAmbiguousMainClass(manifest)) {
285
return false;
286
}
287
if (ename != null) {
288
addMainClass(manifest, ename);
289
}
290
if (isMultiRelease) {
291
addMultiRelease(manifest);
292
}
293
}
294
expand();
295
if (!moduleInfos.isEmpty()) {
296
// All actual file entries (excl manifest and module-info.class)
297
Set<String> jentries = new HashSet<>();
298
// all packages if it's a class or resource
299
Set<String> packages = new HashSet<>();
300
entries.stream()
301
.filter(e -> !e.isDir)
302
.forEach( e -> {
303
addPackageIfNamed(packages, e.name);
304
jentries.add(e.name);
305
});
306
addExtendedModuleAttributes(moduleInfos, packages);
307
308
// Basic consistency checks for modular jars.
309
if (!checkModuleInfo(moduleInfos.get(MODULE_INFO), jentries))
310
return false;
311
312
} else if (moduleVersion != null || modulesToHash != null) {
313
error(getMsg("error.module.options.without.info"));
314
return false;
315
}
316
if (vflag && fname == null) {
317
// Disable verbose output so that it does not appear
318
// on stdout along with file data
319
// error("Warning: -v option ignored");
320
vflag = false;
321
}
322
final String tmpbase = (fname == null)
323
? "tmpjar"
324
: fname.substring(fname.indexOf(File.separatorChar) + 1);
325
326
tmpFile = createTemporaryFile(tmpbase, ".jar");
327
try (OutputStream out = new FileOutputStream(tmpFile)) {
328
create(new BufferedOutputStream(out, 4096), manifest);
329
}
330
validateAndClose(tmpFile);
331
} else if (uflag) {
332
File inputFile = null;
333
if (fname != null) {
334
inputFile = new File(fname);
335
tmpFile = createTempFileInSameDirectoryAs(inputFile);
336
} else {
337
vflag = false;
338
tmpFile = createTemporaryFile("tmpjar", ".jar");
339
}
340
expand();
341
try (FileInputStream in = (fname != null) ? new FileInputStream(inputFile)
342
: new FileInputStream(FileDescriptor.in);
343
FileOutputStream out = new FileOutputStream(tmpFile);
344
InputStream manifest = (!Mflag && (mname != null)) ?
345
(new FileInputStream(mname)) : null;
346
) {
347
boolean updateOk = update(in, new BufferedOutputStream(out),
348
manifest, moduleInfos, null);
349
if (ok) {
350
ok = updateOk;
351
}
352
}
353
validateAndClose(tmpFile);
354
} else if (tflag) {
355
replaceFSC(filesMap);
356
// For the "list table contents" action, access using the
357
// ZipFile class is always most efficient since only a
358
// "one-finger" scan through the central directory is required.
359
String[] files = filesMapToFiles(filesMap);
360
if (fname != null) {
361
list(fname, files);
362
} else {
363
InputStream in = new FileInputStream(FileDescriptor.in);
364
try {
365
list(new BufferedInputStream(in), files);
366
} finally {
367
in.close();
368
}
369
}
370
} else if (xflag) {
371
replaceFSC(filesMap);
372
// For the extract action, when extracting all the entries,
373
// access using the ZipInputStream class is most efficient,
374
// since only a single sequential scan through the zip file is
375
// required. When using the ZipFile class, a "two-finger" scan
376
// is required, but this is likely to be more efficient when a
377
// partial extract is requested. In case the zip file has
378
// "leading garbage", we fall back from the ZipInputStream
379
// implementation to the ZipFile implementation, since only the
380
// latter can handle it.
381
382
String[] files = filesMapToFiles(filesMap);
383
if (fname != null && files != null) {
384
extract(fname, files);
385
} else {
386
InputStream in = (fname == null)
387
? new FileInputStream(FileDescriptor.in)
388
: new FileInputStream(fname);
389
try {
390
if (!extract(new BufferedInputStream(in), files) && fname != null) {
391
extract(fname, files);
392
}
393
} finally {
394
in.close();
395
}
396
}
397
} else if (iflag) {
398
String[] files = filesMap.get(BASE_VERSION); // base entries only, can be null
399
genIndex(rootjar, files);
400
} else if (dflag) {
401
boolean found;
402
if (fname != null) {
403
try (ZipFile zf = new ZipFile(fname)) {
404
found = describeModule(zf);
405
}
406
} else {
407
try (FileInputStream fin = new FileInputStream(FileDescriptor.in)) {
408
found = describeModuleFromStream(fin);
409
}
410
}
411
if (!found)
412
error(getMsg("error.module.descriptor.not.found"));
413
} else if (validate) {
414
File file;
415
if (fname != null) {
416
file = new File(fname);
417
} else {
418
file = createTemporaryFile("tmpJar", ".jar");
419
try (InputStream in = new FileInputStream(FileDescriptor.in)) {
420
Files.copy(in, file.toPath());
421
}
422
}
423
ok = validateJar(file);
424
}
425
} catch (IOException e) {
426
fatalError(e);
427
ok = false;
428
} catch (Error ee) {
429
ee.printStackTrace();
430
ok = false;
431
} catch (Throwable t) {
432
t.printStackTrace();
433
ok = false;
434
} finally {
435
if (tmpFile != null && tmpFile.exists())
436
tmpFile.delete();
437
}
438
out.flush();
439
err.flush();
440
return ok;
441
}
442
443
private boolean validateJar(File file) throws IOException {
444
try (ZipFile zf = new ZipFile(file)) {
445
return Validator.validate(this, zf);
446
} catch (IOException e) {
447
error(formatMsg2("error.validator.jarfile.exception", fname, e.getMessage()));
448
return true;
449
}
450
}
451
452
private void validateAndClose(File tmpfile) throws IOException {
453
if (ok && isMultiRelease) {
454
ok = validateJar(tmpfile);
455
if (!ok) {
456
error(formatMsg("error.validator.jarfile.invalid", fname));
457
}
458
}
459
Path path = tmpfile.toPath();
460
try {
461
if (ok) {
462
if (fname != null) {
463
Files.move(path, Paths.get(fname), StandardCopyOption.REPLACE_EXISTING);
464
} else {
465
Files.copy(path, new FileOutputStream(FileDescriptor.out));
466
}
467
}
468
} finally {
469
Files.deleteIfExists(path);
470
}
471
}
472
473
private String[] filesMapToFiles(Map<Integer,String[]> filesMap) {
474
if (filesMap.isEmpty()) return null;
475
return filesMap.entrySet()
476
.stream()
477
.flatMap(this::filesToEntryNames)
478
.toArray(String[]::new);
479
}
480
481
Stream<String> filesToEntryNames(Map.Entry<Integer,String[]> fileEntries) {
482
int version = fileEntries.getKey();
483
Set<String> cpaths = pathsMap.get(version);
484
return Stream.of(fileEntries.getValue())
485
.map(f -> toVersionedName(toEntryName(f, cpaths, false), version));
486
}
487
488
/**
489
* Parses command line arguments.
490
*/
491
boolean parseArgs(String args[]) {
492
/* Preprocess and expand @file arguments */
493
try {
494
args = CommandLine.parse(args);
495
} catch (FileNotFoundException e) {
496
fatalError(formatMsg("error.cant.open", e.getMessage()));
497
return false;
498
} catch (IOException e) {
499
fatalError(e);
500
return false;
501
}
502
/* parse flags */
503
int count = 1;
504
try {
505
String flags = args[0];
506
507
// Note: flags.length == 2 can be treated as the short version of
508
// the GNU option since the there cannot be any other options,
509
// excluding -C, as per the old way.
510
if (flags.startsWith("--") ||
511
(flags.startsWith("-") && flags.length() == 2)) {
512
try {
513
count = GNUStyleOptions.parseOptions(this, args);
514
} catch (GNUStyleOptions.BadArgs x) {
515
if (info == null) {
516
if (x.showUsage) {
517
usageError(x.getMessage());
518
} else {
519
error(x.getMessage());
520
}
521
return false;
522
}
523
}
524
if (info != null) {
525
info.accept(out);
526
return true;
527
}
528
} else {
529
// Legacy/compatibility options
530
if (flags.startsWith("-")) {
531
flags = flags.substring(1);
532
}
533
for (int i = 0; i < flags.length(); i++) {
534
switch (flags.charAt(i)) {
535
case 'c':
536
if (xflag || tflag || uflag || iflag) {
537
usageError(getMsg("error.multiple.main.operations"));
538
return false;
539
}
540
cflag = true;
541
break;
542
case 'u':
543
if (cflag || xflag || tflag || iflag) {
544
usageError(getMsg("error.multiple.main.operations"));
545
return false;
546
}
547
uflag = true;
548
break;
549
case 'x':
550
if (cflag || uflag || tflag || iflag) {
551
usageError(getMsg("error.multiple.main.operations"));
552
return false;
553
}
554
xflag = true;
555
break;
556
case 't':
557
if (cflag || uflag || xflag || iflag) {
558
usageError(getMsg("error.multiple.main.operations"));
559
return false;
560
}
561
tflag = true;
562
break;
563
case 'M':
564
Mflag = true;
565
break;
566
case 'v':
567
vflag = true;
568
break;
569
case 'f':
570
fname = args[count++];
571
break;
572
case 'm':
573
mname = args[count++];
574
break;
575
case '0':
576
flag0 = true;
577
break;
578
case 'i':
579
if (cflag || uflag || xflag || tflag) {
580
usageError(getMsg("error.multiple.main.operations"));
581
return false;
582
}
583
// do not increase the counter, files will contain rootjar
584
rootjar = args[count++];
585
iflag = true;
586
break;
587
case 'e':
588
ename = args[count++];
589
break;
590
case 'P':
591
pflag = true;
592
break;
593
default:
594
usageError(formatMsg("error.illegal.option",
595
String.valueOf(flags.charAt(i))));
596
return false;
597
}
598
}
599
}
600
} catch (ArrayIndexOutOfBoundsException e) {
601
usageError(getMsg("main.usage.summary"));
602
return false;
603
}
604
if (!cflag && !tflag && !xflag && !uflag && !iflag && !dflag && !validate) {
605
usageError(getMsg("error.bad.option"));
606
return false;
607
}
608
609
/* parse file arguments */
610
int n = args.length - count;
611
if (n > 0) {
612
int version = BASE_VERSION;
613
int k = 0;
614
String[] nameBuf = new String[n];
615
pathsMap.put(version, new HashSet<>());
616
try {
617
for (int i = count; i < args.length; i++) {
618
if (args[i].equals("-C")) {
619
if (dflag) {
620
// "--describe-module/-d" does not require file argument(s),
621
// but does accept --release
622
usageError(getMsg("error.bad.dflag"));
623
return false;
624
}
625
/* change the directory */
626
String dir = args[++i];
627
dir = (dir.endsWith(File.separator) ?
628
dir : (dir + File.separator));
629
dir = dir.replace(File.separatorChar, '/');
630
631
boolean hasUNC = (File.separatorChar == '\\'&& dir.startsWith("//"));
632
while (dir.indexOf("//") > -1) {
633
dir = dir.replace("//", "/");
634
}
635
if (hasUNC) { // Restore Windows UNC path.
636
dir = "/" + dir;
637
}
638
pathsMap.get(version).add(dir);
639
nameBuf[k++] = dir + args[++i];
640
} else if (args[i].startsWith("--release")) {
641
int v = BASE_VERSION;
642
try {
643
v = Integer.valueOf(args[++i]);
644
} catch (NumberFormatException x) {
645
error(formatMsg("error.release.value.notnumber", args[i]));
646
// this will fall into the next error, thus returning false
647
}
648
if (v < 9) {
649
usageError(formatMsg("error.release.value.toosmall", String.valueOf(v)));
650
return false;
651
}
652
// associate the files, if any, with the previous version number
653
if (k > 0) {
654
String[] files = new String[k];
655
System.arraycopy(nameBuf, 0, files, 0, k);
656
filesMap.put(version, files);
657
isMultiRelease = version > BASE_VERSION;
658
}
659
// reset the counters and start with the new version number
660
k = 0;
661
nameBuf = new String[n];
662
version = v;
663
releaseValue = version;
664
pathsMap.put(version, new HashSet<>());
665
} else {
666
if (dflag) {
667
// "--describe-module/-d" does not require file argument(s),
668
// but does accept --release
669
usageError(getMsg("error.bad.dflag"));
670
return false;
671
}
672
nameBuf[k++] = args[i];
673
}
674
}
675
} catch (ArrayIndexOutOfBoundsException e) {
676
usageError(getMsg("error.bad.file.arg"));
677
return false;
678
}
679
// associate remaining files, if any, with a version
680
if (k > 0) {
681
String[] files = new String[k];
682
System.arraycopy(nameBuf, 0, files, 0, k);
683
filesMap.put(version, files);
684
isMultiRelease = version > BASE_VERSION;
685
}
686
} else if (cflag && (mname == null)) {
687
usageError(getMsg("error.bad.cflag"));
688
return false;
689
} else if (uflag) {
690
if ((mname != null) || (ename != null) || moduleVersion != null) {
691
/* just want to update the manifest */
692
return true;
693
} else {
694
usageError(getMsg("error.bad.uflag"));
695
return false;
696
}
697
}
698
return true;
699
}
700
701
/*
702
* Add the package of the given resource name if it's a .class
703
* or a resource in a named package.
704
*/
705
void addPackageIfNamed(Set<String> packages, String name) {
706
if (name.startsWith(VERSIONS_DIR)) {
707
// trim the version dir prefix
708
int i0 = VERSIONS_DIR_LENGTH;
709
int i = name.indexOf('/', i0);
710
if (i <= 0) {
711
warn(formatMsg("warn.release.unexpected.versioned.entry", name));
712
return;
713
}
714
while (i0 < i) {
715
char c = name.charAt(i0);
716
if (c < '0' || c > '9') {
717
warn(formatMsg("warn.release.unexpected.versioned.entry", name));
718
return;
719
}
720
i0++;
721
}
722
name = name.substring(i + 1, name.length());
723
}
724
String pn = toPackageName(name);
725
// add if this is a class or resource in a package
726
if (Checks.isPackageName(pn)) {
727
packages.add(pn);
728
}
729
}
730
731
private String toEntryName(String name, Set<String> cpaths, boolean isDir) {
732
name = name.replace(File.separatorChar, '/');
733
if (isDir) {
734
name = name.endsWith("/") ? name : name + "/";
735
}
736
String matchPath = "";
737
for (String path : cpaths) {
738
if (name.startsWith(path) && path.length() > matchPath.length()) {
739
matchPath = path;
740
}
741
}
742
name = safeName(name.substring(matchPath.length()));
743
// the old implementaton doesn't remove
744
// "./" if it was led by "/" (?)
745
if (name.startsWith("./")) {
746
name = name.substring(2);
747
}
748
return name;
749
}
750
751
private static String toVersionedName(String name, int version) {
752
return version > BASE_VERSION
753
? VERSIONS_DIR + version + "/" + name : name;
754
}
755
756
private static String toPackageName(String path) {
757
int index = path.lastIndexOf('/');
758
if (index != -1) {
759
return path.substring(0, index).replace('/', '.');
760
} else {
761
return "";
762
}
763
}
764
765
private void expand() throws IOException {
766
for (int version : filesMap.keySet()) {
767
String[] files = filesMap.get(version);
768
expand(null, files, pathsMap.get(version), version);
769
}
770
}
771
772
/**
773
* Expands list of files to process into full list of all files that
774
* can be found by recursively descending directories.
775
*
776
* @param dir parent directory
777
* @param files list of files to expand
778
* @param cpaths set of directories specified by -C option for the files
779
* @throws IOException if an I/O error occurs
780
*/
781
private void expand(File dir, String[] files, Set<String> cpaths, int version)
782
throws IOException
783
{
784
if (files == null) {
785
return;
786
}
787
788
for (int i = 0; i < files.length; i++) {
789
File f;
790
if (dir == null) {
791
f = new File(files[i]);
792
} else {
793
f = new File(dir, files[i]);
794
}
795
796
boolean isDir = f.isDirectory();
797
String name = toEntryName(f.getPath(), cpaths, isDir);
798
799
if (version != BASE_VERSION) {
800
if (name.startsWith(VERSIONS_DIR)) {
801
// the entry starts with VERSIONS_DIR and version != BASE_VERSION,
802
// which means the "[dirs|files]" in --release v [dirs|files]
803
// includes VERSIONS_DIR-ed entries --> warning and skip (?)
804
error(formatMsg2("error.release.unexpected.versioned.entry",
805
name, String.valueOf(version)));
806
ok = false;
807
return;
808
}
809
name = toVersionedName(name, version);
810
}
811
812
if (f.isFile()) {
813
Entry e = new Entry(f, name, false);
814
if (isModuleInfoEntry(name)) {
815
moduleInfos.putIfAbsent(name, Files.readAllBytes(f.toPath()));
816
if (uflag) {
817
entryMap.put(name, e);
818
}
819
} else if (entries.add(e)) {
820
if (uflag) {
821
entryMap.put(name, e);
822
}
823
}
824
} else if (isDir) {
825
Entry e = new Entry(f, name, true);
826
if (entries.add(e)) {
827
// utilize entryMap for the duplicate dir check even in
828
// case of cflag == true.
829
// dir name conflict/duplicate could happen with -C option.
830
// just remove the last "e" from the "entries" (zos will fail
831
// with "duplicated" entries), but continue expanding the
832
// sub tree
833
if (entryMap.containsKey(name)) {
834
entries.remove(e);
835
} else {
836
entryMap.put(name, e);
837
}
838
String[] dirFiles = f.list();
839
// Ensure files list is sorted for reproducible jar content
840
if (dirFiles != null) {
841
Arrays.sort(dirFiles);
842
}
843
expand(f, dirFiles, cpaths, version);
844
}
845
} else {
846
error(formatMsg("error.nosuch.fileordir", String.valueOf(f)));
847
ok = false;
848
}
849
}
850
}
851
852
/**
853
* Creates a new JAR file.
854
*/
855
void create(OutputStream out, Manifest manifest) throws IOException
856
{
857
try (ZipOutputStream zos = new JarOutputStream(out)) {
858
if (flag0) {
859
zos.setMethod(ZipOutputStream.STORED);
860
}
861
// TODO: check module-info attributes against manifest ??
862
if (manifest != null) {
863
if (vflag) {
864
output(getMsg("out.added.manifest"));
865
}
866
ZipEntry e = new ZipEntry(MANIFEST_DIR);
867
setZipEntryTime(e);
868
e.setSize(0);
869
e.setCrc(0);
870
zos.putNextEntry(e);
871
e = new ZipEntry(MANIFEST_NAME);
872
setZipEntryTime(e);
873
if (flag0) {
874
crc32Manifest(e, manifest);
875
}
876
zos.putNextEntry(e);
877
manifest.write(zos);
878
zos.closeEntry();
879
}
880
updateModuleInfo(moduleInfos, zos);
881
for (Entry entry : entries) {
882
addFile(zos, entry);
883
}
884
}
885
}
886
887
private char toUpperCaseASCII(char c) {
888
return (c < 'a' || c > 'z') ? c : (char) (c + 'A' - 'a');
889
}
890
891
/**
892
* Compares two strings for equality, ignoring case. The second
893
* argument must contain only upper-case ASCII characters.
894
* We don't want case comparison to be locale-dependent (else we
895
* have the notorious "turkish i bug").
896
*/
897
private boolean equalsIgnoreCase(String s, String upper) {
898
assert upper.toUpperCase(java.util.Locale.ENGLISH).equals(upper);
899
int len;
900
if ((len = s.length()) != upper.length())
901
return false;
902
for (int i = 0; i < len; i++) {
903
char c1 = s.charAt(i);
904
char c2 = upper.charAt(i);
905
if (c1 != c2 && toUpperCaseASCII(c1) != c2)
906
return false;
907
}
908
return true;
909
}
910
911
/**
912
* Updates an existing jar file.
913
*/
914
boolean update(InputStream in, OutputStream out,
915
InputStream newManifest,
916
Map<String,byte[]> moduleInfos,
917
JarIndex jarIndex) throws IOException
918
{
919
ZipInputStream zis = new ZipInputStream(in);
920
ZipOutputStream zos = new JarOutputStream(out);
921
ZipEntry e = null;
922
boolean foundManifest = false;
923
boolean updateOk = true;
924
925
// All actual entries added/updated/existing, in the jar file (excl manifest
926
// and module-info.class ).
927
Set<String> jentries = new HashSet<>();
928
929
if (jarIndex != null) {
930
addIndex(jarIndex, zos);
931
}
932
933
// put the old entries first, replace if necessary
934
while ((e = zis.getNextEntry()) != null) {
935
String name = e.getName();
936
937
boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
938
boolean isModuleInfoEntry = isModuleInfoEntry(name);
939
940
if ((jarIndex != null && equalsIgnoreCase(name, INDEX_NAME))
941
|| (Mflag && isManifestEntry)) {
942
continue;
943
} else if (isManifestEntry && ((newManifest != null) ||
944
(ename != null) || isMultiRelease)) {
945
foundManifest = true;
946
if (newManifest != null) {
947
// Don't read from the newManifest InputStream, as we
948
// might need it below, and we can't re-read the same data
949
// twice.
950
try (FileInputStream fis = new FileInputStream(mname)) {
951
if (isAmbiguousMainClass(new Manifest(fis))) {
952
return false;
953
}
954
}
955
}
956
// Update the manifest.
957
Manifest old = new Manifest(zis);
958
if (newManifest != null) {
959
old.read(newManifest);
960
}
961
if (!updateManifest(old, zos)) {
962
return false;
963
}
964
} else if (moduleInfos != null && isModuleInfoEntry) {
965
moduleInfos.putIfAbsent(name, zis.readAllBytes());
966
} else {
967
boolean isDir = e.isDirectory();
968
if (!entryMap.containsKey(name)) { // copy the old stuff
969
// do our own compression
970
ZipEntry e2 = new ZipEntry(name);
971
e2.setMethod(e.getMethod());
972
setZipEntryTime(e2, e.getTime());
973
e2.setComment(e.getComment());
974
e2.setExtra(e.getExtra());
975
if (e.getMethod() == ZipEntry.STORED) {
976
e2.setSize(e.getSize());
977
e2.setCrc(e.getCrc());
978
}
979
zos.putNextEntry(e2);
980
copy(zis, zos);
981
} else { // replace with the new files
982
Entry ent = entryMap.get(name);
983
addFile(zos, ent);
984
entryMap.remove(name);
985
entries.remove(ent);
986
isDir = ent.isDir;
987
}
988
if (!isDir) {
989
jentries.add(name);
990
}
991
}
992
}
993
994
// add the remaining new files
995
for (Entry entry : entries) {
996
addFile(zos, entry);
997
if (!entry.isDir) {
998
jentries.add(entry.name);
999
}
1000
}
1001
if (!foundManifest) {
1002
if (newManifest != null) {
1003
Manifest m = new Manifest(newManifest);
1004
updateOk = !isAmbiguousMainClass(m);
1005
if (updateOk) {
1006
if (!updateManifest(m, zos)) {
1007
updateOk = false;
1008
}
1009
}
1010
} else if (ename != null) {
1011
if (!updateManifest(new Manifest(), zos)) {
1012
updateOk = false;
1013
}
1014
}
1015
}
1016
if (updateOk) {
1017
if (moduleInfos != null && !moduleInfos.isEmpty()) {
1018
Set<String> pkgs = new HashSet<>();
1019
jentries.forEach( je -> addPackageIfNamed(pkgs, je));
1020
addExtendedModuleAttributes(moduleInfos, pkgs);
1021
updateOk = checkModuleInfo(moduleInfos.get(MODULE_INFO), jentries);
1022
updateModuleInfo(moduleInfos, zos);
1023
// TODO: check manifest main classes, etc
1024
} else if (moduleVersion != null || modulesToHash != null) {
1025
error(getMsg("error.module.options.without.info"));
1026
updateOk = false;
1027
}
1028
}
1029
zis.close();
1030
zos.close();
1031
return updateOk;
1032
}
1033
1034
private void addIndex(JarIndex index, ZipOutputStream zos)
1035
throws IOException
1036
{
1037
ZipEntry e = new ZipEntry(INDEX_NAME);
1038
setZipEntryTime(e);
1039
if (flag0) {
1040
CRC32OutputStream os = new CRC32OutputStream();
1041
index.write(os);
1042
os.updateEntry(e);
1043
}
1044
zos.putNextEntry(e);
1045
index.write(zos);
1046
zos.closeEntry();
1047
}
1048
1049
private void updateModuleInfo(Map<String,byte[]> moduleInfos, ZipOutputStream zos)
1050
throws IOException
1051
{
1052
String fmt = uflag ? "out.update.module-info": "out.added.module-info";
1053
for (Map.Entry<String,byte[]> mi : moduleInfos.entrySet()) {
1054
String name = mi.getKey();
1055
byte[] bytes = mi.getValue();
1056
ZipEntry e = new ZipEntry(name);
1057
setZipEntryTime(e);
1058
if (flag0) {
1059
crc32ModuleInfo(e, bytes);
1060
}
1061
zos.putNextEntry(e);
1062
zos.write(bytes);
1063
zos.closeEntry();
1064
if (vflag) {
1065
output(formatMsg(fmt, name));
1066
}
1067
}
1068
}
1069
1070
private boolean updateManifest(Manifest m, ZipOutputStream zos)
1071
throws IOException
1072
{
1073
addVersion(m);
1074
addCreatedBy(m);
1075
if (ename != null) {
1076
addMainClass(m, ename);
1077
}
1078
if (isMultiRelease) {
1079
addMultiRelease(m);
1080
}
1081
ZipEntry e = new ZipEntry(MANIFEST_NAME);
1082
setZipEntryTime(e);
1083
if (flag0) {
1084
crc32Manifest(e, m);
1085
}
1086
zos.putNextEntry(e);
1087
m.write(zos);
1088
if (vflag) {
1089
output(getMsg("out.update.manifest"));
1090
}
1091
return true;
1092
}
1093
1094
private static final boolean isWinDriveLetter(char c) {
1095
return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
1096
}
1097
1098
private String safeName(String name) {
1099
if (!pflag) {
1100
int len = name.length();
1101
int i = name.lastIndexOf("../");
1102
if (i == -1) {
1103
i = 0;
1104
} else {
1105
i += 3; // strip any dot-dot components
1106
}
1107
if (File.separatorChar == '\\') {
1108
// the spec requests no drive letter. skip if
1109
// the entry name has one.
1110
while (i < len) {
1111
int off = i;
1112
if (i + 1 < len &&
1113
name.charAt(i + 1) == ':' &&
1114
isWinDriveLetter(name.charAt(i))) {
1115
i += 2;
1116
}
1117
while (i < len && name.charAt(i) == '/') {
1118
i++;
1119
}
1120
if (i == off) {
1121
break;
1122
}
1123
}
1124
} else {
1125
while (i < len && name.charAt(i) == '/') {
1126
i++;
1127
}
1128
}
1129
if (i != 0) {
1130
name = name.substring(i);
1131
}
1132
}
1133
return name;
1134
}
1135
1136
private void addVersion(Manifest m) {
1137
Attributes global = m.getMainAttributes();
1138
if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
1139
global.put(Attributes.Name.MANIFEST_VERSION, VERSION);
1140
}
1141
}
1142
1143
private void addCreatedBy(Manifest m) {
1144
Attributes global = m.getMainAttributes();
1145
if (global.getValue(new Attributes.Name("Created-By")) == null) {
1146
String javaVendor = System.getProperty("java.vendor");
1147
String jdkVersion = System.getProperty("java.version");
1148
global.put(new Attributes.Name("Created-By"), jdkVersion + " (" +
1149
javaVendor + ")");
1150
}
1151
}
1152
1153
private void addMainClass(Manifest m, String mainApp) {
1154
Attributes global = m.getMainAttributes();
1155
1156
// overrides any existing Main-Class attribute
1157
global.put(Attributes.Name.MAIN_CLASS, mainApp);
1158
}
1159
1160
private void addMultiRelease(Manifest m) {
1161
Attributes global = m.getMainAttributes();
1162
global.put(Attributes.Name.MULTI_RELEASE, "true");
1163
}
1164
1165
private boolean isAmbiguousMainClass(Manifest m) {
1166
if (ename != null) {
1167
Attributes global = m.getMainAttributes();
1168
if ((global.get(Attributes.Name.MAIN_CLASS) != null)) {
1169
usageError(getMsg("error.bad.eflag"));
1170
return true;
1171
}
1172
}
1173
return false;
1174
}
1175
1176
/**
1177
* Adds a new file entry to the ZIP output stream.
1178
*/
1179
void addFile(ZipOutputStream zos, Entry entry) throws IOException {
1180
1181
File file = entry.file;
1182
String name = entry.name;
1183
boolean isDir = entry.isDir;
1184
1185
if (name.isEmpty() || name.equals(".") || name.equals(zname)) {
1186
return;
1187
} else if ((name.equals(MANIFEST_DIR) || name.equals(MANIFEST_NAME))
1188
&& !Mflag) {
1189
if (vflag) {
1190
output(formatMsg("out.ignore.entry", name));
1191
}
1192
return;
1193
} else if (name.equals(MODULE_INFO)) {
1194
throw new Error("Unexpected module info: " + name);
1195
}
1196
1197
long size = isDir ? 0 : file.length();
1198
1199
if (vflag) {
1200
out.print(formatMsg("out.adding", name));
1201
}
1202
ZipEntry e = new ZipEntry(name);
1203
setZipEntryTime(e, file.lastModified());
1204
if (size == 0) {
1205
e.setMethod(ZipEntry.STORED);
1206
e.setSize(0);
1207
e.setCrc(0);
1208
} else if (flag0) {
1209
crc32File(e, file);
1210
}
1211
zos.putNextEntry(e);
1212
if (!isDir) {
1213
copy(file, zos);
1214
}
1215
zos.closeEntry();
1216
/* report how much compression occurred. */
1217
if (vflag) {
1218
size = e.getSize();
1219
long csize = e.getCompressedSize();
1220
out.print(formatMsg2("out.size", String.valueOf(size),
1221
String.valueOf(csize)));
1222
if (e.getMethod() == ZipEntry.DEFLATED) {
1223
long ratio = 0;
1224
if (size != 0) {
1225
ratio = ((size - csize) * 100) / size;
1226
}
1227
output(formatMsg("out.deflated", String.valueOf(ratio)));
1228
} else {
1229
output(getMsg("out.stored"));
1230
}
1231
}
1232
}
1233
1234
/**
1235
* A buffer for use only by copy(InputStream, OutputStream).
1236
* Not as clean as allocating a new buffer as needed by copy,
1237
* but significantly more efficient.
1238
*/
1239
private byte[] copyBuf = new byte[8192];
1240
1241
/**
1242
* Copies all bytes from the input stream to the output stream.
1243
* Does not close or flush either stream.
1244
*
1245
* @param from the input stream to read from
1246
* @param to the output stream to write to
1247
* @throws IOException if an I/O error occurs
1248
*/
1249
private void copy(InputStream from, OutputStream to) throws IOException {
1250
int n;
1251
while ((n = from.read(copyBuf)) != -1)
1252
to.write(copyBuf, 0, n);
1253
}
1254
1255
/**
1256
* Copies all bytes from the input file to the output stream.
1257
* Does not close or flush the output stream.
1258
*
1259
* @param from the input file to read from
1260
* @param to the output stream to write to
1261
* @throws IOException if an I/O error occurs
1262
*/
1263
private void copy(File from, OutputStream to) throws IOException {
1264
try (InputStream in = new FileInputStream(from)) {
1265
copy(in, to);
1266
}
1267
}
1268
1269
/**
1270
* Copies all bytes from the input stream to the output file.
1271
* Does not close the input stream.
1272
*
1273
* @param from the input stream to read from
1274
* @param to the output file to write to
1275
* @throws IOException if an I/O error occurs
1276
*/
1277
private void copy(InputStream from, File to) throws IOException {
1278
try (OutputStream out = new FileOutputStream(to)) {
1279
copy(from, out);
1280
}
1281
}
1282
1283
/**
1284
* Computes the crc32 of a module-info.class. This is necessary when the
1285
* ZipOutputStream is in STORED mode.
1286
*/
1287
private void crc32ModuleInfo(ZipEntry e, byte[] bytes) throws IOException {
1288
CRC32OutputStream os = new CRC32OutputStream();
1289
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
1290
in.transferTo(os);
1291
os.updateEntry(e);
1292
}
1293
1294
/**
1295
* Computes the crc32 of a Manifest. This is necessary when the
1296
* ZipOutputStream is in STORED mode.
1297
*/
1298
private void crc32Manifest(ZipEntry e, Manifest m) throws IOException {
1299
CRC32OutputStream os = new CRC32OutputStream();
1300
m.write(os);
1301
os.updateEntry(e);
1302
}
1303
1304
/**
1305
* Computes the crc32 of a File. This is necessary when the
1306
* ZipOutputStream is in STORED mode.
1307
*/
1308
private void crc32File(ZipEntry e, File f) throws IOException {
1309
CRC32OutputStream os = new CRC32OutputStream();
1310
copy(f, os);
1311
if (os.n != f.length()) {
1312
throw new JarException(formatMsg(
1313
"error.incorrect.length", f.getPath()));
1314
}
1315
os.updateEntry(e);
1316
}
1317
1318
void replaceFSC(Map<Integer, String []> filesMap) {
1319
filesMap.keySet().forEach(version -> {
1320
String[] files = filesMap.get(version);
1321
if (files != null) {
1322
for (int i = 0; i < files.length; i++) {
1323
files[i] = files[i].replace(File.separatorChar, '/');
1324
}
1325
}
1326
});
1327
}
1328
1329
@SuppressWarnings("serial")
1330
Set<ZipEntry> newDirSet() {
1331
return new HashSet<ZipEntry>() {
1332
public boolean add(ZipEntry e) {
1333
return ((e == null || useExtractionTime) ? false : super.add(e));
1334
}};
1335
}
1336
1337
void updateLastModifiedTime(Set<ZipEntry> zes) throws IOException {
1338
for (ZipEntry ze : zes) {
1339
long lastModified = ze.getTime();
1340
if (lastModified != -1) {
1341
String name = safeName(ze.getName().replace(File.separatorChar, '/'));
1342
if (name.length() != 0) {
1343
File f = new File(name.replace('/', File.separatorChar));
1344
f.setLastModified(lastModified);
1345
}
1346
}
1347
}
1348
}
1349
1350
/**
1351
* Extracts specified entries from JAR file.
1352
*
1353
* @return whether entries were found and successfully extracted
1354
* (indicating this was a zip file without "leading garbage")
1355
*/
1356
boolean extract(InputStream in, String files[]) throws IOException {
1357
ZipInputStream zis = new ZipInputStream(in);
1358
ZipEntry e;
1359
// Set of all directory entries specified in archive. Disallows
1360
// null entries. Disallows all entries if using pre-6.0 behavior.
1361
boolean entriesFound = false;
1362
Set<ZipEntry> dirs = newDirSet();
1363
while ((e = zis.getNextEntry()) != null) {
1364
entriesFound = true;
1365
if (files == null) {
1366
dirs.add(extractFile(zis, e));
1367
} else {
1368
String name = e.getName();
1369
for (String file : files) {
1370
if (name.startsWith(file)) {
1371
dirs.add(extractFile(zis, e));
1372
break;
1373
}
1374
}
1375
}
1376
}
1377
1378
// Update timestamps of directories specified in archive with their
1379
// timestamps as given in the archive. We do this after extraction,
1380
// instead of during, because creating a file in a directory changes
1381
// that directory's timestamp.
1382
updateLastModifiedTime(dirs);
1383
1384
return entriesFound;
1385
}
1386
1387
/**
1388
* Extracts specified entries from JAR file, via ZipFile.
1389
*/
1390
void extract(String fname, String files[]) throws IOException {
1391
ZipFile zf = new ZipFile(fname);
1392
Set<ZipEntry> dirs = newDirSet();
1393
Enumeration<? extends ZipEntry> zes = zf.entries();
1394
while (zes.hasMoreElements()) {
1395
ZipEntry e = zes.nextElement();
1396
if (files == null) {
1397
dirs.add(extractFile(zf.getInputStream(e), e));
1398
} else {
1399
String name = e.getName();
1400
for (String file : files) {
1401
if (name.startsWith(file)) {
1402
dirs.add(extractFile(zf.getInputStream(e), e));
1403
break;
1404
}
1405
}
1406
}
1407
}
1408
zf.close();
1409
updateLastModifiedTime(dirs);
1410
}
1411
1412
/**
1413
* Extracts next entry from JAR file, creating directories as needed. If
1414
* the entry is for a directory which doesn't exist prior to this
1415
* invocation, returns that entry, otherwise returns null.
1416
*/
1417
ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException {
1418
ZipEntry rc = null;
1419
// The spec requres all slashes MUST be forward '/', it is possible
1420
// an offending zip/jar entry may uses the backwards slash in its
1421
// name. It might cause problem on Windows platform as it skips
1422
// our "safe" check for leading slahs and dot-dot. So replace them
1423
// with '/'.
1424
String name = safeName(e.getName().replace(File.separatorChar, '/'));
1425
if (name.length() == 0) {
1426
return rc; // leading '/' or 'dot-dot' only path
1427
}
1428
File f = new File(name.replace('/', File.separatorChar));
1429
if (e.isDirectory()) {
1430
if (f.exists()) {
1431
if (!f.isDirectory()) {
1432
throw new IOException(formatMsg("error.create.dir",
1433
f.getPath()));
1434
}
1435
} else {
1436
if (!f.mkdirs()) {
1437
throw new IOException(formatMsg("error.create.dir",
1438
f.getPath()));
1439
} else {
1440
rc = e;
1441
}
1442
}
1443
1444
if (vflag) {
1445
output(formatMsg("out.create", name));
1446
}
1447
} else {
1448
if (f.getParent() != null) {
1449
File d = new File(f.getParent());
1450
if (!d.exists() && !d.mkdirs() || !d.isDirectory()) {
1451
throw new IOException(formatMsg(
1452
"error.create.dir", d.getPath()));
1453
}
1454
}
1455
try {
1456
copy(is, f);
1457
} finally {
1458
if (is instanceof ZipInputStream)
1459
((ZipInputStream)is).closeEntry();
1460
else
1461
is.close();
1462
}
1463
if (vflag) {
1464
if (e.getMethod() == ZipEntry.DEFLATED) {
1465
output(formatMsg("out.inflated", name));
1466
} else {
1467
output(formatMsg("out.extracted", name));
1468
}
1469
}
1470
}
1471
if (!useExtractionTime) {
1472
long lastModified = e.getTime();
1473
if (lastModified != -1) {
1474
f.setLastModified(lastModified);
1475
}
1476
}
1477
return rc;
1478
}
1479
1480
/**
1481
* Lists contents of JAR file.
1482
*/
1483
void list(InputStream in, String files[]) throws IOException {
1484
ZipInputStream zis = new ZipInputStream(in);
1485
ZipEntry e;
1486
while ((e = zis.getNextEntry()) != null) {
1487
/*
1488
* In the case of a compressed (deflated) entry, the entry size
1489
* is stored immediately following the entry data and cannot be
1490
* determined until the entry is fully read. Therefore, we close
1491
* the entry first before printing out its attributes.
1492
*/
1493
zis.closeEntry();
1494
printEntry(e, files);
1495
}
1496
}
1497
1498
/**
1499
* Lists contents of JAR file, via ZipFile.
1500
*/
1501
void list(String fname, String files[]) throws IOException {
1502
ZipFile zf = new ZipFile(fname);
1503
Enumeration<? extends ZipEntry> zes = zf.entries();
1504
while (zes.hasMoreElements()) {
1505
printEntry(zes.nextElement(), files);
1506
}
1507
zf.close();
1508
}
1509
1510
/**
1511
* Outputs the class index table to the INDEX.LIST file of the
1512
* root jar file.
1513
*/
1514
void dumpIndex(String rootjar, JarIndex index) throws IOException {
1515
File jarFile = new File(rootjar);
1516
Path jarPath = jarFile.toPath();
1517
Path tmpPath = createTempFileInSameDirectoryAs(jarFile).toPath();
1518
try {
1519
if (update(Files.newInputStream(jarPath),
1520
Files.newOutputStream(tmpPath),
1521
null, null, index)) {
1522
try {
1523
Files.move(tmpPath, jarPath, REPLACE_EXISTING);
1524
} catch (IOException e) {
1525
throw new IOException(getMsg("error.write.file"), e);
1526
}
1527
}
1528
} finally {
1529
Files.deleteIfExists(tmpPath);
1530
}
1531
}
1532
1533
private HashSet<String> jarPaths = new HashSet<String>();
1534
1535
/**
1536
* Generates the transitive closure of the Class-Path attribute for
1537
* the specified jar file.
1538
*/
1539
List<String> getJarPath(String jar) throws IOException {
1540
List<String> files = new ArrayList<String>();
1541
files.add(jar);
1542
jarPaths.add(jar);
1543
1544
// take out the current path
1545
String path = jar.substring(0, Math.max(0, jar.lastIndexOf('/') + 1));
1546
1547
// class path attribute will give us jar file name with
1548
// '/' as separators, so we need to change them to the
1549
// appropriate one before we open the jar file.
1550
JarFile rf = new JarFile(jar.replace('/', File.separatorChar));
1551
1552
if (rf != null) {
1553
Manifest man = rf.getManifest();
1554
if (man != null) {
1555
Attributes attr = man.getMainAttributes();
1556
if (attr != null) {
1557
String value = attr.getValue(Attributes.Name.CLASS_PATH);
1558
if (value != null) {
1559
StringTokenizer st = new StringTokenizer(value);
1560
while (st.hasMoreTokens()) {
1561
String ajar = st.nextToken();
1562
if (!ajar.endsWith("/")) { // it is a jar file
1563
ajar = path.concat(ajar);
1564
/* check on cyclic dependency */
1565
if (! jarPaths.contains(ajar)) {
1566
files.addAll(getJarPath(ajar));
1567
}
1568
}
1569
}
1570
}
1571
}
1572
}
1573
}
1574
rf.close();
1575
return files;
1576
}
1577
1578
/**
1579
* Generates class index file for the specified root jar file.
1580
*/
1581
void genIndex(String rootjar, String[] files) throws IOException {
1582
List<String> jars = getJarPath(rootjar);
1583
int njars = jars.size();
1584
String[] jarfiles;
1585
1586
if (njars == 1 && files != null) {
1587
// no class-path attribute defined in rootjar, will
1588
// use command line specified list of jars
1589
for (int i = 0; i < files.length; i++) {
1590
jars.addAll(getJarPath(files[i]));
1591
}
1592
njars = jars.size();
1593
}
1594
jarfiles = jars.toArray(new String[njars]);
1595
JarIndex index = new JarIndex(jarfiles);
1596
dumpIndex(rootjar, index);
1597
}
1598
1599
/**
1600
* Prints entry information, if requested.
1601
*/
1602
void printEntry(ZipEntry e, String[] files) throws IOException {
1603
if (files == null) {
1604
printEntry(e);
1605
} else {
1606
String name = e.getName();
1607
for (String file : files) {
1608
if (name.startsWith(file)) {
1609
printEntry(e);
1610
return;
1611
}
1612
}
1613
}
1614
}
1615
1616
/**
1617
* Prints entry information.
1618
*/
1619
void printEntry(ZipEntry e) throws IOException {
1620
if (vflag) {
1621
StringBuilder sb = new StringBuilder();
1622
String s = Long.toString(e.getSize());
1623
for (int i = 6 - s.length(); i > 0; --i) {
1624
sb.append(' ');
1625
}
1626
sb.append(s).append(' ').append(new Date(e.getTime()).toString());
1627
sb.append(' ').append(e.getName());
1628
output(sb.toString());
1629
} else {
1630
output(e.getName());
1631
}
1632
}
1633
1634
/**
1635
* Prints usage message.
1636
*/
1637
void usageError(String s) {
1638
err.println(s);
1639
err.println(getMsg("main.usage.summary.try"));
1640
}
1641
1642
/**
1643
* A fatal exception has been caught. No recovery possible
1644
*/
1645
void fatalError(Exception e) {
1646
e.printStackTrace();
1647
}
1648
1649
/**
1650
* A fatal condition has been detected; message is "s".
1651
* No recovery possible
1652
*/
1653
void fatalError(String s) {
1654
error(program + ": " + s);
1655
}
1656
1657
/**
1658
* Print an output message; like verbose output and the like
1659
*/
1660
protected void output(String s) {
1661
out.println(s);
1662
}
1663
1664
/**
1665
* Print an error message; like something is broken
1666
*/
1667
void error(String s) {
1668
err.println(s);
1669
}
1670
1671
/**
1672
* Print a warning message
1673
*/
1674
void warn(String s) {
1675
err.println(s);
1676
}
1677
1678
/**
1679
* Main routine to start program.
1680
*/
1681
public static void main(String args[]) {
1682
Main jartool = new Main(System.out, System.err, "jar");
1683
System.exit(jartool.run(args) ? 0 : 1);
1684
}
1685
1686
/**
1687
* An OutputStream that doesn't send its output anywhere, (but could).
1688
* It's here to find the CRC32 of an input file, necessary for STORED
1689
* mode in ZIP.
1690
*/
1691
private static class CRC32OutputStream extends java.io.OutputStream {
1692
final CRC32 crc = new CRC32();
1693
long n = 0;
1694
1695
CRC32OutputStream() {}
1696
1697
public void write(int r) throws IOException {
1698
crc.update(r);
1699
n++;
1700
}
1701
1702
public void write(byte[] b, int off, int len) throws IOException {
1703
crc.update(b, off, len);
1704
n += len;
1705
}
1706
1707
/**
1708
* Updates a ZipEntry which describes the data read by this
1709
* output stream, in STORED mode.
1710
*/
1711
public void updateEntry(ZipEntry e) {
1712
e.setMethod(ZipEntry.STORED);
1713
e.setSize(n);
1714
e.setCrc(crc.getValue());
1715
}
1716
}
1717
1718
/**
1719
* Attempt to create temporary file in the system-provided temporary folder, if failed attempts
1720
* to create it in the same folder as the file in parameter (if any)
1721
*/
1722
private File createTemporaryFile(String tmpbase, String suffix) {
1723
File tmpfile = null;
1724
1725
try {
1726
tmpfile = File.createTempFile(tmpbase, suffix);
1727
} catch (IOException | SecurityException e) {
1728
// Unable to create file due to permission violation or security exception
1729
}
1730
if (tmpfile == null) {
1731
// Were unable to create temporary file, fall back to temporary file in the same folder
1732
if (fname != null) {
1733
try {
1734
File tmpfolder = new File(fname).getAbsoluteFile().getParentFile();
1735
tmpfile = File.createTempFile(fname, ".tmp" + suffix, tmpfolder);
1736
} catch (IOException ioe) {
1737
// Last option failed - fall gracefully
1738
fatalError(ioe);
1739
}
1740
} else {
1741
// No options left - we can not compress to stdout without access to the temporary folder
1742
fatalError(new IOException(getMsg("error.create.tempfile")));
1743
}
1744
}
1745
return tmpfile;
1746
}
1747
1748
// Modular jar support
1749
1750
/**
1751
* Associates a module descriptor's zip entry name along with its
1752
* bytes and an optional URI. Used when describing modules.
1753
*/
1754
interface ModuleInfoEntry {
1755
String name();
1756
Optional<String> uriString();
1757
InputStream bytes() throws IOException;
1758
}
1759
1760
static class ZipFileModuleInfoEntry implements ModuleInfoEntry {
1761
private final ZipFile zipFile;
1762
private final ZipEntry entry;
1763
ZipFileModuleInfoEntry(ZipFile zipFile, ZipEntry entry) {
1764
this.zipFile = zipFile;
1765
this.entry = entry;
1766
}
1767
@Override public String name() { return entry.getName(); }
1768
@Override public InputStream bytes() throws IOException {
1769
return zipFile.getInputStream(entry);
1770
}
1771
/** Returns an optional containing the effective URI. */
1772
@Override public Optional<String> uriString() {
1773
String uri = (Paths.get(zipFile.getName())).toUri().toString();
1774
uri = "jar:" + uri + "!/" + entry.getName();
1775
return Optional.of(uri);
1776
}
1777
}
1778
1779
static class StreamedModuleInfoEntry implements ModuleInfoEntry {
1780
private final String name;
1781
private final byte[] bytes;
1782
StreamedModuleInfoEntry(String name, byte[] bytes) {
1783
this.name = name;
1784
this.bytes = bytes;
1785
}
1786
@Override public String name() { return name; }
1787
@Override public InputStream bytes() throws IOException {
1788
return new ByteArrayInputStream(bytes);
1789
}
1790
/** Returns an empty optional. */
1791
@Override public Optional<String> uriString() {
1792
return Optional.empty(); // no URI can be derived
1793
}
1794
}
1795
1796
/** Describes a module from a given zip file. */
1797
private boolean describeModule(ZipFile zipFile) throws IOException {
1798
ZipFileModuleInfoEntry[] infos = zipFile.stream()
1799
.filter(e -> isModuleInfoEntry(e.getName()))
1800
.sorted(ENTRY_COMPARATOR)
1801
.map(e -> new ZipFileModuleInfoEntry(zipFile, e))
1802
.toArray(ZipFileModuleInfoEntry[]::new);
1803
1804
if (infos.length == 0) {
1805
// No module descriptor found, derive and describe the automatic module
1806
String fn = zipFile.getName();
1807
ModuleFinder mf = ModuleFinder.of(Paths.get(fn));
1808
try {
1809
Set<ModuleReference> mref = mf.findAll();
1810
if (mref.isEmpty()) {
1811
output(formatMsg("error.unable.derive.automodule", fn));
1812
return true;
1813
}
1814
ModuleDescriptor md = mref.iterator().next().descriptor();
1815
output(getMsg("out.automodule") + "\n");
1816
describeModule(md, null, null, "");
1817
} catch (FindException e) {
1818
String msg = formatMsg("error.unable.derive.automodule", fn);
1819
Throwable t = e.getCause();
1820
if (t != null)
1821
msg = msg + "\n" + t.getMessage();
1822
output(msg);
1823
}
1824
} else {
1825
return describeModuleFromEntries(infos);
1826
}
1827
return true;
1828
}
1829
1830
private boolean describeModuleFromStream(FileInputStream fis)
1831
throws IOException
1832
{
1833
List<ModuleInfoEntry> infos = new LinkedList<>();
1834
1835
try (BufferedInputStream bis = new BufferedInputStream(fis);
1836
ZipInputStream zis = new ZipInputStream(bis)) {
1837
ZipEntry e;
1838
while ((e = zis.getNextEntry()) != null) {
1839
String ename = e.getName();
1840
if (isModuleInfoEntry(ename)) {
1841
infos.add(new StreamedModuleInfoEntry(ename, zis.readAllBytes()));
1842
}
1843
}
1844
}
1845
1846
if (infos.size() == 0)
1847
return false;
1848
1849
ModuleInfoEntry[] sorted = infos.stream()
1850
.sorted(Comparator.comparing(ModuleInfoEntry::name, ENTRYNAME_COMPARATOR))
1851
.toArray(ModuleInfoEntry[]::new);
1852
1853
return describeModuleFromEntries(sorted);
1854
}
1855
1856
private boolean lessThanEqualReleaseValue(ModuleInfoEntry entry) {
1857
return intVersionFromEntry(entry) <= releaseValue ? true : false;
1858
}
1859
1860
private static String versionFromEntryName(String name) {
1861
String s = name.substring(VERSIONS_DIR_LENGTH);
1862
return s.substring(0, s.indexOf("/"));
1863
}
1864
1865
private static int intVersionFromEntry(ModuleInfoEntry entry) {
1866
String name = entry.name();
1867
if (!name.startsWith(VERSIONS_DIR))
1868
return BASE_VERSION;
1869
1870
String s = name.substring(VERSIONS_DIR_LENGTH);
1871
s = s.substring(0, s.indexOf('/'));
1872
return Integer.valueOf(s);
1873
}
1874
1875
/**
1876
* Describes a single module descriptor, determined by the specified
1877
* --release, if any, from the given ordered entries.
1878
* The given infos must be ordered as per ENTRY_COMPARATOR.
1879
*/
1880
private boolean describeModuleFromEntries(ModuleInfoEntry[] infos)
1881
throws IOException
1882
{
1883
assert infos.length > 0;
1884
1885
// Informative: output all non-root descriptors, if any
1886
String releases = Arrays.stream(infos)
1887
.filter(e -> !e.name().equals(MODULE_INFO))
1888
.map(ModuleInfoEntry::name)
1889
.map(Main::versionFromEntryName)
1890
.collect(joining(" "));
1891
if (!releases.isEmpty())
1892
output("releases: " + releases + "\n");
1893
1894
// Describe the operative descriptor for the specified --release, if any
1895
if (releaseValue != -1) {
1896
ModuleInfoEntry entry = null;
1897
int i = 0;
1898
while (i < infos.length && lessThanEqualReleaseValue(infos[i])) {
1899
entry = infos[i];
1900
i++;
1901
}
1902
1903
if (entry == null) {
1904
output(formatMsg("error.no.operative.descriptor",
1905
String.valueOf(releaseValue)));
1906
return false;
1907
}
1908
1909
String uriString = entry.uriString().orElse("");
1910
try (InputStream is = entry.bytes()) {
1911
describeModule(is, uriString);
1912
}
1913
} else {
1914
// no specific --release specified, output the root, if any
1915
if (infos[0].name().equals(MODULE_INFO)) {
1916
String uriString = infos[0].uriString().orElse("");
1917
try (InputStream is = infos[0].bytes()) {
1918
describeModule(is, uriString);
1919
}
1920
} else {
1921
// no root, output message to specify --release
1922
output(getMsg("error.no.root.descriptor"));
1923
}
1924
}
1925
return true;
1926
}
1927
1928
static <T> String toLowerCaseString(Collection<T> set) {
1929
if (set.isEmpty()) { return ""; }
1930
return " " + set.stream().map(e -> e.toString().toLowerCase(Locale.ROOT))
1931
.sorted().collect(joining(" "));
1932
}
1933
1934
static <T> String toString(Collection<T> set) {
1935
if (set.isEmpty()) { return ""; }
1936
return " " + set.stream().map(e -> e.toString()).sorted().collect(joining(" "));
1937
}
1938
1939
private void describeModule(InputStream entryInputStream, String uriString)
1940
throws IOException
1941
{
1942
ModuleInfo.Attributes attrs = ModuleInfo.read(entryInputStream, null);
1943
ModuleDescriptor md = attrs.descriptor();
1944
ModuleTarget target = attrs.target();
1945
ModuleHashes hashes = attrs.recordedHashes();
1946
1947
describeModule(md, target, hashes, uriString);
1948
}
1949
1950
private void describeModule(ModuleDescriptor md,
1951
ModuleTarget target,
1952
ModuleHashes hashes,
1953
String uriString)
1954
throws IOException
1955
{
1956
StringBuilder sb = new StringBuilder();
1957
1958
sb.append(md.toNameAndVersion());
1959
1960
if (!uriString.isEmpty())
1961
sb.append(" ").append(uriString);
1962
if (md.isOpen())
1963
sb.append(" open");
1964
if (md.isAutomatic())
1965
sb.append(" automatic");
1966
sb.append("\n");
1967
1968
// unqualified exports (sorted by package)
1969
md.exports().stream()
1970
.sorted(Comparator.comparing(Exports::source))
1971
.filter(e -> !e.isQualified())
1972
.forEach(e -> sb.append("exports ").append(e.source())
1973
.append(toLowerCaseString(e.modifiers()))
1974
.append("\n"));
1975
1976
// dependences
1977
md.requires().stream().sorted()
1978
.forEach(r -> sb.append("requires ").append(r.name())
1979
.append(toLowerCaseString(r.modifiers()))
1980
.append("\n"));
1981
1982
// service use and provides
1983
md.uses().stream().sorted()
1984
.forEach(s -> sb.append("uses ").append(s).append("\n"));
1985
1986
md.provides().stream()
1987
.sorted(Comparator.comparing(Provides::service))
1988
.forEach(p -> sb.append("provides ").append(p.service())
1989
.append(" with")
1990
.append(toString(p.providers()))
1991
.append("\n"));
1992
1993
// qualified exports
1994
md.exports().stream()
1995
.sorted(Comparator.comparing(Exports::source))
1996
.filter(Exports::isQualified)
1997
.forEach(e -> sb.append("qualified exports ").append(e.source())
1998
.append(" to").append(toLowerCaseString(e.targets()))
1999
.append("\n"));
2000
2001
// open packages
2002
md.opens().stream()
2003
.sorted(Comparator.comparing(Opens::source))
2004
.filter(o -> !o.isQualified())
2005
.forEach(o -> sb.append("opens ").append(o.source())
2006
.append(toLowerCaseString(o.modifiers()))
2007
.append("\n"));
2008
2009
md.opens().stream()
2010
.sorted(Comparator.comparing(Opens::source))
2011
.filter(Opens::isQualified)
2012
.forEach(o -> sb.append("qualified opens ").append(o.source())
2013
.append(toLowerCaseString(o.modifiers()))
2014
.append(" to").append(toLowerCaseString(o.targets()))
2015
.append("\n"));
2016
2017
// non-exported/non-open packages
2018
Set<String> concealed = new TreeSet<>(md.packages());
2019
md.exports().stream().map(Exports::source).forEach(concealed::remove);
2020
md.opens().stream().map(Opens::source).forEach(concealed::remove);
2021
concealed.forEach(p -> sb.append("contains ").append(p).append("\n"));
2022
2023
md.mainClass().ifPresent(v -> sb.append("main-class ").append(v).append("\n"));
2024
2025
if (target != null) {
2026
String targetPlatform = target.targetPlatform();
2027
if (!targetPlatform.isEmpty())
2028
sb.append("platform ").append(targetPlatform).append("\n");
2029
}
2030
2031
if (hashes != null) {
2032
hashes.names().stream().sorted().forEach(
2033
mod -> sb.append("hashes ").append(mod).append(" ")
2034
.append(hashes.algorithm()).append(" ")
2035
.append(toHex(hashes.hashFor(mod)))
2036
.append("\n"));
2037
}
2038
2039
output(sb.toString());
2040
}
2041
2042
private static String toHex(byte[] ba) {
2043
StringBuilder sb = new StringBuilder(ba.length << 1);
2044
for (byte b: ba) {
2045
sb.append(String.format("%02x", b & 0xff));
2046
}
2047
return sb.toString();
2048
}
2049
2050
static String toBinaryName(String classname) {
2051
return (classname.replace('.', '/')) + ".class";
2052
}
2053
2054
private boolean checkModuleInfo(byte[] moduleInfoBytes, Set<String> entries)
2055
throws IOException
2056
{
2057
boolean ok = true;
2058
if (moduleInfoBytes != null) { // no root module-info.class if null
2059
try {
2060
// ModuleDescriptor.read() checks open/exported pkgs vs packages
2061
ModuleDescriptor md = ModuleDescriptor.read(ByteBuffer.wrap(moduleInfoBytes));
2062
// A module must have the implementation class of the services it 'provides'.
2063
if (md.provides().stream().map(Provides::providers).flatMap(List::stream)
2064
.filter(p -> !entries.contains(toBinaryName(p)))
2065
.peek(p -> fatalError(formatMsg("error.missing.provider", p)))
2066
.count() != 0) {
2067
ok = false;
2068
}
2069
} catch (InvalidModuleDescriptorException x) {
2070
fatalError(x.getMessage());
2071
ok = false;
2072
}
2073
}
2074
return ok;
2075
}
2076
2077
/**
2078
* Adds extended modules attributes to the given module-info's. The given
2079
* Map values are updated in-place. Returns false if an error occurs.
2080
*/
2081
private void addExtendedModuleAttributes(Map<String,byte[]> moduleInfos,
2082
Set<String> packages)
2083
throws IOException
2084
{
2085
for (Map.Entry<String,byte[]> e: moduleInfos.entrySet()) {
2086
ModuleDescriptor md = ModuleDescriptor.read(ByteBuffer.wrap(e.getValue()));
2087
e.setValue(extendedInfoBytes(md, e.getValue(), packages));
2088
}
2089
}
2090
2091
static boolean isModuleInfoEntry(String name) {
2092
// root or versioned module-info.class
2093
if (name.endsWith(MODULE_INFO)) {
2094
int end = name.length() - MODULE_INFO.length();
2095
if (end == 0)
2096
return true;
2097
if (name.startsWith(VERSIONS_DIR)) {
2098
int off = VERSIONS_DIR_LENGTH;
2099
if (off == end) // meta-inf/versions/module-info.class
2100
return false;
2101
while (off < end - 1) {
2102
char c = name.charAt(off++);
2103
if (c < '0' || c > '9')
2104
return false;
2105
}
2106
return name.charAt(off) == '/';
2107
}
2108
}
2109
return false;
2110
}
2111
2112
/**
2113
* Returns a byte array containing the given module-info.class plus any
2114
* extended attributes.
2115
*
2116
* If --module-version, --main-class, or other options were provided
2117
* then the corresponding class file attributes are added to the
2118
* module-info here.
2119
*/
2120
private byte[] extendedInfoBytes(ModuleDescriptor md,
2121
byte[] miBytes,
2122
Set<String> packages)
2123
throws IOException
2124
{
2125
ByteArrayOutputStream baos = new ByteArrayOutputStream();
2126
InputStream is = new ByteArrayInputStream(miBytes);
2127
ModuleInfoExtender extender = ModuleInfoExtender.newExtender(is);
2128
2129
// Add (or replace) the Packages attribute
2130
extender.packages(packages);
2131
2132
// --main-class
2133
if (ename != null)
2134
extender.mainClass(ename);
2135
2136
// --module-version
2137
if (moduleVersion != null)
2138
extender.version(moduleVersion);
2139
2140
// --hash-modules
2141
if (modulesToHash != null) {
2142
String mn = md.name();
2143
Hasher hasher = new Hasher(md, fname);
2144
ModuleHashes moduleHashes = hasher.computeHashes(mn);
2145
if (moduleHashes != null) {
2146
extender.hashes(moduleHashes);
2147
} else {
2148
warn("warning: no module is recorded in hash in " + mn);
2149
}
2150
}
2151
2152
if (moduleResolution.value() != 0) {
2153
extender.moduleResolution(moduleResolution);
2154
}
2155
2156
extender.write(baos);
2157
return baos.toByteArray();
2158
}
2159
2160
/**
2161
* Compute and record hashes
2162
*/
2163
private class Hasher {
2164
final ModuleHashesBuilder hashesBuilder;
2165
final ModuleFinder finder;
2166
final Set<String> modules;
2167
Hasher(ModuleDescriptor descriptor, String fname) throws IOException {
2168
// Create a module finder that finds the modular JAR
2169
// being created/updated
2170
URI uri = Paths.get(fname).toUri();
2171
ModuleReference mref = new ModuleReference(descriptor, uri) {
2172
@Override
2173
public ModuleReader open() {
2174
throw new UnsupportedOperationException("should not reach here");
2175
}
2176
};
2177
2178
// Compose a module finder with the module path and
2179
// the modular JAR being created or updated
2180
this.finder = ModuleFinder.compose(moduleFinder,
2181
new ModuleFinder() {
2182
@Override
2183
public Optional<ModuleReference> find(String name) {
2184
if (descriptor.name().equals(name))
2185
return Optional.of(mref);
2186
else
2187
return Optional.empty();
2188
}
2189
2190
@Override
2191
public Set<ModuleReference> findAll() {
2192
return Collections.singleton(mref);
2193
}
2194
});
2195
2196
// Determine the modules that matches the pattern {@code modulesToHash}
2197
Set<String> roots = finder.findAll().stream()
2198
.map(ref -> ref.descriptor().name())
2199
.filter(mn -> modulesToHash.matcher(mn).find())
2200
.collect(Collectors.toSet());
2201
2202
// use system module path unless it creates a modular JAR for
2203
// a module that is present in the system image e.g. upgradeable
2204
// module
2205
ModuleFinder system;
2206
String name = descriptor.name();
2207
if (name != null && ModuleFinder.ofSystem().find(name).isPresent()) {
2208
system = ModuleFinder.of();
2209
} else {
2210
system = ModuleFinder.ofSystem();
2211
}
2212
// get a resolved module graph
2213
Configuration config =
2214
Configuration.empty().resolve(system, finder, roots);
2215
2216
// filter modules resolved from the system module finder
2217
this.modules = config.modules().stream()
2218
.map(ResolvedModule::name)
2219
.filter(mn -> roots.contains(mn) && !system.find(mn).isPresent())
2220
.collect(Collectors.toSet());
2221
2222
this.hashesBuilder = new ModuleHashesBuilder(config, modules);
2223
}
2224
2225
/**
2226
* Compute hashes of the specified module.
2227
*
2228
* It records the hashing modules that depend upon the specified
2229
* module directly or indirectly.
2230
*/
2231
ModuleHashes computeHashes(String name) {
2232
if (hashesBuilder == null)
2233
return null;
2234
2235
return hashesBuilder.computeHashes(Set.of(name)).get(name);
2236
}
2237
}
2238
2239
// sort base entries before versioned entries, and sort entry classes with
2240
// nested classes so that the outter class appears before the associated
2241
// nested class
2242
static Comparator<String> ENTRYNAME_COMPARATOR = (s1, s2) -> {
2243
2244
if (s1.equals(s2)) return 0;
2245
boolean b1 = s1.startsWith(VERSIONS_DIR);
2246
boolean b2 = s2.startsWith(VERSIONS_DIR);
2247
if (b1 && !b2) return 1;
2248
if (!b1 && b2) return -1;
2249
int n = 0; // starting char for String compare
2250
if (b1 && b2) {
2251
// normally strings would be sorted so "10" goes before "9", but
2252
// version number strings need to be sorted numerically
2253
n = VERSIONS_DIR.length(); // skip the common prefix
2254
int i1 = s1.indexOf('/', n);
2255
int i2 = s2.indexOf('/', n);
2256
if (i1 == -1) throw new Validator.InvalidJarException(s1);
2257
if (i2 == -1) throw new Validator.InvalidJarException(s2);
2258
// shorter version numbers go first
2259
if (i1 != i2) return i1 - i2;
2260
// otherwise, handle equal length numbers below
2261
}
2262
int l1 = s1.length();
2263
int l2 = s2.length();
2264
int lim = Math.min(l1, l2);
2265
for (int k = n; k < lim; k++) {
2266
char c1 = s1.charAt(k);
2267
char c2 = s2.charAt(k);
2268
if (c1 != c2) {
2269
// change natural ordering so '.' comes before '$'
2270
// i.e. outer classes come before nested classes
2271
if (c1 == '$' && c2 == '.') return 1;
2272
if (c1 == '.' && c2 == '$') return -1;
2273
return c1 - c2;
2274
}
2275
}
2276
return l1 - l2;
2277
};
2278
2279
static Comparator<ZipEntry> ENTRY_COMPARATOR =
2280
Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR);
2281
2282
// Set the ZipEntry dostime using date if specified otherwise the current time
2283
private void setZipEntryTime(ZipEntry e) {
2284
setZipEntryTime(e, System.currentTimeMillis());
2285
}
2286
2287
// Set the ZipEntry dostime using the date if specified
2288
// otherwise the original time
2289
private void setZipEntryTime(ZipEntry e, long origTime) {
2290
if (date != null) {
2291
e.setTimeLocal(date);
2292
} else {
2293
e.setTime(origTime);
2294
}
2295
}
2296
}
2297
2298