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