ModuleInfoBuilder.java revision 4217:bd10ad9aefb3
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.JdepsTask.*;
28import static com.sun.tools.jdeps.Analyzer.*;
29import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
30
31import java.io.IOException;
32import java.io.PrintWriter;
33import java.io.UncheckedIOException;
34import java.lang.module.ModuleDescriptor;
35import java.lang.module.ModuleDescriptor.Exports;
36import java.lang.module.ModuleDescriptor.Provides;
37import java.lang.module.ModuleDescriptor.Requires;
38import java.lang.module.ModuleFinder;
39import java.nio.file.Files;
40import java.nio.file.Path;
41import java.nio.file.Paths;
42import java.util.Collections;
43import java.util.Comparator;
44import java.util.HashMap;
45import java.util.List;
46import java.util.Locale;
47import java.util.Map;
48import java.util.Optional;
49import java.util.Set;
50import java.util.function.Function;
51import java.util.stream.Collectors;
52import java.util.stream.Stream;
53import static java.util.stream.Collectors.*;
54
55
56public class ModuleInfoBuilder {
57    final JdepsConfiguration configuration;
58    final Path outputdir;
59    final boolean open;
60
61    final DependencyFinder dependencyFinder;
62    final Analyzer analyzer;
63
64    // an input JAR file (loaded as an automatic module for analysis)
65    // maps to a normal module to generate module-info.java
66    final Map<Module, Module> automaticToNormalModule;
67    public ModuleInfoBuilder(JdepsConfiguration configuration,
68                             List<String> args,
69                             Path outputdir,
70                             boolean open) {
71        this.configuration = configuration;
72        this.outputdir = outputdir;
73        this.open = open;
74
75        this.dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER);
76        this.analyzer = new Analyzer(configuration, Type.CLASS, DEFAULT_FILTER);
77
78        // add targets to modulepath if it has module-info.class
79        List<Path> paths = args.stream()
80            .map(fn -> Paths.get(fn))
81            .collect(toList());
82
83        // automatic module to convert to normal module
84        this.automaticToNormalModule = ModuleFinder.of(paths.toArray(new Path[0]))
85                .findAll().stream()
86                .map(configuration::toModule)
87                .collect(toMap(Function.identity(), Function.identity()));
88
89        Optional<Module> om = automaticToNormalModule.keySet().stream()
90                                    .filter(m -> !m.descriptor().isAutomatic())
91                                    .findAny();
92        if (om.isPresent()) {
93            throw new UncheckedBadArgs(new BadArgs("err.genmoduleinfo.not.jarfile",
94                                                   om.get().getPathName()));
95        }
96        if (automaticToNormalModule.isEmpty()) {
97            throw new UncheckedBadArgs(new BadArgs("err.invalid.path", args));
98        }
99    }
100
101    public boolean run() throws IOException {
102        try {
103            // pass 1: find API dependencies
104            Map<Archive, Set<Archive>> requiresTransitive = computeRequiresTransitive();
105
106            // pass 2: analyze all class dependences
107            dependencyFinder.parse(automaticModules().stream());
108
109            analyzer.run(automaticModules(), dependencyFinder.locationToArchive());
110
111            boolean missingDeps = false;
112            for (Module m : automaticModules()) {
113                Set<Archive> apiDeps = requiresTransitive.containsKey(m)
114                                            ? requiresTransitive.get(m)
115                                            : Collections.emptySet();
116
117                Path file = outputdir.resolve(m.name()).resolve("module-info.java");
118
119                // computes requires and requires transitive
120                Module normalModule = toNormalModule(m, apiDeps);
121                if (normalModule != null) {
122                    automaticToNormalModule.put(m, normalModule);
123
124                    // generate module-info.java
125                    System.out.format("writing to %s%n", file);
126                    writeModuleInfo(file,  normalModule.descriptor());
127                } else {
128                    // find missing dependences
129                    System.out.format("Missing dependence: %s not generated%n", file);
130                    missingDeps = true;
131                }
132            }
133
134            return !missingDeps;
135        } finally {
136            dependencyFinder.shutdown();
137        }
138    }
139
140    boolean notFound(Archive m) {
141        return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS;
142    }
143
144    private Module toNormalModule(Module module, Set<Archive> requiresTransitive)
145        throws IOException
146    {
147        // done analysis
148        module.close();
149
150        if (analyzer.requires(module).anyMatch(this::notFound)) {
151            // missing dependencies
152            return null;
153        }
154
155        Map<String, Boolean> requires = new HashMap<>();
156        requiresTransitive.stream()
157            .map(Archive::getModule)
158            .forEach(m -> requires.put(m.name(), Boolean.TRUE));
159
160        analyzer.requires(module)
161            .map(Archive::getModule)
162            .forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE));
163
164        return module.toNormalModule(requires);
165    }
166
167    /**
168     * Returns the stream of resulting modules
169     */
170    Stream<Module> modules() {
171        return automaticToNormalModule.values().stream();
172    }
173
174    /**
175     * Returns the stream of resulting ModuleDescriptors
176     */
177    public Stream<ModuleDescriptor> descriptors() {
178        return automaticToNormalModule.entrySet().stream()
179                    .map(Map.Entry::getValue)
180                    .map(Module::descriptor);
181    }
182
183    void visitMissingDeps(Analyzer.Visitor visitor) {
184        automaticModules().stream()
185            .filter(m -> analyzer.requires(m).anyMatch(this::notFound))
186            .forEach(m -> {
187                analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE);
188            });
189    }
190
191    void writeModuleInfo(Path file, ModuleDescriptor md) {
192        try {
193            Files.createDirectories(file.getParent());
194            try (PrintWriter pw = new PrintWriter(Files.newOutputStream(file))) {
195                printModuleInfo(pw, md);
196            }
197        } catch (IOException e) {
198            throw new UncheckedIOException(e);
199        }
200    }
201
202    private void printModuleInfo(PrintWriter writer, ModuleDescriptor md) {
203        writer.format("%smodule %s {%n", open ? "open " : "", md.name());
204
205        Map<String, Module> modules = configuration.getModules();
206
207        // first print requires
208        Set<Requires> reqs = md.requires().stream()
209            .filter(req -> !req.name().equals("java.base") && req.modifiers().isEmpty())
210            .collect(Collectors.toSet());
211        reqs.stream()
212            .sorted(Comparator.comparing(Requires::name))
213            .forEach(req -> writer.format("    requires %s;%n",
214                                          toString(req.modifiers(), req.name())));
215        if (!reqs.isEmpty()) {
216            writer.println();
217        }
218
219        // requires transitive
220        reqs = md.requires().stream()
221                 .filter(req -> !req.name().equals("java.base") && !req.modifiers().isEmpty())
222                 .collect(Collectors.toSet());
223        reqs.stream()
224            .sorted(Comparator.comparing(Requires::name))
225            .forEach(req -> writer.format("    requires %s;%n",
226                                          toString(req.modifiers(), req.name())));
227        if (!reqs.isEmpty()) {
228            writer.println();
229        }
230
231        if (!open) {
232            md.exports().stream()
233              .peek(exp -> {
234                  if (exp.isQualified())
235                      throw new InternalError(md.name() + " qualified exports: " + exp);
236                  })
237              .sorted(Comparator.comparing(Exports::source))
238              .forEach(exp -> writer.format("    exports %s;%n", exp.source()));
239
240            if (!md.exports().isEmpty()) {
241                writer.println();
242            }
243        }
244
245        md.provides().stream()
246          .sorted(Comparator.comparing(Provides::service))
247          .map(p -> p.providers().stream()
248                     .map(impl -> "        " + impl.replace('$', '.'))
249                     .collect(joining(",\n",
250                                      String.format("    provides %s with%n",
251                                                    p.service().replace('$', '.')),
252                                      ";")))
253                     .forEach(writer::println);
254
255        if (!md.provides().isEmpty()) {
256            writer.println();
257        }
258        writer.println("}");
259    }
260
261    private Set<Module> automaticModules() {
262        return automaticToNormalModule.keySet();
263    }
264
265    /**
266     * Returns a string containing the given set of modifiers and label.
267     */
268    private static <M> String toString(Set<M> mods, String what) {
269        return (Stream.concat(mods.stream().map(e -> e.toString().toLowerCase(Locale.US)),
270                              Stream.of(what)))
271                      .collect(Collectors.joining(" "));
272    }
273
274    /**
275     * Compute 'requires transitive' dependences by analyzing API dependencies
276     */
277    private Map<Archive, Set<Archive>> computeRequiresTransitive()
278        throws IOException
279    {
280        // parse the input modules
281        dependencyFinder.parseExportedAPIs(automaticModules().stream());
282
283        return dependencyFinder.dependences();
284    }
285}
286