blob: 9b176256bd26cccd27075b1f284e8accd315a2d9 [file] [log] [blame]
Thomas Vachuska58de4162015-09-10 16:15:33 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Thomas Vachuska58de4162015-09-10 16:15:33 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Thomas Vachuska6b331262015-04-27 11:09:07 -070016package org.onlab.jdvue;
17
18import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.ObjectWriter;
21import com.fasterxml.jackson.databind.node.ArrayNode;
22import com.fasterxml.jackson.databind.node.ObjectNode;
23
24import java.io.BufferedReader;
25import java.io.FileWriter;
26import java.io.IOException;
27import java.io.InputStream;
28import java.io.InputStreamReader;
29import java.util.Set;
30
31/**
32 * Generator of a self-contained HTML file which serves as a GUI for
33 * visualizing Java package dependencies carried in the supplied catalog.
34 *
35 * The HTML file is an adaptation of D3.js Hierarchical Edge Bundling as
36 * shown at http://bl.ocks.org/mbostock/7607999.
37 *
38 * @author Thomas Vachuska
39 */
40public class DependencyViewer {
41
42 private static final String JPD_EXT = ".db";
43 private static final String HTML_EXT = ".html";
44
45 private static final String INDEX = "index.html";
46 private static final String D3JS = "d3.v3.min.js";
47
48 private static final String TITLE_PLACEHOLDER = "TITLE_PLACEHOLDER";
49 private static final String D3JS_PLACEHOLDER = "D3JS_PLACEHOLDER";
50 private static final String DATA_PLACEHOLDER = "DATA_PLACEHOLDER";
51
52 private final Catalog catalog;
53
54 /**
55 * Creates a Java package dependency viewer.
56 *
57 * @param catalog dependency catalog
58 */
59 public DependencyViewer(Catalog catalog) {
60 this.catalog = catalog;
61 }
62
63 /**
64 * Main program entry point.
65 *
66 * @param args command line arguments
67 */
68 public static void main(String[] args) {
69 Catalog cat = new Catalog();
70 DependencyViewer viewer = new DependencyViewer(cat);
71 try {
72 String path = args[0];
73 cat.load(path + JPD_EXT);
74 cat.analyze();
75
76 System.err.println(cat);
77 viewer.dumpLongestCycle(cat);
78 viewer.writeHTMLFile(path);
79 } catch (IOException e) {
80 System.err.println("Unable to process catalog: " + e.getMessage());
81 }
82 }
83
84 /**
85 * Prints out the longest cycle; just for kicks.
86 * @param cat catalog
87 */
88 private void dumpLongestCycle(Catalog cat) {
89 DependencyCycle longest = null;
90 for (DependencyCycle cycle : cat.getCycles()) {
91 if (longest == null || longest.getCycleSegments().size() < cycle.getCycleSegments().size()) {
92 longest = cycle;
93 }
94 }
95
96 if (longest != null) {
97 for (Dependency dependency : longest.getCycleSegments()) {
98 System.out.println(dependency);
99 }
100 }
101 }
102
103 /**
104 * Writes the HTML catalog file for the given viewer.
105 *
106 * @param path base file path
107 * @throws IOException if issues encountered writing the HTML file
108 */
109 public void writeHTMLFile(String path) throws IOException {
110 String index = slurp(getClass().getResourceAsStream(INDEX));
111 String d3js = slurp(getClass().getResourceAsStream(D3JS));
112
113 FileWriter fw = new FileWriter(path + HTML_EXT);
114 ObjectWriter writer = new ObjectMapper().writer(); // .writerWithDefaultPrettyPrinter();
115 fw.write(index.replace(TITLE_PLACEHOLDER, path)
116 .replace(D3JS_PLACEHOLDER, d3js)
117 .replace(DATA_PLACEHOLDER, writer.writeValueAsString(toJson())));
118 fw.close();
119 }
120
121 /**
122 * Slurps the specified input stream into a string.
123 *
124 * @param stream input stream to be read
125 * @return string containing the contents of the input stream
126 * @throws IOException if issues encountered reading from the stream
127 */
128 static String slurp(InputStream stream) throws IOException {
129 StringBuilder sb = new StringBuilder();
130 BufferedReader br = new BufferedReader(new InputStreamReader(stream));
131 String line;
132 while ((line = br.readLine()) != null) {
133 sb.append(line).append(System.lineSeparator());
134 }
135 br.close();
136 return sb.toString();
137 }
138
139 // Produces a JSON structure designed to drive the hierarchical visual
140 // representation of Java package dependencies and any dependency cycles
141 private JsonNode toJson() {
142 ObjectMapper mapper = new ObjectMapper();
143 ObjectNode root = mapper.createObjectNode();
144 root.put("packages", jsonPackages(mapper));
145 root.put("cycleSegments", jsonCycleSegments(mapper, catalog.getCycleSegments()));
146 root.put("summary", jsonSummary(mapper));
147 return root;
148 }
149
150 // Produces a JSON summary of dependencies
151 private JsonNode jsonSummary(ObjectMapper mapper) {
152 ObjectNode summary = mapper.createObjectNode();
153 summary.put("packages", catalog.getPackages().size());
154 summary.put("sources", catalog.getSources().size());
155 summary.put("cycles", catalog.getCycles().size());
156 summary.put("cycleSegments", catalog.getCycleSegments().size());
157 return summary;
158 }
159
160 // Produces a JSON structure with package dependency data
161 private JsonNode jsonPackages(ObjectMapper mapper) {
162 ArrayNode packages = mapper.createArrayNode();
163 for (JavaPackage javaPackage : catalog.getPackages()) {
164 packages.add(json(mapper, javaPackage));
165 }
166 return packages;
167 }
168
169 // Produces a JSON structure with all cyclic segments
170 private JsonNode jsonCycleSegments(ObjectMapper mapper,
171 Set<Dependency> segments) {
172 ObjectNode cyclicSegments = mapper.createObjectNode();
173 for (Dependency dependency : segments) {
174 String s = dependency.getSource().name();
175 String t = dependency.getTarget().name();
176 cyclicSegments.put(t + "-" + s,
177 mapper.createObjectNode().put("s", s).put("t", t));
178 }
179 return cyclicSegments;
180 }
181
182 // Produces a JSON object structure describing the specified Java package.
183 private JsonNode json(ObjectMapper mapper, JavaPackage javaPackage) {
184 ObjectNode node = mapper.createObjectNode();
185
186 ArrayNode imports = mapper.createArrayNode();
187 for (JavaPackage dependency : javaPackage.getDependencies()) {
188 imports.add(dependency.name());
189 }
190
191 Set<DependencyCycle> packageCycles = catalog.getPackageCycles(javaPackage);
192 Set<Dependency> packageCycleSegments = catalog.getPackageCycleSegments(javaPackage);
193
194 node.put("name", javaPackage.name());
195 node.put("size", javaPackage.getSources().size());
196 node.put("imports", imports);
197 node.put("cycleSegments", jsonCycleSegments(mapper, packageCycleSegments));
198 node.put("cycleCount", packageCycles.size());
199 node.put("cycleSegmentCount", packageCycleSegments.size());
200 return node;
201 }
202
203}