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