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 boolean notFound(Archive m) { 141 return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS; 142 } 143 144 private Module toNormalModule(Module module, Set<Archive> requiresTransitive) 145 throws IOException 146 { 147 // done analysis 148 module.close(); 149 150 if (analyzer.requires(module).anyMatch(this::notFound)) { 151 // missing dependencies 152 return null; 153 } 154 155 Map<String, Boolean> requires = new HashMap<>(); 156 requiresTransitive.stream() 157 .map(Archive::getModule) 158 .forEach(m -> requires.put(m.name(), Boolean.TRUE)); 159 160 analyzer.requires(module) 161 .map(Archive::getModule) 162 .forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE)); 163 164 return module.toNormalModule(requires); 165 } 166 167 /** 168 * Returns the stream of resulting modules 169 */ 170 Stream<Module> modules() { 171 return automaticToNormalModule.values().stream(); 172 } 173 174 /** 175 * Returns the stream of resulting ModuleDescriptors 176 */ 177 public Stream<ModuleDescriptor> descriptors() { 178 return automaticToNormalModule.entrySet().stream() 179 .map(Map.Entry::getValue) 180 .map(Module::descriptor); 181 } 182 183 void visitMissingDeps(Analyzer.Visitor visitor) { 184 automaticModules().stream() 185 .filter(m -> analyzer.requires(m).anyMatch(this::notFound)) 186 .forEach(m -> { 187 analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE); 188 }); 189 } 190 191 void writeModuleInfo(Path file, ModuleDescriptor md) { 192 try { 193 Files.createDirectories(file.getParent()); 194 try (PrintWriter pw = new PrintWriter(Files.newOutputStream(file))) { 195 printModuleInfo(pw, md); 196 } 197 } catch (IOException e) { 198 throw new UncheckedIOException(e); 199 } 200 } 201 202 private void printModuleInfo(PrintWriter writer, ModuleDescriptor md) { 203 writer.format("%smodule %s {%n", open ? "open " : "", md.name()); 204 205 Map<String, Module> modules = configuration.getModules(); 206 // first print the JDK modules 207 md.requires().stream() 208 .filter(req -> !req.name().equals("java.base")) // implicit requires 209 .sorted(Comparator.comparing(Requires::name)) 210 .forEach(req -> writer.format(" requires %s;%n", 211 toString(req.modifiers(), req.name()))); 212 213 if (!open) { 214 md.exports().stream() 215 .peek(exp -> { 216 if (exp.isQualified()) 217 throw new InternalError(md.name() + " qualified exports: " + exp); 218 }) 219 .sorted(Comparator.comparing(Exports::source)) 220 .forEach(exp -> writer.format(" exports %s;%n", exp.source())); 221 } 222 223 md.provides().stream() 224 .sorted(Comparator.comparing(Provides::service)) 225 .map(p -> p.providers().stream() 226 .map(impl -> " " + impl.replace('$', '.')) 227 .collect(joining(",\n", 228 String.format(" provides %s with%n", 229 p.service().replace('$', '.')), 230 ";"))) 231 .forEach(writer::println); 232 233 writer.println("}"); 234 } 235 236 private Set<Module> automaticModules() { 237 return automaticToNormalModule.keySet(); 238 } 239 240 /** 241 * Returns a string containing the given set of modifiers and label. 242 */ 243 private static <M> String toString(Set<M> mods, String what) { 244 return (Stream.concat(mods.stream().map(e -> e.toString().toLowerCase(Locale.US)), 245 Stream.of(what))) 246 .collect(Collectors.joining(" ")); 247 } 248 249 /** 250 * Compute 'requires transitive' dependences by analyzing API dependencies 251 */ 252 private Map<Archive, Set<Archive>> computeRequiresTransitive() 253 throws IOException 254 { 255 // parse the input modules 256 dependencyFinder.parseExportedAPIs(automaticModules().stream()); 257 258 return dependencyFinder.dependences(); 259 } 260 }