blob: 06a31cd6f45ddd9aae1a3bc8a76754dbd40b2fde [file] [log] [blame]
/*
* 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();
}
}