Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleInfoBuilder.java
41161 views
/*1* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package com.sun.tools.jdeps;2526import static com.sun.tools.jdeps.JdepsTask.*;27import static com.sun.tools.jdeps.Analyzer.*;28import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;2930import java.io.IOException;31import java.io.PrintWriter;32import java.io.UncheckedIOException;33import java.lang.module.ModuleDescriptor;34import java.lang.module.ModuleDescriptor.Exports;35import java.lang.module.ModuleDescriptor.Provides;36import java.lang.module.ModuleDescriptor.Requires;37import java.lang.module.ModuleFinder;38import java.nio.file.Files;39import java.nio.file.Path;40import java.nio.file.Paths;41import java.util.Collections;42import java.util.Comparator;43import java.util.HashMap;44import java.util.List;45import java.util.Locale;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;52import static java.util.stream.Collectors.*;535455public class ModuleInfoBuilder {56final JdepsConfiguration configuration;57final Path outputdir;58final boolean open;5960final DependencyFinder dependencyFinder;61final Analyzer analyzer;6263// an input JAR file (loaded as an automatic module for analysis)64// maps to a normal module to generate module-info.java65final Map<Module, Module> automaticToNormalModule;66public ModuleInfoBuilder(JdepsConfiguration configuration,67List<String> args,68Path outputdir,69boolean open) {70this.configuration = configuration;71this.outputdir = outputdir;72this.open = open;7374this.dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER);75this.analyzer = new Analyzer(configuration, Type.CLASS, DEFAULT_FILTER);7677// add targets to modulepath if it has module-info.class78List<Path> paths = args.stream()79.map(fn -> Paths.get(fn))80.toList();8182// automatic module to convert to normal module83this.automaticToNormalModule = ModuleFinder.of(paths.toArray(new Path[0]))84.findAll().stream()85.map(configuration::toModule)86.collect(toMap(Function.identity(), Function.identity()));8788Optional<Module> om = automaticToNormalModule.keySet().stream()89.filter(m -> !m.descriptor().isAutomatic())90.findAny();91if (om.isPresent()) {92throw new UncheckedBadArgs(new BadArgs("err.genmoduleinfo.not.jarfile",93om.get().getPathName()));94}95if (automaticToNormalModule.isEmpty()) {96throw new UncheckedBadArgs(new BadArgs("err.invalid.path", args));97}98}99100public boolean run(boolean ignoreMissingDeps, PrintWriter log, boolean quiet) throws IOException {101try {102// pass 1: find API dependencies103Map<Archive, Set<Archive>> requiresTransitive = computeRequiresTransitive();104105// pass 2: analyze all class dependences106dependencyFinder.parse(automaticModules().stream());107108analyzer.run(automaticModules(), dependencyFinder.locationToArchive());109110for (Module m : automaticModules()) {111Set<Archive> apiDeps = requiresTransitive.containsKey(m)112? requiresTransitive.get(m)113: Collections.emptySet();114115// if this is a multi-release JAR, write to versions/$VERSION/module-info.java116Runtime.Version version = configuration.getVersion();117Path dir = version != null118? outputdir.resolve(m.name())119.resolve("versions")120.resolve(String.valueOf(version.feature()))121: outputdir.resolve(m.name());122Path file = dir.resolve("module-info.java");123124// computes requires and requires transitive125Module normalModule = toNormalModule(m, apiDeps, ignoreMissingDeps);126if (normalModule != null) {127automaticToNormalModule.put(m, normalModule);128129// generate module-info.java130if (!quiet) {131if (ignoreMissingDeps && analyzer.requires(m).anyMatch(Analyzer::notFound)) {132log.format("Warning: --ignore-missing-deps specified. Missing dependencies from %s are ignored%n",133m.name());134}135log.format("writing to %s%n", file);136}137writeModuleInfo(file, normalModule.descriptor());138} else {139// find missing dependences140return false;141}142}143144} finally {145dependencyFinder.shutdown();146}147return true;148}149150private Module toNormalModule(Module module, Set<Archive> requiresTransitive, boolean ignoreMissingDeps)151throws IOException152{153// done analysis154module.close();155156if (!ignoreMissingDeps && analyzer.requires(module).anyMatch(Analyzer::notFound)) {157// missing dependencies158return null;159}160161Map<String, Boolean> requires = new HashMap<>();162requiresTransitive.stream()163.filter(a -> !(ignoreMissingDeps && Analyzer.notFound(a)))164.map(Archive::getModule)165.forEach(m -> requires.put(m.name(), Boolean.TRUE));166167analyzer.requires(module)168.filter(a -> !(ignoreMissingDeps && Analyzer.notFound(a)))169.map(Archive::getModule)170.forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE));171172return module.toNormalModule(requires);173}174175/**176* Returns the stream of resulting modules177*/178Stream<Module> modules() {179return automaticToNormalModule.values().stream();180}181182/**183* Returns the stream of resulting ModuleDescriptors184*/185public Stream<ModuleDescriptor> descriptors() {186return automaticToNormalModule.entrySet().stream()187.map(Map.Entry::getValue)188.map(Module::descriptor);189}190191void visitMissingDeps(Analyzer.Visitor visitor) {192automaticModules().stream()193.filter(m -> analyzer.requires(m).anyMatch(Analyzer::notFound))194.forEach(m -> {195analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);196});197}198199void writeModuleInfo(Path file, ModuleDescriptor md) {200try {201Files.createDirectories(file.getParent());202try (PrintWriter pw = new PrintWriter(Files.newOutputStream(file))) {203printModuleInfo(pw, md);204}205} catch (IOException e) {206throw new UncheckedIOException(e);207}208}209210private void printModuleInfo(PrintWriter writer, ModuleDescriptor md) {211writer.format("%smodule %s {%n", open ? "open " : "", md.name());212213Map<String, Module> modules = configuration.getModules();214215// first print requires216Set<Requires> reqs = md.requires().stream()217.filter(req -> !req.name().equals("java.base") && req.modifiers().isEmpty())218.collect(Collectors.toSet());219reqs.stream()220.sorted(Comparator.comparing(Requires::name))221.forEach(req -> writer.format(" requires %s;%n",222toString(req.modifiers(), req.name())));223if (!reqs.isEmpty()) {224writer.println();225}226227// requires transitive228reqs = md.requires().stream()229.filter(req -> !req.name().equals("java.base") && !req.modifiers().isEmpty())230.collect(Collectors.toSet());231reqs.stream()232.sorted(Comparator.comparing(Requires::name))233.forEach(req -> writer.format(" requires %s;%n",234toString(req.modifiers(), req.name())));235if (!reqs.isEmpty()) {236writer.println();237}238239if (!open) {240md.exports().stream()241.peek(exp -> {242if (exp.isQualified())243throw new InternalError(md.name() + " qualified exports: " + exp);244})245.sorted(Comparator.comparing(Exports::source))246.forEach(exp -> writer.format(" exports %s;%n", exp.source()));247248if (!md.exports().isEmpty()) {249writer.println();250}251}252253md.provides().stream()254.sorted(Comparator.comparing(Provides::service))255.map(p -> p.providers().stream()256.map(impl -> " " + impl.replace('$', '.'))257.collect(joining(",\n",258String.format(" provides %s with%n",259p.service().replace('$', '.')),260";")))261.forEach(writer::println);262263if (!md.provides().isEmpty()) {264writer.println();265}266writer.println("}");267}268269private Set<Module> automaticModules() {270return automaticToNormalModule.keySet();271}272273/**274* Returns a string containing the given set of modifiers and label.275*/276private static <M> String toString(Set<M> mods, String what) {277return (Stream.concat(mods.stream().map(e -> e.toString().toLowerCase(Locale.US)),278Stream.of(what)))279.collect(Collectors.joining(" "));280}281282/**283* Compute 'requires transitive' dependences by analyzing API dependencies284*/285private Map<Archive, Set<Archive>> computeRequiresTransitive()286throws IOException287{288// parse the input modules289dependencyFinder.parseExportedAPIs(automaticModules().stream());290291return dependencyFinder.dependences();292}293}294295296