ModuleInfoBuilder.java revision 3294:9adfb22ff08f
1/*
2 * Copyright (c) 2015, 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.Analyzer.Type.CLASS;
28import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;
29import static com.sun.tools.jdeps.Module.trace;
30
31import java.io.IOException;
32import java.io.PrintWriter;
33import java.nio.file.Files;
34import java.nio.file.Path;
35import java.util.HashMap;
36import java.util.Map;
37import java.util.Optional;
38import java.util.Set;
39import java.util.function.Function;
40import java.util.stream.Collectors;
41import java.util.stream.Stream;
42
43public class ModuleInfoBuilder {
44    final ModulePaths modulePaths;
45    final DependencyFinder dependencyFinder;
46    final JdepsFilter filter;
47    final Analyzer analyzer;
48    final Map<Module, Module> strictModules = new HashMap<>();
49    ModuleInfoBuilder(ModulePaths modulePaths, DependencyFinder finder) {
50        this.modulePaths = modulePaths;
51        this.dependencyFinder = finder;
52        this.filter = new JdepsFilter.Builder().filter(true, true).build();
53        this.analyzer = new Analyzer(CLASS, filter);
54    }
55
56    private Stream<Module> automaticModules() {
57        return modulePaths.getModules().values()
58                .stream()
59                .filter(Module::isAutomatic);
60    }
61
62    /**
63     * Compute 'requires public' dependences by analyzing API dependencies
64     */
65    Map<Module, Set<Module>> computeRequiresPublic() throws IOException {
66        dependencyFinder.findDependencies(filter, true /* api only */, 1);
67        Analyzer pass1 = new Analyzer(Analyzer.Type.CLASS, filter);
68
69        pass1.run(dependencyFinder.archives());
70
71        return automaticModules().collect(Collectors.toMap(Function.identity(),
72                source -> pass1.requires(source)
73                               .map(Archive::getModule)
74                               .collect(Collectors.toSet())));
75    }
76
77    boolean run(Analyzer.Type verbose, boolean quiet) throws IOException {
78        // add all automatic modules to the root set
79        automaticModules().forEach(dependencyFinder::addRoot);
80
81        // pass 1: find API dependencies
82        Map<Module, Set<Module>> requiresPublic = computeRequiresPublic();
83
84        // pass 2: analyze all class dependences
85        dependencyFinder.findDependencies(filter, false /* all classes */, 1);
86        analyzer.run(dependencyFinder.archives());
87
88        // computes requires and requires public
89        automaticModules().forEach(m -> {
90            Map<String, Boolean> requires;
91            if (requiresPublic.containsKey(m)) {
92                requires = requiresPublic.get(m)
93                        .stream()
94                        .collect(Collectors.toMap(Archive::getName, (v) -> Boolean.TRUE));
95            } else {
96                requires = new HashMap<>();
97            }
98            analyzer.requires(m)
99                    .forEach(d -> requires.putIfAbsent(d.getName(), Boolean.FALSE));
100
101            trace("strict module %s requires %s%n", m.name(), requires);
102            strictModules.put(m, m.toStrictModule(requires));
103        });
104
105        // find any missing dependences
106        Optional<Module> missingDeps = automaticModules()
107                .filter(this::missingDep)
108                .findAny();
109        if (missingDeps.isPresent()) {
110            automaticModules()
111                    .filter(this::missingDep)
112                    .forEach(m -> {
113                        System.err.format("Missing dependencies from %s%n", m.name());
114                        analyzer.visitDependences(m,
115                                new Analyzer.Visitor() {
116                                    @Override
117                                    public void visitDependence(String origin, Archive originArchive,
118                                                                String target, Archive targetArchive) {
119                                        if (targetArchive == NOT_FOUND)
120                                            System.err.format("   %-50s -> %-50s %s%n",
121                                                    origin, target, targetArchive.getName());
122                                    }
123                                }, verbose);
124                        System.err.println();
125                    });
126
127            System.err.println("ERROR: missing dependencies (check \"requires NOT_FOUND;\")");
128        }
129        return missingDeps.isPresent() ? false : true;
130    }
131
132    private boolean missingDep(Archive m) {
133        return analyzer.requires(m).filter(a -> a.equals(NOT_FOUND))
134                       .findAny().isPresent();
135    }
136
137    void build(Path dir) throws IOException {
138        ModuleInfoWriter writer = new ModuleInfoWriter(dir);
139        writer.generateOutput(strictModules.values(), analyzer);
140    }
141
142    private class ModuleInfoWriter {
143        private final Path outputDir;
144        ModuleInfoWriter(Path dir) {
145            this.outputDir = dir;
146        }
147
148        void generateOutput(Iterable<Module> modules, Analyzer analyzer) throws IOException {
149            // generate module-info.java file for each archive
150            for (Module m : modules) {
151                if (m.packages().contains("")) {
152                    System.err.format("ERROR: %s contains unnamed package.  " +
153                                      "module-info.java not generated%n", m.getPathName());
154                    continue;
155                }
156
157                String mn = m.getName();
158                Path srcFile = outputDir.resolve(mn).resolve("module-info.java");
159                Files.createDirectories(srcFile.getParent());
160                System.out.println("writing to " + srcFile);
161                try (PrintWriter pw = new PrintWriter(Files.newOutputStream(srcFile))) {
162                    printModuleInfo(pw, m);
163                }
164            }
165        }
166
167        private void printModuleInfo(PrintWriter writer, Module m) {
168            writer.format("module %s {%n", m.name());
169
170            Map<String, Module> modules = modulePaths.getModules();
171            Map<String, Boolean> requires = m.requires();
172            // first print the JDK modules
173            requires.keySet().stream()
174                    .filter(mn -> !mn.equals("java.base"))   // implicit requires
175                    .filter(mn -> modules.containsKey(mn) && modules.get(mn).isJDK())
176                    .sorted()
177                    .forEach(mn -> {
178                        String modifier = requires.get(mn) ? "public " : "";
179                        writer.format("    requires %s%s;%n", modifier, mn);
180                    });
181
182            // print requires non-JDK modules
183            requires.keySet().stream()
184                    .filter(mn -> !modules.containsKey(mn) || !modules.get(mn).isJDK())
185                    .sorted()
186                    .forEach(mn -> {
187                        String modifier = requires.get(mn) ? "public " : "";
188                        writer.format("    requires %s%s;%n", modifier, mn);
189                    });
190
191            m.packages().stream()
192                    .sorted()
193                    .forEach(pn -> writer.format("    exports %s;%n", pn));
194
195            m.provides().entrySet().stream()
196                    .sorted(Map.Entry.comparingByKey())
197                    .forEach(e -> {
198                        String service = e.getKey();
199                        e.getValue().stream()
200                                .sorted()
201                                .forEach(impl -> writer.format("    provides %s with %s;%n", service, impl));
202                    });
203
204            writer.println("}");
205        }
206    }
207}
208