1 /* 2 * Copyright (c) 2014, 2016, 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 26 package build.tools.jigsaw; 27 28 import java.io.IOException; 29 import java.io.PrintStream; 30 import java.lang.module.Configuration; 31 import java.lang.module.ModuleDescriptor; 32 import java.lang.module.ModuleFinder; 33 import java.lang.module.ModuleReference; 34 import java.lang.module.ResolvedModule; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.nio.file.Paths; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.TreeSet; 46 import java.util.function.Function; 47 import java.util.stream.Collectors; 48 import static java.lang.module.ModuleDescriptor.Requires.Modifier.TRANSITIVE; 49 50 /** 51 * Generate the DOT file for a module graph for each module in the JDK 52 * after transitive reduction. 53 */ 54 public class GenGraphs { 55 56 public static void main(String[] args) throws Exception { 57 58 if (args.length != 1) { 59 System.err.println("ERROR: specify the output directory"); 60 System.exit(1); 61 } 62 Path dir = Paths.get(args[0]); 63 Files.createDirectories(dir); 64 65 ModuleFinder finder = ModuleFinder.ofSystem(); 66 67 Set<ModuleDescriptor> javaSEModules 68 = new TreeSet<>(finder.findAll().stream() 69 .map(ModuleReference::descriptor) 70 .filter(m -> (m.name().startsWith("java.") && 71 !m.name().equals("java.smartcardio"))) 72 .collect(Collectors.toSet())); 73 Set<ModuleDescriptor> jdkModules 74 = new TreeSet<>(finder.findAll().stream() 75 .map(ModuleReference::descriptor) 76 .filter(m -> !javaSEModules.contains(m)) 77 .collect(Collectors.toSet())); 78 79 GenGraphs genGraphs = new GenGraphs(javaSEModules, jdkModules); 80 Set<String> mods = new HashSet<>(); 81 for (ModuleReference mref: finder.findAll()) { 82 ModuleDescriptor descriptor = mref.descriptor(); 83 String name = descriptor.name(); 84 mods.add(name); 85 Configuration cf = Configuration.empty() 86 .resolveRequires(finder, 87 ModuleFinder.of(), 88 Set.of(name)); 89 genGraphs.genDotFile(dir, name, cf); 90 } 91 92 Configuration cf = Configuration.empty() 93 .resolveRequires(finder, 94 ModuleFinder.of(), 95 mods); 96 genGraphs.genDotFile(dir, "jdk", cf); 97 98 } 99 100 private final Set<ModuleDescriptor> javaGroup; 101 private final Set<ModuleDescriptor> jdkGroup; 102 103 GenGraphs(Set<ModuleDescriptor> javaGroup, Set<ModuleDescriptor> jdkGroup) { 104 this.javaGroup = Collections.unmodifiableSet(javaGroup); 105 this.jdkGroup = Collections.unmodifiableSet(jdkGroup); 106 } 107 108 private static final String ORANGE = "#e76f00"; 109 private static final String BLUE = "#437291"; 110 private static final String GRAY = "#dddddd"; 111 112 private static final String REEXPORTS = ""; 113 private static final String REQUIRES = "style=\"dashed\""; 114 private static final String REQUIRES_BASE = "color=\"" + GRAY + "\""; 115 116 private static final Map<String,Integer> weights = new HashMap<>(); 117 private static final List<Set<String>> ranks = new ArrayList<>(); 118 119 private static void weight(String s, String t, int w) { 120 weights.put(s + ":" + t, w); 121 } 122 123 private static int weightOf(String s, String t) { 124 int w = weights.getOrDefault(s + ":" + t, 1); 125 if (w != 1) 126 return w; 127 if (s.startsWith("java.") && t.startsWith("java.")) 128 return 10; 129 return 1; 130 } 131 132 static { 133 int h = 1000; 134 weight("java.se", "java.sql.rowset", h * 10); 135 weight("java.sql.rowset", "java.sql", h * 10); 136 weight("java.sql", "java.xml", h * 10); 137 weight("java.xml", "java.base", h * 10); 138 139 ranks.add(Set.of("java.logging", "java.scripting", "java.xml")); 140 ranks.add(Set.of("java.sql")); 141 ranks.add(Set.of("java.compiler", "java.instrument")); 142 ranks.add(Set.of("java.desktop", "java.management")); 143 ranks.add(Set.of("java.corba", "java.xml.ws")); 144 ranks.add(Set.of("java.xml.bind", "java.annotations.common")); 145 146 } 147 148 private void genDotFile(Path dir, String name, Configuration cf) throws IOException { 149 try (PrintStream out 150 = new PrintStream(Files.newOutputStream(dir.resolve(name + ".dot")))) { 151 152 Map<String, ModuleDescriptor> nameToModule; 153 if (name.equals("java.se.ee")) { 154 nameToModule = cf.modules().stream() 155 .map(ResolvedModule::reference) 156 .map(ModuleReference::descriptor) 157 .filter(md -> !md.name().startsWith("jdk.")) 158 .collect(Collectors.toMap(ModuleDescriptor::name, Function.identity())); 159 } else { 160 nameToModule = cf.modules().stream() 161 .map(ResolvedModule::reference) 162 .map(ModuleReference::descriptor) 163 .collect(Collectors.toMap(ModuleDescriptor::name, Function.identity())); 164 } 165 Set<ModuleDescriptor> descriptors = new TreeSet<>(nameToModule.values()); 166 167 out.format("digraph \"%s\" {%n", name); 168 out.format("size=\"25,25\";"); 169 out.format("nodesep=.5;%n"); 170 out.format("ranksep=1.5;%n"); 171 out.format("pencolor=transparent;%n"); 172 out.format("node [shape=plaintext, fontname=\"DejaVuSans\", fontsize=36, margin=\".2,.2\"];%n"); 173 out.format("edge [penwidth=4, color=\"#999999\", arrowhead=open, arrowsize=2];%n"); 174 175 out.format("subgraph %sse {%n", name.equals("jdk") ? "cluster_" : ""); 176 descriptors.stream() 177 .filter(javaGroup::contains) 178 .map(ModuleDescriptor::name) 179 .forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n", 180 mn, ORANGE, "java")); 181 out.format("}%n"); 182 183 // same ranks 184 ranks.stream() 185 .forEach(group -> out.format("{rank=same %s}%n", 186 descriptors.stream() 187 .map(ModuleDescriptor::name) 188 .filter(group::contains) 189 .map(mn -> "\"" + mn + "\"") 190 .collect(Collectors.joining(",")) 191 )); 192 193 descriptors.stream() 194 .filter(jdkGroup::contains) 195 .map(ModuleDescriptor::name) 196 .forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n", 197 mn, BLUE, "jdk")); 198 199 // transitive reduction 200 Graph<String> graph = gengraph(cf); 201 descriptors.forEach(md -> { 202 String mn = md.name(); 203 Set<String> requiresTransitive = md.requires().stream() 204 .filter(d -> d.modifiers().contains(TRANSITIVE)) 205 .map(d -> d.name()) 206 .collect(Collectors.toSet()); 207 208 graph.adjacentNodes(mn) 209 .stream() 210 .filter(nameToModule::containsKey) 211 .forEach(dn -> { 212 String attr = dn.equals("java.base") ? REQUIRES_BASE 213 : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES); 214 int w = weightOf(mn, dn); 215 if (w > 1) 216 attr += "weight=" + w; 217 out.format(" \"%s\" -> \"%s\" [%s];%n", mn, dn, attr); 218 }); 219 }); 220 221 out.println("}"); 222 } 223 } 224 225 /** 226 * Returns a Graph of the given Configuration after transitive reduction. 227 * 228 * Transitive reduction of requires transitive edge and requires edge have 229 * to be applied separately to prevent the requires transitive edges 230 * (e.g. U -> V) from being reduced by a path (U -> X -> Y -> V) 231 * in which V would not be re-exported from U. 232 */ 233 private Graph<String> gengraph(Configuration cf) { 234 Graph.Builder<String> builder = new Graph.Builder<>(); 235 for (ResolvedModule resolvedModule : cf.modules()) { 236 String mn = resolvedModule.reference().descriptor().name(); 237 builder.addNode(mn); 238 resolvedModule.reads().stream() 239 .map(ResolvedModule::name) 240 .forEach(target -> builder.addEdge(mn, target)); 241 } 242 Graph<String> rpg = requiresTransitiveGraph(cf); 243 return builder.build().reduce(rpg); 244 } 245 246 /** 247 * Returns a Graph containing only requires transitive edges 248 * with transitive reduction. 249 */ 250 private Graph<String> requiresTransitiveGraph(Configuration cf) { 251 Graph.Builder<String> builder = new Graph.Builder<>(); 252 for (ResolvedModule resolvedModule : cf.modules()) { 253 ModuleDescriptor descriptor = resolvedModule.reference().descriptor(); 254 String mn = descriptor.name(); 255 descriptor.requires().stream() 256 .filter(d -> d.modifiers().contains(TRANSITIVE)) 257 .map(d -> d.name()) 258 .forEach(d -> builder.addEdge(mn, d)); 259 } 260 return builder.build().reduce(); 261 } 262 }