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 */
25package com.sun.tools.jdeps;
26
27import static com.sun.tools.jdeps.Graph.*;
28import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
29import static com.sun.tools.jdeps.Module.*;
30import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
31import static java.util.stream.Collectors.*;
32
33import com.sun.tools.classfile.Dependency;
34import com.sun.tools.jdeps.JdepsTask.BadArgs;
35
36import java.io.IOException;
37import java.io.OutputStream;
38import java.io.PrintWriter;
39import java.lang.module.ModuleDescriptor;
40import java.nio.file.Files;
41import java.nio.file.Path;
42import java.util.Collections;
43import java.util.Comparator;
44import java.util.HashMap;
45import java.util.HashSet;
46import java.util.Map;
47import java.util.Optional;
48import java.util.Set;
49import java.util.function.Function;
50import java.util.stream.Collectors;
51import 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 */
57public class ModuleAnalyzer {
58    private static final String JAVA_BASE = "java.base";
59
60    private final JdepsConfiguration configuration;
61    private final PrintWriter log;
62    private final DependencyFinder dependencyFinder;
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;
70
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        }
81    }
82
83    public boolean run() throws IOException {
84        try {
85            // compute "requires transitive" dependences
86            modules.values().forEach(ModuleDeps::computeRequiresTransitive);
87
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();
96        }
97        return true;
98    }
99
100    class ModuleDeps {
101        final Module root;
102        Set<Module> requiresTransitive;
103        Set<Module> requires;
104        Map<String, Set<String>> unusedQualifiedExports;
105
106        ModuleDeps(Module root) {
107            this.root = root;
108        }
109
110        /**
111         * Compute 'requires transitive' dependences by analyzing API dependencies
112         */
113        private void computeRequiresTransitive() {
114            // record requires transitive
115            this.requiresTransitive = computeRequires(true)
116                .filter(m -> !m.name().equals(JAVA_BASE))
117                .collect(toSet());
118
119            trace("requires transitive: %s%n", requiresTransitive);
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() {
142            return descriptor(requiresTransitive, requires);
143        }
144
145        private ModuleDescriptor descriptor(Set<Module> requiresTransitive,
146                                            Set<Module> requires) {
147
148            ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(root.name());
149
150            if (!root.name().equals(JAVA_BASE))
151                builder.requires(Set.of(MANDATED), JAVA_BASE);
152
153            requiresTransitive.stream()
154                .filter(m -> !m.name().equals(JAVA_BASE))
155                .map(Module::name)
156                .forEach(mn -> builder.requires(Set.of(TRANSITIVE), mn));
157
158            requires.stream()
159                .filter(m -> !requiresTransitive.contains(m))
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
167        private Graph<Module> buildReducedGraph() {
168            ModuleGraphBuilder rpBuilder = new ModuleGraphBuilder(configuration);
169            rpBuilder.addModule(root);
170            requiresTransitive.stream()
171                          .forEach(m -> rpBuilder.addEdge(root, m));
172
173            // requires transitive graph
174            Graph<Module> rbg = rpBuilder.build().reduce();
175
176            ModuleGraphBuilder gb = new ModuleGraphBuilder(configuration);
177            gb.addModule(root);
178            requires.stream()
179                    .forEach(m -> gb.addEdge(root, m));
180
181            // transitive reduction
182            Graph<Module> newGraph = gb.buildGraph().reduce(rbg);
183            if (DEBUG) {
184                System.err.println("after transitive reduction: ");
185                newGraph.printGraph(log);
186            }
187            return newGraph;
188        }
189
190        /**
191         * Apply the transitive reduction on the module graph
192         * and returns the corresponding ModuleDescriptor
193         */
194        ModuleDescriptor reduced() {
195            Graph<Module> g = buildReducedGraph();
196            return descriptor(requiresTransitive, g.adjacentNodes(root));
197        }
198
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            }
214
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        }
241
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<>();
262
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());
281
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;
303        }
304    }
305
306    private boolean matches(ModuleDescriptor md, ModuleDescriptor other) {
307        // build requires transitive from ModuleDescriptor
308        Set<ModuleDescriptor.Requires> reqTransitive = md.requires().stream()
309            .filter(req -> req.modifiers().contains(TRANSITIVE))
310            .collect(toSet());
311        Set<ModuleDescriptor.Requires> otherReqTransitive = other.requires().stream()
312            .filter(req -> req.modifiers().contains(TRANSITIVE))
313            .collect(toSet());
314
315        if (!reqTransitive.equals(otherReqTransitive)) {
316            trace("mismatch requires transitive: %s%n", reqTransitive);
317            return false;
318        }
319
320        Set<ModuleDescriptor.Requires> unused = md.requires().stream()
321            .filter(req -> !other.requires().contains(req))
322            .collect(Collectors.toSet());
323
324        if (!unused.isEmpty()) {
325            trace("mismatch requires: %s%n", unused);
326            return false;
327        }
328        return true;
329    }
330
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    }
352}
353