JdepsWriter.java revision 3294:9adfb22ff08f
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 */
25package com.sun.tools.jdeps;
26
27import java.io.IOException;
28import java.io.PrintWriter;
29import java.io.UncheckedIOException;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.util.Collection;
33import java.util.HashMap;
34import java.util.Map;
35
36import static com.sun.tools.jdeps.Analyzer.Type.*;
37
38public abstract class JdepsWriter {
39    final Analyzer.Type type;
40    final boolean showProfile;
41    final boolean showModule;
42
43    JdepsWriter(Analyzer.Type type, boolean showProfile, boolean showModule) {
44        this.type = type;
45        this.showProfile = showProfile;
46        this.showModule = showModule;
47    }
48
49    abstract void generateOutput(Collection<Archive> archives, Analyzer analyzer) throws IOException;
50
51    public static class DotFileWriter extends JdepsWriter {
52        final boolean showLabel;
53        final Path outputDir;
54        DotFileWriter(Path dir, Analyzer.Type type,
55                      boolean showProfile, boolean showModule, boolean showLabel) {
56            super(type, showProfile, showModule);
57            this.showLabel = showLabel;
58            this.outputDir = dir;
59        }
60
61        @Override
62        void generateOutput(Collection<Archive> archives, Analyzer analyzer)
63                throws IOException
64        {
65            // output individual .dot file for each archive
66            if (type != SUMMARY) {
67                archives.stream()
68                        .filter(analyzer::hasDependences)
69                        .forEach(archive -> {
70                            Path dotfile = outputDir.resolve(archive.getName() + ".dot");
71                            try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
72                                 DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
73                                analyzer.visitDependences(archive, formatter);
74                            } catch (IOException e) {
75                                throw new UncheckedIOException(e);
76                            }
77                        });
78            }
79            // generate summary dot file
80            generateSummaryDotFile(archives, analyzer);
81        }
82
83        private void generateSummaryDotFile(Collection<Archive> archives, Analyzer analyzer)
84                throws IOException
85        {
86            // If verbose mode (-v or -verbose option),
87            // the summary.dot file shows package-level dependencies.
88            Analyzer.Type summaryType =
89                (type == PACKAGE || type == SUMMARY) ? SUMMARY : PACKAGE;
90            Path summary = outputDir.resolve("summary.dot");
91            try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
92                 SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {
93                for (Archive archive : archives) {
94                    if (type == PACKAGE || type == SUMMARY) {
95                        if (showLabel) {
96                            // build labels listing package-level dependencies
97                            analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);
98                        }
99                    }
100                    analyzer.visitDependences(archive, dotfile, summaryType);
101                }
102            }
103        }
104
105        class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
106            private final PrintWriter writer;
107            private final String name;
108            DotFileFormatter(PrintWriter writer, Archive archive) {
109                this.writer = writer;
110                this.name = archive.getName();
111                writer.format("digraph \"%s\" {%n", name);
112                writer.format("    // Path: %s%n", archive.getPathName());
113            }
114
115            @Override
116            public void close() {
117                writer.println("}");
118            }
119
120            @Override
121            public void visitDependence(String origin, Archive originArchive,
122                                        String target, Archive targetArchive) {
123                String tag = toTag(originArchive, target, targetArchive);
124                writer.format("   %-50s -> \"%s\";%n",
125                              String.format("\"%s\"", origin),
126                              tag.isEmpty() ? target
127                                            : String.format("%s (%s)", target, tag));
128            }
129        }
130
131        class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {
132            private final PrintWriter writer;
133            private final Analyzer.Type type;
134            private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();
135            SummaryDotFile(PrintWriter writer, Analyzer.Type type) {
136                this.writer = writer;
137                this.type = type;
138                writer.format("digraph \"summary\" {%n");
139            }
140
141            @Override
142            public void close() {
143                writer.println("}");
144            }
145
146            @Override
147            public void visitDependence(String origin, Archive originArchive,
148                                        String target, Archive targetArchive) {
149
150                String targetName = type == PACKAGE ? target : targetArchive.getName();
151                if (targetArchive.getModule().isJDK()) {
152                    Module m = (Module)targetArchive;
153                    String n = showProfileOrModule(m);
154                    if (!n.isEmpty()) {
155                        targetName += " (" + n + ")";
156                    }
157                } else if (type == PACKAGE) {
158                    targetName += " (" + targetArchive.getName() + ")";
159                }
160                String label = getLabel(originArchive, targetArchive);
161                writer.format("  %-50s -> \"%s\"%s;%n",
162                        String.format("\"%s\"", origin), targetName, label);
163            }
164
165            String getLabel(Archive origin, Archive target) {
166                if (edges.isEmpty())
167                    return "";
168
169                StringBuilder label = edges.get(origin).get(target);
170                return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());
171            }
172
173            Analyzer.Visitor labelBuilder() {
174                // show the package-level dependencies as labels in the dot graph
175                return new Analyzer.Visitor() {
176                    @Override
177                    public void visitDependence(String origin, Archive originArchive,
178                                                String target, Archive targetArchive)
179                    {
180                        edges.putIfAbsent(originArchive, new HashMap<>());
181                        edges.get(originArchive).putIfAbsent(targetArchive, new StringBuilder());
182                        StringBuilder sb = edges.get(originArchive).get(targetArchive);
183                        String tag = toTag(originArchive, target, targetArchive);
184                        addLabel(sb, origin, target, tag);
185                    }
186
187                    void addLabel(StringBuilder label, String origin, String target, String tag) {
188                        label.append(origin).append(" -> ").append(target);
189                        if (!tag.isEmpty()) {
190                            label.append(" (" + tag + ")");
191                        }
192                        label.append("\\n");
193                    }
194                };
195            }
196        }
197    }
198
199    static class SimpleWriter extends JdepsWriter {
200        final PrintWriter writer;
201        SimpleWriter(PrintWriter writer, Analyzer.Type type,
202                     boolean showProfile, boolean showModule) {
203            super(type, showProfile, showModule);
204            this.writer = writer;
205        }
206
207        @Override
208        void generateOutput(Collection<Archive> archives, Analyzer analyzer) {
209            RawOutputFormatter depFormatter = new RawOutputFormatter(writer);
210            RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);
211            for (Archive archive : archives) {
212                // print summary
213                if (showModule && archive.getModule().isNamed()) {
214                    summaryFormatter.showModuleRequires(archive.getModule());
215                } else {
216                    analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
217                }
218
219                if (analyzer.hasDependences(archive) && type != SUMMARY) {
220                    // print the class-level or package-level dependences
221                    analyzer.visitDependences(archive, depFormatter);
222                }
223            }
224        }
225
226        class RawOutputFormatter implements Analyzer.Visitor {
227            private final PrintWriter writer;
228            private String pkg = "";
229
230            RawOutputFormatter(PrintWriter writer) {
231                this.writer = writer;
232            }
233
234            @Override
235            public void visitDependence(String origin, Archive originArchive,
236                                        String target, Archive targetArchive) {
237                String tag = toTag(originArchive, target, targetArchive);
238                if (showModule || type == VERBOSE) {
239                    writer.format("   %-50s -> %-50s %s%n", origin, target, tag);
240                } else {
241                    if (!origin.equals(pkg)) {
242                        pkg = origin;
243                        writer.format("   %s (%s)%n", origin, originArchive.getName());
244                    }
245                    writer.format("      -> %-50s %s%n", target, tag);
246                }
247            }
248        }
249
250        class RawSummaryFormatter implements Analyzer.Visitor {
251            private final PrintWriter writer;
252
253            RawSummaryFormatter(PrintWriter writer) {
254                this.writer = writer;
255            }
256
257            @Override
258            public void visitDependence(String origin, Archive originArchive,
259                                        String target, Archive targetArchive) {
260
261                String targetName = targetArchive.getPathName();
262                if (targetArchive.getModule().isNamed()) {
263                    targetName = targetArchive.getModule().name();
264                }
265                writer.format("%s -> %s", originArchive.getName(), targetName);
266                if (showProfile && targetArchive.getModule().isJDK()) {
267                    writer.format(" (%s)", target);
268                }
269                writer.format("%n");
270            }
271
272            public void showModuleRequires(Module module) {
273                if (!module.isNamed())
274                    return;
275
276                writer.format("module %s", module.name());
277                if (module.isAutomatic())
278                    writer.format(" (automatic)");
279                writer.println();
280                module.requires().keySet()
281                        .stream()
282                        .sorted()
283                        .forEach(req -> writer.format(" requires %s%s%n",
284                                                      module.requires.get(req) ? "public " : "",
285                                                      req));
286            }
287        }
288    }
289
290    /**
291     * If the given archive is JDK archive, this method returns the profile name
292     * only if -profile option is specified; it accesses a private JDK API and
293     * the returned value will have "JDK internal API" prefix
294     *
295     * For non-JDK archives, this method returns the file name of the archive.
296     */
297    String toTag(Archive source, String name, Archive target) {
298        if (source == target || !target.getModule().isNamed()) {
299            return target.getName();
300        }
301
302        Module module = target.getModule();
303        String pn = name;
304        if ((type == CLASS || type == VERBOSE)) {
305            int i = name.lastIndexOf('.');
306            pn = i > 0 ? name.substring(0, i) : "";
307        }
308
309        // exported API
310        boolean jdkunsupported = Module.isJDKUnsupported(module, pn);
311        if (module.isExported(pn) && !jdkunsupported) {
312            return showProfileOrModule(module);
313        }
314
315        // JDK internal API
316        if (!source.getModule().isJDK() && module.isJDK()){
317            return "JDK internal API (" + module.name() + ")";
318        }
319
320        // qualified exports or inaccessible
321        boolean isExported = module.isExported(pn, source.getModule().name());
322        return module.name() + (isExported ?  " (qualified)" : " (internal)");
323    }
324
325    String showProfileOrModule(Module m) {
326        String tag = "";
327        if (showProfile) {
328            Profile p = Profile.getProfile(m);
329            if (p != null) {
330                tag = p.profileName();
331            }
332        } else if (showModule) {
333            tag = m.name();
334        }
335        return tag;
336    }
337
338    Profile getProfile(String name) {
339        String pn = name;
340        if (type == CLASS || type == VERBOSE) {
341            int i = name.lastIndexOf('.');
342            pn = i > 0 ? name.substring(0, i) : "";
343        }
344        return Profile.getProfile(pn);
345    }
346
347}
348