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