ModuleInfoBuilder.java revision 3294:9adfb22ff08f
1/* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25package com.sun.tools.jdeps; 26 27import static com.sun.tools.jdeps.Analyzer.Type.CLASS; 28import static com.sun.tools.jdeps.Analyzer.NOT_FOUND; 29import static com.sun.tools.jdeps.Module.trace; 30 31import java.io.IOException; 32import java.io.PrintWriter; 33import java.nio.file.Files; 34import java.nio.file.Path; 35import java.util.HashMap; 36import java.util.Map; 37import java.util.Optional; 38import java.util.Set; 39import java.util.function.Function; 40import java.util.stream.Collectors; 41import java.util.stream.Stream; 42 43public class ModuleInfoBuilder { 44 final ModulePaths modulePaths; 45 final DependencyFinder dependencyFinder; 46 final JdepsFilter filter; 47 final Analyzer analyzer; 48 final Map<Module, Module> strictModules = new HashMap<>(); 49 ModuleInfoBuilder(ModulePaths modulePaths, DependencyFinder finder) { 50 this.modulePaths = modulePaths; 51 this.dependencyFinder = finder; 52 this.filter = new JdepsFilter.Builder().filter(true, true).build(); 53 this.analyzer = new Analyzer(CLASS, filter); 54 } 55 56 private Stream<Module> automaticModules() { 57 return modulePaths.getModules().values() 58 .stream() 59 .filter(Module::isAutomatic); 60 } 61 62 /** 63 * Compute 'requires public' dependences by analyzing API dependencies 64 */ 65 Map<Module, Set<Module>> computeRequiresPublic() throws IOException { 66 dependencyFinder.findDependencies(filter, true /* api only */, 1); 67 Analyzer pass1 = new Analyzer(Analyzer.Type.CLASS, filter); 68 69 pass1.run(dependencyFinder.archives()); 70 71 return automaticModules().collect(Collectors.toMap(Function.identity(), 72 source -> pass1.requires(source) 73 .map(Archive::getModule) 74 .collect(Collectors.toSet()))); 75 } 76 77 boolean run(Analyzer.Type verbose, boolean quiet) throws IOException { 78 // add all automatic modules to the root set 79 automaticModules().forEach(dependencyFinder::addRoot); 80 81 // pass 1: find API dependencies 82 Map<Module, Set<Module>> requiresPublic = computeRequiresPublic(); 83 84 // pass 2: analyze all class dependences 85 dependencyFinder.findDependencies(filter, false /* all classes */, 1); 86 analyzer.run(dependencyFinder.archives()); 87 88 // computes requires and requires public 89 automaticModules().forEach(m -> { 90 Map<String, Boolean> requires; 91 if (requiresPublic.containsKey(m)) { 92 requires = requiresPublic.get(m) 93 .stream() 94 .collect(Collectors.toMap(Archive::getName, (v) -> Boolean.TRUE)); 95 } else { 96 requires = new HashMap<>(); 97 } 98 analyzer.requires(m) 99 .forEach(d -> requires.putIfAbsent(d.getName(), Boolean.FALSE)); 100 101 trace("strict module %s requires %s%n", m.name(), requires); 102 strictModules.put(m, m.toStrictModule(requires)); 103 }); 104 105 // find any missing dependences 106 Optional<Module> missingDeps = automaticModules() 107 .filter(this::missingDep) 108 .findAny(); 109 if (missingDeps.isPresent()) { 110 automaticModules() 111 .filter(this::missingDep) 112 .forEach(m -> { 113 System.err.format("Missing dependencies from %s%n", m.name()); 114 analyzer.visitDependences(m, 115 new Analyzer.Visitor() { 116 @Override 117 public void visitDependence(String origin, Archive originArchive, 118 String target, Archive targetArchive) { 119 if (targetArchive == NOT_FOUND) 120 System.err.format(" %-50s -> %-50s %s%n", 121 origin, target, targetArchive.getName()); 122 } 123 }, verbose); 124 System.err.println(); 125 }); 126 127 System.err.println("ERROR: missing dependencies (check \"requires NOT_FOUND;\")"); 128 } 129 return missingDeps.isPresent() ? false : true; 130 } 131 132 private boolean missingDep(Archive m) { 133 return analyzer.requires(m).filter(a -> a.equals(NOT_FOUND)) 134 .findAny().isPresent(); 135 } 136 137 void build(Path dir) throws IOException { 138 ModuleInfoWriter writer = new ModuleInfoWriter(dir); 139 writer.generateOutput(strictModules.values(), analyzer); 140 } 141 142 private class ModuleInfoWriter { 143 private final Path outputDir; 144 ModuleInfoWriter(Path dir) { 145 this.outputDir = dir; 146 } 147 148 void generateOutput(Iterable<Module> modules, Analyzer analyzer) throws IOException { 149 // generate module-info.java file for each archive 150 for (Module m : modules) { 151 if (m.packages().contains("")) { 152 System.err.format("ERROR: %s contains unnamed package. " + 153 "module-info.java not generated%n", m.getPathName()); 154 continue; 155 } 156 157 String mn = m.getName(); 158 Path srcFile = outputDir.resolve(mn).resolve("module-info.java"); 159 Files.createDirectories(srcFile.getParent()); 160 System.out.println("writing to " + srcFile); 161 try (PrintWriter pw = new PrintWriter(Files.newOutputStream(srcFile))) { 162 printModuleInfo(pw, m); 163 } 164 } 165 } 166 167 private void printModuleInfo(PrintWriter writer, Module m) { 168 writer.format("module %s {%n", m.name()); 169 170 Map<String, Module> modules = modulePaths.getModules(); 171 Map<String, Boolean> requires = m.requires(); 172 // first print the JDK modules 173 requires.keySet().stream() 174 .filter(mn -> !mn.equals("java.base")) // implicit requires 175 .filter(mn -> modules.containsKey(mn) && modules.get(mn).isJDK()) 176 .sorted() 177 .forEach(mn -> { 178 String modifier = requires.get(mn) ? "public " : ""; 179 writer.format(" requires %s%s;%n", modifier, mn); 180 }); 181 182 // print requires non-JDK modules 183 requires.keySet().stream() 184 .filter(mn -> !modules.containsKey(mn) || !modules.get(mn).isJDK()) 185 .sorted() 186 .forEach(mn -> { 187 String modifier = requires.get(mn) ? "public " : ""; 188 writer.format(" requires %s%s;%n", modifier, mn); 189 }); 190 191 m.packages().stream() 192 .sorted() 193 .forEach(pn -> writer.format(" exports %s;%n", pn)); 194 195 m.provides().entrySet().stream() 196 .sorted(Map.Entry.comparingByKey()) 197 .forEach(e -> { 198 String service = e.getKey(); 199 e.getValue().stream() 200 .sorted() 201 .forEach(impl -> writer.format(" provides %s with %s;%n", service, impl)); 202 }); 203 204 writer.println("}"); 205 } 206 } 207} 208