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/jdeps/ModuleAnalyzer.java
41161 views
1
/*
2
* Copyright (c) 2016, 2020, 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
package com.sun.tools.jdeps;
26
27
import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
28
import static com.sun.tools.jdeps.Module.*;
29
import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
30
import static java.util.stream.Collectors.*;
31
32
import com.sun.tools.classfile.Dependency;
33
34
import java.io.IOException;
35
import java.io.PrintWriter;
36
import java.lang.module.ModuleDescriptor;
37
import java.util.Comparator;
38
import java.util.HashMap;
39
import java.util.HashSet;
40
import java.util.Map;
41
import java.util.Optional;
42
import java.util.Set;
43
import java.util.function.Function;
44
import java.util.stream.Collectors;
45
import java.util.stream.Stream;
46
47
/**
48
* Analyze module dependences and compare with module descriptor.
49
* Also identify any qualified exports not used by the target module.
50
*/
51
public class ModuleAnalyzer {
52
private static final String JAVA_BASE = "java.base";
53
54
private final JdepsConfiguration configuration;
55
private final PrintWriter log;
56
private final DependencyFinder dependencyFinder;
57
private final Map<Module, ModuleDeps> modules;
58
59
public ModuleAnalyzer(JdepsConfiguration config,
60
PrintWriter log,
61
Set<String> names) {
62
this.configuration = config;
63
this.log = log;
64
65
this.dependencyFinder = new DependencyFinder(config, DEFAULT_FILTER);
66
if (names.isEmpty()) {
67
this.modules = configuration.rootModules().stream()
68
.collect(toMap(Function.identity(), ModuleDeps::new));
69
} else {
70
this.modules = names.stream()
71
.map(configuration::findModule)
72
.flatMap(Optional::stream)
73
.collect(toMap(Function.identity(), ModuleDeps::new));
74
}
75
}
76
77
public boolean run(boolean ignoreMissingDeps) throws IOException {
78
try {
79
for (ModuleDeps md: modules.values()) {
80
// compute "requires transitive" dependences
81
md.computeRequiresTransitive(ignoreMissingDeps);
82
// compute "requires" dependences
83
md.computeRequires(ignoreMissingDeps);
84
// print module descriptor
85
md.printModuleDescriptor();
86
87
// apply transitive reduction and reports recommended requires.
88
boolean ok = md.analyzeDeps();
89
if (!ok) return false;
90
91
if (ignoreMissingDeps && md.hasMissingDependencies()) {
92
log.format("Warning: --ignore-missing-deps specified. Missing dependencies from %s are ignored%n",
93
md.root.name());
94
}
95
}
96
} finally {
97
dependencyFinder.shutdown();
98
}
99
return true;
100
}
101
102
103
class ModuleDeps {
104
final Module root;
105
Set<Module> requiresTransitive;
106
Set<Module> requires;
107
Map<String, Set<String>> unusedQualifiedExports;
108
109
ModuleDeps(Module root) {
110
this.root = root;
111
}
112
113
/**
114
* Compute 'requires transitive' dependences by analyzing API dependencies
115
*/
116
private void computeRequiresTransitive(boolean ignoreMissingDeps) {
117
// record requires transitive
118
this.requiresTransitive = computeRequires(true, ignoreMissingDeps)
119
.filter(m -> !m.name().equals(JAVA_BASE))
120
.collect(toSet());
121
122
trace("requires transitive: %s%n", requiresTransitive);
123
}
124
125
private void computeRequires(boolean ignoreMissingDeps) {
126
this.requires = computeRequires(false, ignoreMissingDeps).collect(toSet());
127
trace("requires: %s%n", requires);
128
}
129
130
private Stream<Module> computeRequires(boolean apionly, boolean ignoreMissingDeps) {
131
// analyze all classes
132
if (apionly) {
133
dependencyFinder.parseExportedAPIs(Stream.of(root));
134
} else {
135
dependencyFinder.parse(Stream.of(root));
136
}
137
138
// find the modules of all the dependencies found
139
return dependencyFinder.getDependences(root)
140
.filter(a -> !(ignoreMissingDeps && Analyzer.notFound(a)))
141
.map(Archive::getModule);
142
}
143
144
boolean hasMissingDependencies() {
145
return dependencyFinder.getDependences(root).anyMatch(Analyzer::notFound);
146
}
147
148
ModuleDescriptor descriptor() {
149
return descriptor(requiresTransitive, requires);
150
}
151
152
private ModuleDescriptor descriptor(Set<Module> requiresTransitive,
153
Set<Module> requires) {
154
155
ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(root.name());
156
157
if (!root.name().equals(JAVA_BASE))
158
builder.requires(Set.of(MANDATED), JAVA_BASE);
159
160
requiresTransitive.stream()
161
.filter(m -> !m.name().equals(JAVA_BASE))
162
.map(Module::name)
163
.forEach(mn -> builder.requires(Set.of(TRANSITIVE), mn));
164
165
requires.stream()
166
.filter(m -> !requiresTransitive.contains(m))
167
.filter(m -> !m.name().equals(JAVA_BASE))
168
.map(Module::name)
169
.forEach(mn -> builder.requires(mn));
170
171
return builder.build();
172
}
173
174
private Graph<Module> buildReducedGraph() {
175
ModuleGraphBuilder rpBuilder = new ModuleGraphBuilder(configuration);
176
rpBuilder.addModule(root);
177
requiresTransitive.stream()
178
.forEach(m -> rpBuilder.addEdge(root, m));
179
180
// requires transitive graph
181
Graph<Module> rbg = rpBuilder.build().reduce();
182
183
ModuleGraphBuilder gb = new ModuleGraphBuilder(configuration);
184
gb.addModule(root);
185
requires.stream()
186
.forEach(m -> gb.addEdge(root, m));
187
188
// transitive reduction
189
Graph<Module> newGraph = gb.buildGraph().reduce(rbg);
190
if (DEBUG) {
191
System.err.println("after transitive reduction: ");
192
newGraph.printGraph(log);
193
}
194
return newGraph;
195
}
196
197
/**
198
* Apply the transitive reduction on the module graph
199
* and returns the corresponding ModuleDescriptor
200
*/
201
ModuleDescriptor reduced() {
202
Graph<Module> g = buildReducedGraph();
203
return descriptor(requiresTransitive, g.adjacentNodes(root));
204
}
205
206
private void showMissingDeps() {
207
// build the analyzer if there are missing dependences
208
Analyzer analyzer = new Analyzer(configuration, Analyzer.Type.CLASS, DEFAULT_FILTER);
209
analyzer.run(Set.of(root), dependencyFinder.locationToArchive());
210
log.println("Error: Missing dependencies: classes not found from the module path.");
211
Analyzer.Visitor visitor = new Analyzer.Visitor() {
212
@Override
213
public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {
214
log.format(" %-50s -> %-50s %s%n", origin, target, targetArchive.getName());
215
}
216
};
217
analyzer.visitDependences(root, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);
218
log.println();
219
}
220
221
/**
222
* Apply transitive reduction on the resulting graph and reports
223
* recommended requires.
224
*/
225
private boolean analyzeDeps() {
226
if (requires.stream().anyMatch(m -> m == UNNAMED_MODULE)) {
227
showMissingDeps();
228
return false;
229
}
230
231
ModuleDescriptor analyzedDescriptor = descriptor();
232
if (!matches(root.descriptor(), analyzedDescriptor)) {
233
log.format(" [Suggested module descriptor for %s]%n", root.name());
234
analyzedDescriptor.requires()
235
.stream()
236
.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))
237
.forEach(req -> log.format(" requires %s;%n", req));
238
}
239
240
ModuleDescriptor reduced = reduced();
241
if (!matches(root.descriptor(), reduced)) {
242
log.format(" [Transitive reduced graph for %s]%n", root.name());
243
reduced.requires()
244
.stream()
245
.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))
246
.forEach(req -> log.format(" requires %s;%n", req));
247
}
248
249
checkQualifiedExports();
250
log.println();
251
return true;
252
}
253
254
private void checkQualifiedExports() {
255
// detect any qualified exports not used by the target module
256
unusedQualifiedExports = unusedQualifiedExports();
257
if (!unusedQualifiedExports.isEmpty())
258
log.format(" [Unused qualified exports in %s]%n", root.name());
259
260
unusedQualifiedExports.keySet().stream()
261
.sorted()
262
.forEach(pn -> log.format(" exports %s to %s%n", pn,
263
unusedQualifiedExports.get(pn).stream()
264
.sorted()
265
.collect(joining(","))));
266
}
267
268
void printModuleDescriptor() {
269
printModuleDescriptor(log, root);
270
}
271
272
private void printModuleDescriptor(PrintWriter out, Module module) {
273
ModuleDescriptor descriptor = module.descriptor();
274
out.format("%s (%s)%n", descriptor.name(), module.location());
275
276
if (descriptor.name().equals(JAVA_BASE))
277
return;
278
279
out.println(" [Module descriptor]");
280
descriptor.requires()
281
.stream()
282
.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))
283
.forEach(req -> out.format(" requires %s;%n", req));
284
}
285
286
287
/**
288
* Detects any qualified exports not used by the target module.
289
*/
290
private Map<String, Set<String>> unusedQualifiedExports() {
291
Map<String, Set<String>> unused = new HashMap<>();
292
293
// build the qualified exports map
294
Map<String, Set<String>> qualifiedExports =
295
root.exports().entrySet().stream()
296
.filter(e -> !e.getValue().isEmpty())
297
.map(Map.Entry::getKey)
298
.collect(toMap(Function.identity(), _k -> new HashSet<>()));
299
300
Set<Module> mods = new HashSet<>();
301
root.exports().values()
302
.stream()
303
.flatMap(Set::stream)
304
.forEach(target -> configuration.findModule(target)
305
.ifPresentOrElse(mods::add,
306
() -> log.format("Warning: %s not found%n", target))
307
);
308
309
// parse all target modules
310
dependencyFinder.parse(mods.stream());
311
312
// adds to the qualified exports map if a module references it
313
mods.stream().forEach(m ->
314
m.getDependencies()
315
.map(Dependency.Location::getPackageName)
316
.filter(qualifiedExports::containsKey)
317
.forEach(pn -> qualifiedExports.get(pn).add(m.name())));
318
319
// compare with the exports from ModuleDescriptor
320
Set<String> staleQualifiedExports =
321
qualifiedExports.keySet().stream()
322
.filter(pn -> !qualifiedExports.get(pn).equals(root.exports().get(pn)))
323
.collect(toSet());
324
325
if (!staleQualifiedExports.isEmpty()) {
326
for (String pn : staleQualifiedExports) {
327
Set<String> targets = new HashSet<>(root.exports().get(pn));
328
targets.removeAll(qualifiedExports.get(pn));
329
unused.put(pn, targets);
330
}
331
}
332
return unused;
333
}
334
}
335
336
private boolean matches(ModuleDescriptor md, ModuleDescriptor other) {
337
// build requires transitive from ModuleDescriptor
338
Set<ModuleDescriptor.Requires> reqTransitive = md.requires().stream()
339
.filter(req -> req.modifiers().contains(TRANSITIVE))
340
.collect(toSet());
341
Set<ModuleDescriptor.Requires> otherReqTransitive = other.requires().stream()
342
.filter(req -> req.modifiers().contains(TRANSITIVE))
343
.collect(toSet());
344
345
if (!reqTransitive.equals(otherReqTransitive)) {
346
trace("mismatch requires transitive: %s%n", reqTransitive);
347
return false;
348
}
349
350
Set<ModuleDescriptor.Requires> unused = md.requires().stream()
351
.filter(req -> !other.requires().contains(req))
352
.collect(Collectors.toSet());
353
354
if (!unused.isEmpty()) {
355
trace("mismatch requires: %s%n", unused);
356
return false;
357
}
358
return true;
359
}
360
361
// ---- for testing purpose
362
public ModuleDescriptor[] descriptors(String name) {
363
ModuleDeps moduleDeps = modules.keySet().stream()
364
.filter(m -> m.name().equals(name))
365
.map(modules::get)
366
.findFirst().get();
367
368
ModuleDescriptor[] descriptors = new ModuleDescriptor[3];
369
descriptors[0] = moduleDeps.root.descriptor();
370
descriptors[1] = moduleDeps.descriptor();
371
descriptors[2] = moduleDeps.reduced();
372
return descriptors;
373
}
374
375
public Map<String, Set<String>> unusedQualifiedExports(String name) {
376
ModuleDeps moduleDeps = modules.keySet().stream()
377
.filter(m -> m.name().equals(name))
378
.map(modules::get)
379
.findFirst().get();
380
return moduleDeps.unusedQualifiedExports;
381
}
382
}
383
384