langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleInfoBuilder.java
changeset 36526 3b41f1c69604
child 38524 badd925c1d2f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleInfoBuilder.java	Thu Mar 17 19:04:28 2016 +0000
@@ -0,0 +1,207 @@
+/*
+ * 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("}");
+        }
+    }
+}