Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeprscan/Main.java
41161 views
1
/*
2
* Copyright (c) 2016, 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 com.sun.tools.jdeprscan;
27
28
import java.io.File;
29
import java.io.IOException;
30
import java.io.PrintStream;
31
import java.net.URI;
32
import java.nio.charset.StandardCharsets;
33
import java.nio.file.Files;
34
import java.nio.file.FileSystems;
35
import java.nio.file.Path;
36
import java.nio.file.Paths;
37
import java.util.ArrayDeque;
38
import java.util.ArrayList;
39
import java.util.Arrays;
40
import java.util.Collection;
41
import java.util.EnumSet;
42
import java.util.HashSet;
43
import java.util.List;
44
import java.util.Map;
45
import java.util.NoSuchElementException;
46
import java.util.Set;
47
import java.util.Queue;
48
import java.util.stream.Collectors;
49
import java.util.stream.IntStream;
50
import java.util.stream.Stream;
51
import java.util.stream.StreamSupport;
52
import java.util.jar.JarEntry;
53
import java.util.jar.JarFile;
54
55
import javax.tools.Diagnostic;
56
import javax.tools.DiagnosticListener;
57
import javax.tools.JavaCompiler;
58
import javax.tools.JavaFileManager;
59
import javax.tools.JavaFileObject;
60
import javax.tools.JavaFileObject.Kind;
61
import javax.tools.StandardJavaFileManager;
62
import javax.tools.StandardLocation;
63
import javax.tools.ToolProvider;
64
65
import com.sun.tools.javac.file.JavacFileManager;
66
import com.sun.tools.javac.platform.JDKPlatformProvider;
67
68
import com.sun.tools.jdeprscan.scan.Scan;
69
70
import static java.util.stream.Collectors.*;
71
72
import javax.lang.model.element.PackageElement;
73
import javax.lang.model.element.TypeElement;
74
75
/**
76
* Deprecation Scanner tool. Loads API deprecation information from the
77
* JDK image, or optionally, from a jar file or class hierarchy. Then scans
78
* a class library for usages of those APIs.
79
*
80
* TODO:
81
* - audit error handling throughout, but mainly in scan package
82
* - handling of covariant overrides
83
* - handling of override of method found in multiple superinterfaces
84
* - convert type/method/field output to Java source like syntax, e.g.
85
* instead of java/lang/Character.isJavaLetter(C)Z
86
* print void java.lang.Character.isJavaLetter(char)boolean
87
* - more example output in man page
88
* - more rigorous GNU style option parsing; use joptsimple?
89
*
90
* FUTURES:
91
* - add module support: --add-modules, --module-path, module arg
92
* - load deprecation declarations from a designated class library instead
93
* of the JDK
94
* - load deprecation declarations from a module
95
* - scan a module (but a modular jar can be treated just a like an ordinary jar)
96
* - multi-version jar
97
*/
98
public class Main implements DiagnosticListener<JavaFileObject> {
99
final PrintStream out;
100
final PrintStream err;
101
final List<File> bootClassPath = new ArrayList<>();
102
final List<File> classPath = new ArrayList<>();
103
final List<File> systemModules = new ArrayList<>();
104
final List<String> options = new ArrayList<>();
105
final List<String> comments = new ArrayList<>();
106
107
// Valid releases need to match what the compiler supports.
108
// Keep these updated manually until there's a compiler API
109
// that allows querying of supported releases.
110
final Set<String> releasesWithoutForRemoval = Set.of("6", "7", "8");
111
final Set<String> releasesWithForRemoval = // "9", "10", "11", ...
112
IntStream.rangeClosed(9, Runtime.version().feature())
113
.mapToObj(Integer::toString)
114
.collect(Collectors.toUnmodifiableSet());
115
116
final Set<String> validReleases;
117
{
118
Set<String> temp = new HashSet<>(releasesWithoutForRemoval);
119
temp.addAll(releasesWithForRemoval);
120
validReleases = Set.of(temp.toArray(new String[0]));
121
}
122
123
boolean verbose = false;
124
boolean forRemoval = false;
125
126
final JavaCompiler compiler;
127
final StandardJavaFileManager fm;
128
129
List<DeprData> deprList; // non-null after successful load phase
130
131
/**
132
* Processes a collection of class names. Names should fully qualified
133
* names in the form "pkg.pkg.pkg.classname".
134
*
135
* @param classNames collection of fully qualified classnames to process
136
* @return true for success, false for failure
137
* @throws IOException if an I/O error occurs
138
*/
139
boolean doClassNames(Collection<String> classNames) throws IOException {
140
if (verbose) {
141
out.println("List of classes to process:");
142
classNames.forEach(out::println);
143
out.println("End of class list.");
144
}
145
146
// TODO: not sure this is necessary...
147
if (fm instanceof JavacFileManager) {
148
((JavacFileManager)fm).setSymbolFileEnabled(false);
149
}
150
151
fm.setLocation(StandardLocation.CLASS_PATH, classPath);
152
if (!bootClassPath.isEmpty()) {
153
fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath);
154
}
155
156
if (!systemModules.isEmpty()) {
157
fm.setLocation(StandardLocation.SYSTEM_MODULES, systemModules);
158
}
159
160
LoadProc proc = new LoadProc();
161
JavaCompiler.CompilationTask task =
162
compiler.getTask(null, fm, this, options, classNames, null);
163
task.setProcessors(List.of(proc));
164
boolean r = task.call();
165
if (r) {
166
if (forRemoval) {
167
deprList = proc.getDeprecations().stream()
168
.filter(DeprData::isForRemoval)
169
.toList();
170
} else {
171
deprList = proc.getDeprecations();
172
}
173
}
174
return r;
175
}
176
177
/**
178
* Processes a stream of filenames (strings). The strings are in the
179
* form pkg/pkg/pkg/classname.class relative to the root of a package
180
* hierarchy.
181
*
182
* @param filenames a Stream of filenames to process
183
* @return true for success, false for failure
184
* @throws IOException if an I/O error occurs
185
*/
186
boolean doFileNames(Stream<String> filenames) throws IOException {
187
return doClassNames(
188
filenames.filter(name -> name.endsWith(".class"))
189
.filter(name -> !name.endsWith("package-info.class"))
190
.filter(name -> !name.endsWith("module-info.class"))
191
.map(s -> s.replaceAll("\\.class$", ""))
192
.map(s -> s.replace(File.separatorChar, '.'))
193
.toList());
194
}
195
196
/**
197
* Replaces all but the first occurrence of '/' with '.'. Assumes
198
* that the name is in the format module/pkg/pkg/classname.class.
199
* That is, the name should contain at least one '/' character
200
* separating the module name from the package-class name.
201
*
202
* @param filename the input filename
203
* @return the modular classname
204
*/
205
String convertModularFileName(String filename) {
206
int slash = filename.indexOf('/');
207
return filename.substring(0, slash)
208
+ "/"
209
+ filename.substring(slash+1).replace('/', '.');
210
}
211
212
/**
213
* Processes a stream of filenames (strings) including a module prefix.
214
* The strings are in the form module/pkg/pkg/pkg/classname.class relative
215
* to the root of a directory containing modules. The strings are processed
216
* into module-qualified class names of the form
217
* "module/pkg.pkg.pkg.classname".
218
*
219
* @param filenames a Stream of filenames to process
220
* @return true for success, false for failure
221
* @throws IOException if an I/O error occurs
222
*/
223
boolean doModularFileNames(Stream<String> filenames) throws IOException {
224
return doClassNames(
225
filenames.filter(name -> name.endsWith(".class"))
226
.filter(name -> !name.endsWith("package-info.class"))
227
.filter(name -> !name.endsWith("module-info.class"))
228
.map(s -> s.replaceAll("\\.class$", ""))
229
.map(this::convertModularFileName)
230
.toList());
231
}
232
233
/**
234
* Processes named class files in the given directory. The directory
235
* should be the root of a package hierarchy. If classNames is
236
* empty, walks the directory hierarchy to find all classes.
237
*
238
* @param dirname the name of the directory to process
239
* @param classNames the names of classes to process
240
* @return true for success, false for failure
241
* @throws IOException if an I/O error occurs
242
*/
243
boolean processDirectory(String dirname, Collection<String> classNames) throws IOException {
244
if (!Files.isDirectory(Paths.get(dirname))) {
245
err.printf("%s: not a directory%n", dirname);
246
return false;
247
}
248
249
classPath.add(0, new File(dirname));
250
251
if (classNames.isEmpty()) {
252
Path base = Paths.get(dirname);
253
int baseCount = base.getNameCount();
254
try (Stream<Path> paths = Files.walk(base)) {
255
Stream<String> files =
256
paths.filter(p -> p.getNameCount() > baseCount)
257
.map(p -> p.subpath(baseCount, p.getNameCount()))
258
.map(Path::toString);
259
return doFileNames(files);
260
}
261
} else {
262
return doClassNames(classNames);
263
}
264
}
265
266
/**
267
* Processes all class files in the given jar file.
268
*
269
* @param jarname the name of the jar file to process
270
* @return true for success, false for failure
271
* @throws IOException if an I/O error occurs
272
*/
273
boolean doJarFile(String jarname) throws IOException {
274
try (JarFile jf = new JarFile(jarname)) {
275
Stream<String> files =
276
jf.stream()
277
.map(JarEntry::getName);
278
return doFileNames(files);
279
}
280
}
281
282
/**
283
* Processes named class files from the given jar file,
284
* or all classes if classNames is empty.
285
*
286
* @param jarname the name of the jar file to process
287
* @param classNames the names of classes to process
288
* @return true for success, false for failure
289
* @throws IOException if an I/O error occurs
290
*/
291
boolean processJarFile(String jarname, Collection<String> classNames) throws IOException {
292
classPath.add(0, new File(jarname));
293
294
if (classNames.isEmpty()) {
295
return doJarFile(jarname);
296
} else {
297
return doClassNames(classNames);
298
}
299
}
300
301
/**
302
* Processes named class files from rt.jar of a JDK version 7 or 8.
303
* If classNames is empty, processes all classes.
304
*
305
* @param jdkHome the path to the "home" of the JDK to process
306
* @param classNames the names of classes to process
307
* @return true for success, false for failure
308
* @throws IOException if an I/O error occurs
309
*/
310
boolean processOldJdk(String jdkHome, Collection<String> classNames) throws IOException {
311
String RTJAR = jdkHome + "/jre/lib/rt.jar";
312
String CSJAR = jdkHome + "/jre/lib/charsets.jar";
313
314
bootClassPath.add(0, new File(RTJAR));
315
bootClassPath.add(1, new File(CSJAR));
316
options.add("-source");
317
options.add("8");
318
319
if (classNames.isEmpty()) {
320
return doJarFile(RTJAR);
321
} else {
322
return doClassNames(classNames);
323
}
324
}
325
326
/**
327
* Processes listed classes given a JDK 9 home.
328
*/
329
boolean processJdk9(String jdkHome, Collection<String> classes) throws IOException {
330
systemModules.add(new File(jdkHome));
331
return doClassNames(classes);
332
}
333
334
/**
335
* Processes the class files from the currently running JDK,
336
* using the jrt: filesystem.
337
*
338
* @return true for success, false for failure
339
* @throws IOException if an I/O error occurs
340
*/
341
boolean processSelf(Collection<String> classes) throws IOException {
342
options.add("--add-modules");
343
options.add("java.se");
344
345
if (classes.isEmpty()) {
346
Path modules = FileSystems.getFileSystem(URI.create("jrt:/"))
347
.getPath("/modules");
348
349
// names are /modules/<modulename>/pkg/.../Classname.class
350
try (Stream<Path> paths = Files.walk(modules)) {
351
Stream<String> files =
352
paths.filter(p -> p.getNameCount() > 2)
353
.map(p -> p.subpath(1, p.getNameCount()))
354
.map(Path::toString);
355
return doModularFileNames(files);
356
}
357
} else {
358
return doClassNames(classes);
359
}
360
}
361
362
/**
363
* Process classes from a particular JDK release, using only information
364
* in this JDK.
365
*
366
* @param release a supported release version, like "8" or "10".
367
* @param classes collection of classes to process, may be empty
368
* @return success value
369
*/
370
boolean processRelease(String release, Collection<String> classes) throws IOException {
371
boolean hasModules;
372
boolean hasJavaSE_EE;
373
374
try {
375
int releaseNum = Integer.parseInt(release);
376
377
hasModules = releaseNum >= 9;
378
hasJavaSE_EE = hasModules && releaseNum <= 10;
379
} catch (NumberFormatException ex) {
380
hasModules = true;
381
hasJavaSE_EE = false;
382
}
383
384
options.addAll(List.of("--release", release));
385
386
if (hasModules) {
387
List<String> rootMods = hasJavaSE_EE ? List.of("java.se", "java.se.ee")
388
: List.of("java.se");
389
TraverseProc proc = new TraverseProc(rootMods);
390
JavaCompiler.CompilationTask task =
391
compiler.getTask(null, fm, this,
392
// options
393
List.of("--add-modules", String.join(",", rootMods),
394
"--release", release),
395
// classes
396
List.of("java.lang.Object"),
397
null);
398
task.setProcessors(List.of(proc));
399
if (!task.call()) {
400
return false;
401
}
402
Map<PackageElement, List<TypeElement>> types = proc.getPublicTypes();
403
options.add("--add-modules");
404
options.add(String.join(",", rootMods));
405
return doClassNames(
406
types.values().stream()
407
.flatMap(List::stream)
408
.map(TypeElement::toString)
409
.toList());
410
} else {
411
JDKPlatformProvider pp = new JDKPlatformProvider();
412
if (StreamSupport.stream(pp.getSupportedPlatformNames().spliterator(),
413
false)
414
.noneMatch(n -> n.equals(release))) {
415
return false;
416
}
417
JavaFileManager fm = pp.getPlatform(release, "").getFileManager();
418
List<String> classNames = new ArrayList<>();
419
for (JavaFileObject fo : fm.list(StandardLocation.PLATFORM_CLASS_PATH,
420
"",
421
EnumSet.of(Kind.CLASS),
422
true)) {
423
classNames.add(fm.inferBinaryName(StandardLocation.PLATFORM_CLASS_PATH, fo));
424
}
425
426
options.add("-Xlint:-options");
427
428
return doClassNames(classNames);
429
}
430
}
431
432
/**
433
* An enum denoting the mode in which the tool is running.
434
* Different modes correspond to the different process* methods.
435
* The exception is UNKNOWN, which indicates that a mode wasn't
436
* specified on the command line, which is an error.
437
*/
438
static enum LoadMode {
439
CLASSES, DIR, JAR, OLD_JDK, JDK9, SELF, RELEASE, LOAD_CSV
440
}
441
442
static enum ScanMode {
443
ARGS, LIST, PRINT_CSV
444
}
445
446
/**
447
* A checked exception that's thrown if a command-line syntax error
448
* is detected.
449
*/
450
static class UsageException extends Exception {
451
private static final long serialVersionUID = 3611828659572908743L;
452
}
453
454
/**
455
* Convenience method to throw UsageException if a condition is false.
456
*
457
* @param cond the condition that's required to be true
458
* @throws UsageException
459
*/
460
void require(boolean cond) throws UsageException {
461
if (!cond) {
462
throw new UsageException();
463
}
464
}
465
466
/**
467
* Constructs an instance of the finder tool.
468
*
469
* @param out the stream to which the tool's output is sent
470
* @param err the stream to which error messages are sent
471
*/
472
Main(PrintStream out, PrintStream err) {
473
this.out = out;
474
this.err = err;
475
compiler = ToolProvider.getSystemJavaCompiler();
476
fm = compiler.getStandardFileManager(this, null, StandardCharsets.UTF_8);
477
}
478
479
/**
480
* Prints the diagnostic to the err stream.
481
*
482
* Specified by the DiagnosticListener interface.
483
*
484
* @param diagnostic the tool diagnostic to print
485
*/
486
@Override
487
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
488
err.println(diagnostic);
489
}
490
491
/**
492
* Parses arguments and performs the requested processing.
493
*
494
* @param argArray command-line arguments
495
* @return true on success, false on error
496
*/
497
boolean run(String... argArray) {
498
Queue<String> args = new ArrayDeque<>(Arrays.asList(argArray));
499
LoadMode loadMode = LoadMode.RELEASE;
500
ScanMode scanMode = ScanMode.ARGS;
501
String dir = null;
502
String jar = null;
503
String jdkHome = null;
504
String release = Integer.toString(Runtime.version().feature());
505
List<String> loadClasses = new ArrayList<>();
506
String csvFile = null;
507
508
try {
509
while (!args.isEmpty()) {
510
String a = args.element();
511
if (a.startsWith("-")) {
512
args.remove();
513
switch (a) {
514
case "--class-path":
515
classPath.clear();
516
Arrays.stream(args.remove().split(File.pathSeparator))
517
.map(File::new)
518
.forEachOrdered(classPath::add);
519
break;
520
case "--for-removal":
521
forRemoval = true;
522
break;
523
case "--full-version":
524
out.println(System.getProperty("java.vm.version"));
525
return false;
526
case "--help":
527
case "-h":
528
case "-?":
529
printHelp(out);
530
out.println();
531
out.println(Messages.get("main.help"));
532
return true;
533
case "-l":
534
case "--list":
535
require(scanMode == ScanMode.ARGS);
536
scanMode = ScanMode.LIST;
537
break;
538
case "--release":
539
loadMode = LoadMode.RELEASE;
540
release = args.remove();
541
if (!validReleases.contains(release)) {
542
throw new UsageException();
543
}
544
break;
545
case "-v":
546
case "--verbose":
547
verbose = true;
548
break;
549
case "--version":
550
out.println(System.getProperty("java.version"));
551
return false;
552
case "--Xcompiler-arg":
553
options.add(args.remove());
554
break;
555
case "--Xcsv-comment":
556
comments.add(args.remove());
557
break;
558
case "--Xhelp":
559
out.println(Messages.get("main.xhelp"));
560
return false;
561
case "--Xload-class":
562
loadMode = LoadMode.CLASSES;
563
loadClasses.add(args.remove());
564
break;
565
case "--Xload-csv":
566
loadMode = LoadMode.LOAD_CSV;
567
csvFile = args.remove();
568
break;
569
case "--Xload-dir":
570
loadMode = LoadMode.DIR;
571
dir = args.remove();
572
break;
573
case "--Xload-jar":
574
loadMode = LoadMode.JAR;
575
jar = args.remove();
576
break;
577
case "--Xload-jdk9":
578
loadMode = LoadMode.JDK9;
579
jdkHome = args.remove();
580
break;
581
case "--Xload-old-jdk":
582
loadMode = LoadMode.OLD_JDK;
583
jdkHome = args.remove();
584
break;
585
case "--Xload-self":
586
loadMode = LoadMode.SELF;
587
break;
588
case "--Xprint-csv":
589
require(scanMode == ScanMode.ARGS);
590
scanMode = ScanMode.PRINT_CSV;
591
break;
592
default:
593
throw new UsageException();
594
}
595
} else {
596
break;
597
}
598
}
599
600
if ((scanMode == ScanMode.ARGS) == args.isEmpty()) {
601
throw new UsageException();
602
}
603
604
if ( forRemoval && loadMode == LoadMode.RELEASE &&
605
releasesWithoutForRemoval.contains(release)) {
606
throw new UsageException();
607
}
608
609
boolean success = false;
610
611
switch (loadMode) {
612
case CLASSES:
613
success = doClassNames(loadClasses);
614
break;
615
case DIR:
616
success = processDirectory(dir, loadClasses);
617
break;
618
case JAR:
619
success = processJarFile(jar, loadClasses);
620
break;
621
case JDK9:
622
require(!args.isEmpty());
623
success = processJdk9(jdkHome, loadClasses);
624
break;
625
case LOAD_CSV:
626
deprList = DeprDB.loadFromFile(csvFile);
627
success = true;
628
break;
629
case OLD_JDK:
630
success = processOldJdk(jdkHome, loadClasses);
631
break;
632
case RELEASE:
633
success = processRelease(release, loadClasses);
634
break;
635
case SELF:
636
success = processSelf(loadClasses);
637
break;
638
default:
639
throw new UsageException();
640
}
641
642
if (!success) {
643
return false;
644
}
645
} catch (NoSuchElementException | UsageException ex) {
646
printHelp(err);
647
return false;
648
} catch (IOException ioe) {
649
if (verbose) {
650
ioe.printStackTrace(err);
651
} else {
652
err.println(ioe);
653
}
654
return false;
655
}
656
657
// now the scanning phase
658
659
boolean scanStatus = true;
660
661
switch (scanMode) {
662
case LIST:
663
for (DeprData dd : deprList) {
664
if (!forRemoval || dd.isForRemoval()) {
665
out.println(Pretty.print(dd));
666
}
667
}
668
break;
669
case PRINT_CSV:
670
out.println("#jdepr1");
671
comments.forEach(s -> out.println("# " + s));
672
for (DeprData dd : deprList) {
673
CSV.write(out, dd.kind, dd.typeName, dd.nameSig, dd.since, dd.forRemoval);
674
}
675
break;
676
case ARGS:
677
DeprDB db = DeprDB.loadFromList(deprList);
678
List<String> cp = classPath.stream()
679
.map(File::toString)
680
.toList();
681
Scan scan = new Scan(out, err, cp, db, verbose);
682
683
for (String a : args) {
684
boolean s;
685
if (a.endsWith(".jar")) {
686
s = scan.scanJar(a);
687
} else if (a.endsWith(".class")) {
688
s = scan.processClassFile(a);
689
} else if (Files.isDirectory(Paths.get(a))) {
690
s = scan.scanDir(a);
691
} else {
692
s = scan.processClassName(a.replace('.', '/'));
693
}
694
scanStatus = scanStatus && s;
695
}
696
break;
697
}
698
699
return scanStatus;
700
}
701
702
private void printHelp(PrintStream out) {
703
JDKPlatformProvider pp = new JDKPlatformProvider();
704
String supportedReleases =
705
String.join("|", pp.getSupportedPlatformNames());
706
out.println(Messages.get("main.usage", supportedReleases));
707
}
708
709
/**
710
* Programmatic main entry point: initializes the tool instance to
711
* use stdout and stderr; runs the tool, passing command-line args;
712
* returns an exit status.
713
*
714
* @return true on success, false otherwise
715
*/
716
public static boolean call(PrintStream out, PrintStream err, String... args) {
717
return new Main(out, err).run(args);
718
}
719
720
/**
721
* Calls the main entry point and exits the JVM with an exit
722
* status determined by the return status.
723
*/
724
public static void main(String[] args) {
725
System.exit(call(System.out, System.err, args) ? 0 : 1);
726
}
727
}
728
729