blob: 1afed6bfa77d8c3e6148c65ce5136a224e4a7bb4 [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.Analyzer.Type.CLASS;
import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;
import static com.sun.tools.jdeps.Module.trace;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ModuleInfoBuilder {
final ModulePaths modulePaths;
final DependencyFinder dependencyFinder;
final JdepsFilter filter;
final Analyzer analyzer;
final Map<Module, Module> strictModules = new HashMap<>();
ModuleInfoBuilder(ModulePaths modulePaths, DependencyFinder finder) {
this.modulePaths = modulePaths;
this.dependencyFinder = finder;
this.filter = new JdepsFilter.Builder().filter(true, true).build();
this.analyzer = new Analyzer(CLASS, filter);
}
private Stream<Module> automaticModules() {
return modulePaths.getModules().values()
.stream()
.filter(Module::isAutomatic);
}
/**
* Compute 'requires public' dependences by analyzing API dependencies
*/
Map<Module, Set<Module>> computeRequiresPublic() throws IOException {
dependencyFinder.findDependencies(filter, true /* api only */, 1);
Analyzer pass1 = new Analyzer(Analyzer.Type.CLASS, filter);
pass1.run(dependencyFinder.archives());
return automaticModules().collect(Collectors.toMap(Function.identity(),
source -> pass1.requires(source)
.map(Archive::getModule)
.collect(Collectors.toSet())));
}
boolean run(Analyzer.Type verbose, boolean quiet) throws IOException {
// add all automatic modules to the root set
automaticModules().forEach(dependencyFinder::addRoot);
// pass 1: find API dependencies
Map<Module, Set<Module>> requiresPublic = computeRequiresPublic();
// pass 2: analyze all class dependences
dependencyFinder.findDependencies(filter, false /* all classes */, 1);
analyzer.run(dependencyFinder.archives());
// computes requires and requires public
automaticModules().forEach(m -> {
Map<String, Boolean> requires;
if (requiresPublic.containsKey(m)) {
requires = requiresPublic.get(m)
.stream()
.collect(Collectors.toMap(Archive::getName, (v) -> Boolean.TRUE));
} else {
requires = new HashMap<>();
}
analyzer.requires(m)
.forEach(d -> requires.putIfAbsent(d.getName(), Boolean.FALSE));
trace("strict module %s requires %s%n", m.name(), requires);
strictModules.put(m, m.toStrictModule(requires));
});
// find any missing dependences
Optional<Module> missingDeps = automaticModules()
.filter(this::missingDep)
.findAny();
if (missingDeps.isPresent()) {
automaticModules()
.filter(this::missingDep)
.forEach(m -> {
System.err.format("Missing dependencies from %s%n", m.name());
analyzer.visitDependences(m,
new Analyzer.Visitor() {
@Override
public void visitDependence(String origin, Archive originArchive,
String target, Archive targetArchive) {
if (targetArchive == NOT_FOUND)
System.err.format(" %-50s -> %-50s %s%n",
origin, target, targetArchive.getName());
}
}, verbose);
System.err.println();
});
System.err.println("ERROR: missing dependencies (check \"requires NOT_FOUND;\")");
}
return missingDeps.isPresent() ? false : true;
}
private boolean missingDep(Archive m) {
return analyzer.requires(m).filter(a -> a.equals(NOT_FOUND))
.findAny().isPresent();
}
void build(Path dir) throws IOException {
ModuleInfoWriter writer = new ModuleInfoWriter(dir);
writer.generateOutput(strictModules.values(), analyzer);
}
private class ModuleInfoWriter {
private final Path outputDir;
ModuleInfoWriter(Path dir) {
this.outputDir = dir;
}
void generateOutput(Iterable<Module> modules, Analyzer analyzer) throws IOException {
// generate module-info.java file for each archive
for (Module m : modules) {
if (m.packages().contains("")) {
System.err.format("ERROR: %s contains unnamed package. " +
"module-info.java not generated%n", m.getPathName());
continue;
}
String mn = m.getName();
Path srcFile = outputDir.resolve(mn).resolve("module-info.java");
Files.createDirectories(srcFile.getParent());
System.out.println("writing to " + srcFile);
try (PrintWriter pw = new PrintWriter(Files.newOutputStream(srcFile))) {
printModuleInfo(pw, m);
}
}
}
private void printModuleInfo(PrintWriter writer, Module m) {
writer.format("module %s {%n", m.name());
Map<String, Module> modules = modulePaths.getModules();
Map<String, Boolean> requires = m.requires();
// first print the JDK modules
requires.keySet().stream()
.filter(mn -> !mn.equals("java.base")) // implicit requires
.filter(mn -> modules.containsKey(mn) && modules.get(mn).isJDK())
.sorted()
.forEach(mn -> {
String modifier = requires.get(mn) ? "public " : "";
writer.format(" requires %s%s;%n", modifier, mn);
});
// print requires non-JDK modules
requires.keySet().stream()
.filter(mn -> !modules.containsKey(mn) || !modules.get(mn).isJDK())
.sorted()
.forEach(mn -> {
String modifier = requires.get(mn) ? "public " : "";
writer.format(" requires %s%s;%n", modifier, mn);
});
m.packages().stream()
.sorted()
.forEach(pn -> writer.format(" exports %s;%n", pn));
m.provides().entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> {
String service = e.getKey();
e.getValue().stream()
.sorted()
.forEach(impl -> writer.format(" provides %s with %s;%n", service, impl));
});
writer.println("}");
}
}
}