ModuleInfoBuilder.java revision 3792:d516975e8110
1183234Ssimon/*
2280297Sjkim * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3280297Sjkim * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4183234Ssimon *
5183234Ssimon * This code is free software; you can redistribute it and/or modify it
6183234Ssimon * under the terms of the GNU General Public License version 2 only, as
7183234Ssimon * published by the Free Software Foundation.  Oracle designates this
8183234Ssimon * particular file as subject to the "Classpath" exception as provided
9183234Ssimon * by Oracle in the LICENSE file that accompanied this code.
10183234Ssimon *
11183234Ssimon * This code is distributed in the hope that it will be useful, but WITHOUT
12183234Ssimon * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13183234Ssimon * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14280297Sjkim * version 2 for more details (a copy is included in the LICENSE file that
15183234Ssimon * accompanied this code).
16183234Ssimon *
17183234Ssimon * You should have received a copy of the GNU General Public License version
18183234Ssimon * 2 along with this work; if not, write to the Free Software Foundation,
19183234Ssimon * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20183234Ssimon *
21183234Ssimon * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22183234Ssimon * or visit www.oracle.com if you need additional information or have any
23183234Ssimon * questions.
24183234Ssimon */
25183234Ssimonpackage com.sun.tools.jdeps;
26183234Ssimon
27183234Ssimonimport static com.sun.tools.jdeps.JdepsTask.*;
28183234Ssimonimport static com.sun.tools.jdeps.Analyzer.*;
29183234Ssimonimport static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
30183234Ssimon
31183234Ssimonimport java.io.IOException;
32183234Ssimonimport java.io.PrintWriter;
33183234Ssimonimport java.io.UncheckedIOException;
34183234Ssimonimport java.lang.module.ModuleDescriptor;
35183234Ssimonimport java.lang.module.ModuleDescriptor.Exports;
36183234Ssimonimport java.lang.module.ModuleDescriptor.Provides;
37183234Ssimonimport java.lang.module.ModuleDescriptor.Requires;
38183234Ssimonimport java.lang.module.ModuleFinder;
39183234Ssimonimport java.nio.file.Files;
40183234Ssimonimport java.nio.file.Path;
41183234Ssimonimport java.nio.file.Paths;
42183234Ssimonimport java.util.Collections;
43183234Ssimonimport java.util.Comparator;
44183234Ssimonimport java.util.HashMap;
45183234Ssimonimport java.util.List;
46183234Ssimonimport java.util.Map;
47183234Ssimonimport java.util.Optional;
48183234Ssimonimport java.util.Set;
49183234Ssimonimport java.util.function.Function;
50183234Ssimonimport java.util.stream.Stream;
51183234Ssimonimport static java.util.stream.Collectors.*;
52183234Ssimon
53183234Ssimon
54183234Ssimonpublic class ModuleInfoBuilder {
55183234Ssimon    final JdepsConfiguration configuration;
56183234Ssimon    final Path outputdir;
57290207Sjkim    final boolean open;
58290207Sjkim
59183234Ssimon    final DependencyFinder dependencyFinder;
60183234Ssimon    final Analyzer analyzer;
61183234Ssimon
62280297Sjkim    // an input JAR file (loaded as an automatic module for analysis)
63183234Ssimon    // maps to an explicit module to generate module-info.java
64290207Sjkim    final Map<Module, Module> automaticToExplicitModule;
65290207Sjkim    public ModuleInfoBuilder(JdepsConfiguration configuration,
66280297Sjkim                             List<String> args,
67183234Ssimon                             Path outputdir,
68280297Sjkim                             boolean open) {
69280297Sjkim        this.configuration = configuration;
70280297Sjkim        this.outputdir = outputdir;
71183234Ssimon        this.open = open;
72290207Sjkim
73280297Sjkim        this.dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER);
74290207Sjkim        this.analyzer = new Analyzer(configuration, Type.CLASS, DEFAULT_FILTER);
75290207Sjkim
76290207Sjkim        // add targets to modulepath if it has module-info.class
77290207Sjkim        List<Path> paths = args.stream()
78183234Ssimon            .map(fn -> Paths.get(fn))
79238405Sjkim            .collect(toList());
80238405Sjkim
81238405Sjkim        // automatic module to convert to explicit module
82238405Sjkim        this.automaticToExplicitModule = ModuleFinder.of(paths.toArray(new Path[0]))
83238405Sjkim                .findAll().stream()
84238405Sjkim                .map(configuration::toModule)
85238405Sjkim                .collect(toMap(Function.identity(), Function.identity()));
86238405Sjkim
87238405Sjkim        Optional<Module> om = automaticToExplicitModule.keySet().stream()
88238405Sjkim                                    .filter(m -> !m.descriptor().isAutomatic())
89238405Sjkim                                    .findAny();
90238405Sjkim        if (om.isPresent()) {
91280297Sjkim            throw new UncheckedBadArgs(new BadArgs("err.genmoduleinfo.not.jarfile",
92238405Sjkim                                                   om.get().getPathName()));
93238405Sjkim        }
94280297Sjkim        if (automaticToExplicitModule.isEmpty()) {
95280297Sjkim            throw new UncheckedBadArgs(new BadArgs("err.invalid.path", args));
96280297Sjkim        }
97280297Sjkim    }
98238405Sjkim
99238405Sjkim    public boolean run() throws IOException {
100238405Sjkim        try {
101280297Sjkim            // pass 1: find API dependencies
102280297Sjkim            Map<Archive, Set<Archive>> requiresTransitive = computeRequiresTransitive();
103183234Ssimon
104183234Ssimon            // pass 2: analyze all class dependences
105280297Sjkim            dependencyFinder.parse(automaticModules().stream());
106280297Sjkim
107280297Sjkim            analyzer.run(automaticModules(), dependencyFinder.locationToArchive());
108183234Ssimon
109280297Sjkim            boolean missingDeps = false;
110280297Sjkim            for (Module m : automaticModules()) {
111280297Sjkim                Set<Archive> apiDeps = requiresTransitive.containsKey(m)
112183234Ssimon                                            ? requiresTransitive.get(m)
113280297Sjkim                                            : Collections.emptySet();
114280297Sjkim
115280297Sjkim                Path file = outputdir.resolve(m.name()).resolve("module-info.java");
116206046Ssimon
117296279Sjkim                // computes requires and requires transitive
118296279Sjkim                Module explicitModule = toExplicitModule(m, apiDeps);
119296279Sjkim                if (explicitModule != null) {
120296279Sjkim                    automaticToExplicitModule.put(m, explicitModule);
121296279Sjkim
122296279Sjkim                    // generate module-info.java
123296279Sjkim                    System.out.format("writing to %s%n", file);
124296279Sjkim                    writeModuleInfo(file,  explicitModule.descriptor());
125296279Sjkim                } else {
126296279Sjkim                    // find missing dependences
127296279Sjkim                    System.out.format("Missing dependence: %s not generated%n", file);
128296279Sjkim                    missingDeps = true;
129296279Sjkim                }
130296279Sjkim            }
131296279Sjkim
132296279Sjkim            return !missingDeps;
133296279Sjkim        } finally {
134296279Sjkim            dependencyFinder.shutdown();
135296279Sjkim        }
136296279Sjkim    }
137280297Sjkim
138280297Sjkim    boolean notFound(Archive m) {
139280297Sjkim        return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS;
140183234Ssimon    }
141280297Sjkim
142280297Sjkim    private Module toExplicitModule(Module module, Set<Archive> requiresTransitive)
143183234Ssimon        throws IOException
144183234Ssimon    {
145183234Ssimon        // done analysis
146183234Ssimon        module.close();
147183234Ssimon
148183234Ssimon        if (analyzer.requires(module).anyMatch(this::notFound)) {
149183234Ssimon            // missing dependencies
150183234Ssimon            return null;
151183234Ssimon        }
152183234Ssimon
153280297Sjkim        Map<String, Boolean> requires = new HashMap<>();
154183234Ssimon        requiresTransitive.stream()
155280297Sjkim            .map(Archive::getModule)
156280297Sjkim            .forEach(m -> requires.put(m.name(), Boolean.TRUE));
157280297Sjkim
158280297Sjkim        analyzer.requires(module)
159183234Ssimon            .map(Archive::getModule)
160280297Sjkim            .forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE));
161280297Sjkim
162183234Ssimon        return module.toStrictModule(requires);
163280297Sjkim    }
164183234Ssimon
165183234Ssimon    /**
166280297Sjkim     * Returns the stream of resulting modules
167280297Sjkim     */
168280297Sjkim    Stream<Module> modules() {
169280297Sjkim        return automaticToExplicitModule.values().stream();
170183234Ssimon    }
171280297Sjkim
172183234Ssimon    /**
173280297Sjkim     * Returns the stream of resulting ModuleDescriptors
174183234Ssimon     */
175183234Ssimon    public Stream<ModuleDescriptor> descriptors() {
176183234Ssimon        return automaticToExplicitModule.entrySet().stream()
177280297Sjkim                    .map(Map.Entry::getValue)
178183234Ssimon                    .map(Module::descriptor);
179183234Ssimon    }
180183234Ssimon
181280297Sjkim    void visitMissingDeps(Analyzer.Visitor visitor) {
182280297Sjkim        automaticModules().stream()
183280297Sjkim            .filter(m -> analyzer.requires(m).anyMatch(this::notFound))
184280297Sjkim            .forEach(m -> {
185183234Ssimon                analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE);
186183234Ssimon            });
187280297Sjkim    }
188183234Ssimon
189280297Sjkim    void writeModuleInfo(Path file, ModuleDescriptor md) {
190183234Ssimon        try {
191280297Sjkim            Files.createDirectories(file.getParent());
192280297Sjkim            try (PrintWriter pw = new PrintWriter(Files.newOutputStream(file))) {
193280297Sjkim                printModuleInfo(pw, md);
194183234Ssimon            }
195280297Sjkim        } catch (IOException e) {
196280297Sjkim            throw new UncheckedIOException(e);
197280297Sjkim        }
198183234Ssimon    }
199183234Ssimon
200280297Sjkim    private void printModuleInfo(PrintWriter writer, ModuleDescriptor md) {
201280297Sjkim        writer.format("%smodule %s {%n", open ? "open " : "", md.name());
202183234Ssimon
203183234Ssimon        Map<String, Module> modules = configuration.getModules();
204280297Sjkim        // first print the JDK modules
205280297Sjkim        md.requires().stream()
206280297Sjkim          .filter(req -> !req.name().equals("java.base"))   // implicit requires
207280297Sjkim          .sorted(Comparator.comparing(Requires::name))
208280297Sjkim          .forEach(req -> writer.format("    requires %s;%n", req));
209280297Sjkim
210280297Sjkim        if (!open) {
211280297Sjkim            md.exports().stream()
212280297Sjkim              .peek(exp -> {
213280297Sjkim                 if (exp.targets().size() > 0)
214280297Sjkim                    throw new InternalError(md.name() + " qualified exports: " + exp);
215183234Ssimon              })
216183234Ssimon              .sorted(Comparator.comparing(Exports::source))
217280297Sjkim              .forEach(exp -> writer.format("    exports %s;%n", exp.source()));
218183234Ssimon        }
219280297Sjkim
220183234Ssimon        md.provides().stream()
221280297Sjkim          .sorted(Comparator.comparing(Provides::service))
222280297Sjkim          .map(p -> p.providers().stream()
223183234Ssimon                     .map(impl -> "        " + impl.replace('$', '.'))
224183234Ssimon                     .collect(joining(",\n",
225280297Sjkim                                      String.format("    provides %s with%n",
226183234Ssimon                                                    p.service().replace('$', '.')),
227280297Sjkim                                      ";")))
228183234Ssimon          .forEach(writer::println);
229280297Sjkim
230183234Ssimon        writer.println("}");
231280297Sjkim    }
232183234Ssimon
233280297Sjkim    private Set<Module> automaticModules() {
234183234Ssimon        return automaticToExplicitModule.keySet();
235280297Sjkim    }
236280297Sjkim
237280297Sjkim    /**
238280297Sjkim     * Compute 'requires transitive' dependences by analyzing API dependencies
239280297Sjkim     */
240183234Ssimon    private Map<Archive, Set<Archive>> computeRequiresTransitive()
241183234Ssimon        throws IOException
242183234Ssimon    {
243280297Sjkim        // parse the input modules
244280297Sjkim        dependencyFinder.parseExportedAPIs(automaticModules().stream());
245280297Sjkim
246280297Sjkim        return dependencyFinder.dependences();
247183234Ssimon    }
248280297Sjkim}
249280297Sjkim