| /* |
| * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package com.sun.tools.jdeps; |
| |
| import static com.sun.tools.jdeps.JdepsTask.*; |
| import static com.sun.tools.jdeps.Analyzer.*; |
| import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.UncheckedIOException; |
| import java.lang.module.ModuleDescriptor; |
| import java.lang.module.ModuleDescriptor.Exports; |
| import java.lang.module.ModuleDescriptor.Provides; |
| import java.lang.module.ModuleDescriptor.Requires; |
| import java.lang.module.ModuleFinder; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.Function; |
| import java.util.stream.Stream; |
| import static java.util.stream.Collectors.*; |
| |
| |
| public class ModuleInfoBuilder { |
| final JdepsConfiguration configuration; |
| final Path outputdir; |
| final boolean open; |
| |
| final DependencyFinder dependencyFinder; |
| final Analyzer analyzer; |
| |
| // an input JAR file (loaded as an automatic module for analysis) |
| // maps to an explicit module to generate module-info.java |
| final Map<Module, Module> automaticToExplicitModule; |
| public ModuleInfoBuilder(JdepsConfiguration configuration, |
| List<String> args, |
| Path outputdir, |
| boolean open) { |
| this.configuration = configuration; |
| this.outputdir = outputdir; |
| this.open = open; |
| |
| this.dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER); |
| this.analyzer = new Analyzer(configuration, Type.CLASS, DEFAULT_FILTER); |
| |
| // add targets to modulepath if it has module-info.class |
| List<Path> paths = args.stream() |
| .map(fn -> Paths.get(fn)) |
| .collect(toList()); |
| |
| // automatic module to convert to explicit module |
| this.automaticToExplicitModule = ModuleFinder.of(paths.toArray(new Path[0])) |
| .findAll().stream() |
| .map(configuration::toModule) |
| .collect(toMap(Function.identity(), Function.identity())); |
| |
| Optional<Module> om = automaticToExplicitModule.keySet().stream() |
| .filter(m -> !m.descriptor().isAutomatic()) |
| .findAny(); |
| if (om.isPresent()) { |
| throw new UncheckedBadArgs(new BadArgs("err.genmoduleinfo.not.jarfile", |
| om.get().getPathName())); |
| } |
| if (automaticToExplicitModule.isEmpty()) { |
| throw new UncheckedBadArgs(new BadArgs("err.invalid.path", args)); |
| } |
| } |
| |
| public boolean run() throws IOException { |
| try { |
| // pass 1: find API dependencies |
| Map<Archive, Set<Archive>> requiresTransitive = computeRequiresTransitive(); |
| |
| // pass 2: analyze all class dependences |
| dependencyFinder.parse(automaticModules().stream()); |
| |
| analyzer.run(automaticModules(), dependencyFinder.locationToArchive()); |
| |
| boolean missingDeps = false; |
| for (Module m : automaticModules()) { |
| Set<Archive> apiDeps = requiresTransitive.containsKey(m) |
| ? requiresTransitive.get(m) |
| : Collections.emptySet(); |
| |
| Path file = outputdir.resolve(m.name()).resolve("module-info.java"); |
| |
| // computes requires and requires transitive |
| Module explicitModule = toExplicitModule(m, apiDeps); |
| if (explicitModule != null) { |
| automaticToExplicitModule.put(m, explicitModule); |
| |
| // generate module-info.java |
| System.out.format("writing to %s%n", file); |
| writeModuleInfo(file, explicitModule.descriptor()); |
| } else { |
| // find missing dependences |
| System.out.format("Missing dependence: %s not generated%n", file); |
| missingDeps = true; |
| } |
| } |
| |
| return !missingDeps; |
| } finally { |
| dependencyFinder.shutdown(); |
| } |
| } |
| |
| boolean notFound(Archive m) { |
| return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS; |
| } |
| |
| private Module toExplicitModule(Module module, Set<Archive> requiresTransitive) |
| throws IOException |
| { |
| // done analysis |
| module.close(); |
| |
| if (analyzer.requires(module).anyMatch(this::notFound)) { |
| // missing dependencies |
| return null; |
| } |
| |
| Map<String, Boolean> requires = new HashMap<>(); |
| requiresTransitive.stream() |
| .map(Archive::getModule) |
| .forEach(m -> requires.put(m.name(), Boolean.TRUE)); |
| |
| analyzer.requires(module) |
| .map(Archive::getModule) |
| .forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE)); |
| |
| return module.toStrictModule(requires); |
| } |
| |
| /** |
| * Returns the stream of resulting modules |
| */ |
| Stream<Module> modules() { |
| return automaticToExplicitModule.values().stream(); |
| } |
| |
| /** |
| * Returns the stream of resulting ModuleDescriptors |
| */ |
| public Stream<ModuleDescriptor> descriptors() { |
| return automaticToExplicitModule.entrySet().stream() |
| .map(Map.Entry::getValue) |
| .map(Module::descriptor); |
| } |
| |
| void visitMissingDeps(Analyzer.Visitor visitor) { |
| automaticModules().stream() |
| .filter(m -> analyzer.requires(m).anyMatch(this::notFound)) |
| .forEach(m -> { |
| analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE); |
| }); |
| } |
| |
| void writeModuleInfo(Path file, ModuleDescriptor md) { |
| try { |
| Files.createDirectories(file.getParent()); |
| try (PrintWriter pw = new PrintWriter(Files.newOutputStream(file))) { |
| printModuleInfo(pw, md); |
| } |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| private void printModuleInfo(PrintWriter writer, ModuleDescriptor md) { |
| writer.format("%smodule %s {%n", open ? "open " : "", md.name()); |
| |
| Map<String, Module> modules = configuration.getModules(); |
| // first print the JDK modules |
| md.requires().stream() |
| .filter(req -> !req.name().equals("java.base")) // implicit requires |
| .sorted(Comparator.comparing(Requires::name)) |
| .forEach(req -> writer.format(" requires %s;%n", req)); |
| |
| if (!open) { |
| md.exports().stream() |
| .peek(exp -> { |
| if (exp.targets().size() > 0) |
| throw new InternalError(md.name() + " qualified exports: " + exp); |
| }) |
| .sorted(Comparator.comparing(Exports::source)) |
| .forEach(exp -> writer.format(" exports %s;%n", exp.source())); |
| } |
| |
| md.provides().stream() |
| .sorted(Comparator.comparing(Provides::service)) |
| .map(p -> p.providers().stream() |
| .map(impl -> " " + impl.replace('$', '.')) |
| .collect(joining(",\n", |
| String.format(" provides %s with%n", |
| p.service().replace('$', '.')), |
| ";"))) |
| .forEach(writer::println); |
| |
| writer.println("}"); |
| } |
| |
| private Set<Module> automaticModules() { |
| return automaticToExplicitModule.keySet(); |
| } |
| |
| /** |
| * Compute 'requires transitive' dependences by analyzing API dependencies |
| */ |
| private Map<Archive, Set<Archive>> computeRequiresTransitive() |
| throws IOException |
| { |
| // parse the input modules |
| dependencyFinder.parseExportedAPIs(automaticModules().stream()); |
| |
| return dependencyFinder.dependences(); |
| } |
| } |