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 */ 25 package com.sun.tools.jdeps; 26 27 import static com.sun.tools.jdeps.JdepsTask.*; 28 import static com.sun.tools.jdeps.Analyzer.*; 29 import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER; 30 31 import java.io.IOException; 32 import java.io.PrintWriter; 33 import java.io.UncheckedIOException; 34 import java.lang.module.ModuleDescriptor; 35 import java.lang.module.ModuleDescriptor.Exports; 36 import java.lang.module.ModuleDescriptor.Provides; 37 import java.lang.module.ModuleDescriptor.Requires; 38 import java.lang.module.ModuleFinder; 39 import java.nio.file.Files; 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.util.Collections; 43 import java.util.Comparator; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Optional; 48 import java.util.Set; 49 import java.util.function.Function; 50 import java.util.stream.Stream; 51 import static java.util.stream.Collectors.*; 52 53 54 public class ModuleInfoBuilder { 55 final JdepsConfiguration configuration; 56 final Path outputdir; 57 final boolean open; 58 59 final DependencyFinder dependencyFinder; 60 final Analyzer analyzer; 61 62 // an input JAR file (loaded as an automatic module for analysis) 63 // maps to an explicit module to generate module-info.java 64 final Map<Module, Module> automaticToExplicitModule; 65 public ModuleInfoBuilder(JdepsConfiguration configuration, 66 List<String> args, 67 Path outputdir, 68 boolean open) { 69 this.configuration = configuration; 70 this.outputdir = outputdir; 71 this.open = open; 72 73 this.dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER); 74 this.analyzer = new Analyzer(configuration, Type.CLASS, DEFAULT_FILTER); 75 76 // add targets to modulepath if it has module-info.class 77 List<Path> paths = args.stream() 78 .map(fn -> Paths.get(fn)) 79 .collect(toList()); 80 81 // automatic module to convert to explicit module 82 this.automaticToExplicitModule = ModuleFinder.of(paths.toArray(new Path[0])) 83 .findAll().stream() 84 .map(configuration::toModule) 85 .collect(toMap(Function.identity(), Function.identity())); 86 87 Optional<Module> om = automaticToExplicitModule.keySet().stream() 88 .filter(m -> !m.descriptor().isAutomatic()) 89 .findAny(); 90 if (om.isPresent()) { 91 throw new UncheckedBadArgs(new BadArgs("err.genmoduleinfo.not.jarfile", 92 om.get().getPathName())); 93 } 94 if (automaticToExplicitModule.isEmpty()) { 95 throw new UncheckedBadArgs(new BadArgs("err.invalid.path", args)); 96 } 97 } 98 99 public boolean run() throws IOException { 100 try { 101 // pass 1: find API dependencies 102 Map<Archive, Set<Archive>> requiresTransitive = computeRequiresTransitive(); 103 104 // pass 2: analyze all class dependences 105 dependencyFinder.parse(automaticModules().stream()); 106 107 analyzer.run(automaticModules(), dependencyFinder.locationToArchive()); 108 109 boolean missingDeps = false; 110 for (Module m : automaticModules()) { 111 Set<Archive> apiDeps = requiresTransitive.containsKey(m) 112 ? requiresTransitive.get(m) 113 : Collections.emptySet(); 114 115 Path file = outputdir.resolve(m.name()).resolve("module-info.java"); 116 117 // computes requires and requires transitive 118 Module explicitModule = toExplicitModule(m, apiDeps); 119 if (explicitModule != null) { 120 automaticToExplicitModule.put(m, explicitModule); 121 122 // generate module-info.java 123 System.out.format("writing to %s%n", file); 124 writeModuleInfo(file, explicitModule.descriptor()); 125 } else { 126 // find missing dependences 127 System.out.format("Missing dependence: %s not generated%n", file); 128 missingDeps = true; 129 } 130 } 131 132 return !missingDeps; 133 } finally { 134 dependencyFinder.shutdown(); 135 } 136 } 137 138 boolean notFound(Archive m) { 139 return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS; 140 } 141 142 private Module toExplicitModule(Module module, Set<Archive> requiresTransitive) 143 throws IOException 144 { 145 // done analysis 146 module.close(); 147 148 if (analyzer.requires(module).anyMatch(this::notFound)) { 149 // missing dependencies 150 return null; 151 } 152 153 Map<String, Boolean> requires = new HashMap<>(); 154 requiresTransitive.stream() 155 .map(Archive::getModule) 156 .forEach(m -> requires.put(m.name(), Boolean.TRUE)); 157 158 analyzer.requires(module) 159 .map(Archive::getModule) 160 .forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE)); 161 162 return module.toStrictModule(requires); 163 } 164 165 /** 166 * Returns the stream of resulting modules 167 */ 168 Stream<Module> modules() { 169 return automaticToExplicitModule.values().stream(); 170 } 171 172 /** 173 * Returns the stream of resulting ModuleDescriptors 174 */ 175 public Stream<ModuleDescriptor> descriptors() { 176 return automaticToExplicitModule.entrySet().stream() 177 .map(Map.Entry::getValue) 178 .map(Module::descriptor); 179 } 180 181 void visitMissingDeps(Analyzer.Visitor visitor) { 182 automaticModules().stream() 183 .filter(m -> analyzer.requires(m).anyMatch(this::notFound)) 184 .forEach(m -> { 185 analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE); 186 }); 187 } 188 189 void writeModuleInfo(Path file, ModuleDescriptor md) { 190 try { 191 Files.createDirectories(file.getParent()); 192 try (PrintWriter pw = new PrintWriter(Files.newOutputStream(file))) { 193 printModuleInfo(pw, md); 194 } 195 } catch (IOException e) { 196 throw new UncheckedIOException(e); 197 } 198 } 199 200 private void printModuleInfo(PrintWriter writer, ModuleDescriptor md) { 201 writer.format("%smodule %s {%n", open ? "open " : "", md.name()); 202 203 Map<String, Module> modules = configuration.getModules(); 204 // first print the JDK modules 205 md.requires().stream() 206 .filter(req -> !req.name().equals("java.base")) // implicit requires 207 .sorted(Comparator.comparing(Requires::name)) 208 .forEach(req -> writer.format(" requires %s;%n", req)); 209 210 if (!open) { 211 md.exports().stream() 212 .peek(exp -> { 213 if (exp.targets().size() > 0) 214 throw new InternalError(md.name() + " qualified exports: " + exp); 215 }) 216 .sorted(Comparator.comparing(Exports::source)) 217 .forEach(exp -> writer.format(" exports %s;%n", exp.source())); 218 } 219 220 md.provides().stream() 221 .sorted(Comparator.comparing(Provides::service)) 222 .map(p -> p.providers().stream() 223 .map(impl -> " " + impl.replace('$', '.')) 224 .collect(joining(",\n", 225 String.format(" provides %s with%n", 226 p.service().replace('$', '.')), 227 ";"))) 228 .forEach(writer::println); 229 230 writer.println("}"); 231 } 232 233 private Set<Module> automaticModules() { 234 return automaticToExplicitModule.keySet(); 235 } 236 237 /** 238 * Compute 'requires transitive' dependences by analyzing API dependencies 239 */ 240 private Map<Archive, Set<Archive>> computeRequiresTransitive() 241 throws IOException 242 { 243 // parse the input modules 244 dependencyFinder.parseExportedAPIs(automaticModules().stream()); 245 246 return dependencyFinder.dependences(); 247 } 248 }