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