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