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