Thomas Vachuska

Moved jdvue utility from ONOS-tools repo into onos repo.

Change-Id: I0bc1cef80541075c800c5309cb642a244a79fa0b
1 +#!/bin/bash
2 +#-------------------------------------------------------------------------------
3 +# Java Package Dependency viewer
4 +#
5 +# written by Thomas Vachuska
6 +# -- Doobs --
7 +#-------------------------------------------------------------------------------
8 +
9 +JDVUE_ROOT=${JDVUE_ROOT:-$(dirname $0)/..}
10 +cd $JDVUE_ROOT
11 +VER=1.2.0-SNAPSHOT
12 +JAR=$PWD/target/jdvue-${VER}.jar # start with the dev jar first
13 +cd - >/dev/null
14 +
15 +# If the dev jar is not available, use one from .m2/repository
16 +[ -f ${JAR} ] || JAR=~/.m2/repository/org/onlab/tools/jdvue/${VER}/jdvue-${VER}.jar
17 +
18 +# Assume default project to be the base-name of the argument or of current dir
19 +name=$(basename ${1:-$PWD})
20 +
21 +# If the -n option is specified use the next argument as the catalog name
22 +[ "$1" = "-n" -a $# -ge 2 ] && name=$2 && shift 2
23 +
24 +# Use the rest of the arguments as paths to scan for sources to build catalog
25 +find "${@:-.}" -type f -name \*.java \
26 + | grep -v -E '/lost+found/|/target/|archetype-resources' \
27 + | xargs grep -E "^[ \t]*import .*;.*|^[ \t]*package .*;.*" \
28 + | tr -d '\r' > $name.db
29 +
30 +# Now run the Java Dependency Viewer jar on the catalog
31 +java -jar ${JAR} $name && rm $name.db && open $name.html
1 +#!/bin/bash
2 +#-------------------------------------------------------------------------------
3 +# Java Package Dependency scanner
4 +#
5 +# written by Thomas Vachuska
6 +# -- Doobs --
7 +#-------------------------------------------------------------------------------
8 +
9 +find "${@:-.}" -type f -name \*.java \
10 + | grep -v -E '/lost+found/|/target/' \
11 + | xargs grep -E "^[ \t]*import .*;.*|^[ \t]*package .*;.*" \
12 + | tr -d '\r' > jpd.db
1 +<?xml version="1.0"?>
2 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4 + <modelVersion>4.0.0</modelVersion>
5 + <prerequisites>
6 + <maven>3.0.4</maven>
7 + </prerequisites>
8 +
9 + <parent>
10 + <groupId>org.onosproject</groupId>
11 + <artifactId>onos-base</artifactId>
12 + <version>1</version>
13 + <relativePath>../../build/pom.xml</relativePath>
14 + </parent>
15 +
16 + <artifactId>jdvue</artifactId>
17 + <version>1.2.0-SNAPSHOT</version>
18 + <packaging>jar</packaging>
19 +
20 + <description>Java Package Dependency &amp; Analyzer</description>
21 +
22 + <dependencies>
23 + <dependency>
24 + <groupId>com.google.guava</groupId>
25 + <artifactId>guava</artifactId>
26 + <version>18.0</version>
27 + </dependency>
28 + <dependency>
29 + <groupId>com.google.guava</groupId>
30 + <artifactId>guava-testlib</artifactId>
31 + <version>18.0</version>
32 + <scope>test</scope>
33 + </dependency>
34 + <dependency>
35 + <groupId>com.fasterxml.jackson.core</groupId>
36 + <artifactId>jackson-databind</artifactId>
37 + <version>2.2.2</version>
38 + </dependency>
39 + <dependency>
40 + <groupId>junit</groupId>
41 + <artifactId>junit</artifactId>
42 + <version>4.11</version>
43 + <scope>test</scope>
44 + </dependency>
45 + </dependencies>
46 +
47 + <build>
48 + <plugins>
49 + <plugin>
50 + <artifactId>maven-compiler-plugin</artifactId>
51 + <version>3.1</version>
52 + <configuration>
53 + <source>1.8</source>
54 + <target>1.8</target>
55 + </configuration>
56 + </plugin>
57 + <plugin>
58 + <groupId>org.apache.maven.plugins</groupId>
59 + <artifactId>maven-shade-plugin</artifactId>
60 + <version>2.3</version>
61 + <configuration>
62 + <transformers>
63 + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
64 + <mainClass>org.onlab.jdvue.DependencyViewer</mainClass>
65 + </transformer>
66 + </transformers>
67 + </configuration>
68 + <executions>
69 + <execution>
70 + <phase>package</phase>
71 + <goals>
72 + <goal>shade</goal>
73 + </goals>
74 + </execution>
75 + </executions>
76 + </plugin>
77 + </plugins>
78 + </build>
79 +
80 +</project>
1 +package org.onlab.jdvue;
2 +
3 +
4 +import java.io.BufferedReader;
5 +import java.io.FileInputStream;
6 +import java.io.IOException;
7 +import java.io.InputStream;
8 +import java.io.InputStreamReader;
9 +import java.util.ArrayList;
10 +import java.util.Collection;
11 +import java.util.Collections;
12 +import java.util.HashMap;
13 +import java.util.HashSet;
14 +import java.util.List;
15 +import java.util.Map;
16 +import java.util.Set;
17 +
18 +import static com.google.common.base.Objects.toStringHelper;
19 +
20 +/**
21 + * Produces a package &amp; source catalogue.
22 + *
23 + * @author Thomas Vachuska
24 + */
25 +public class Catalog {
26 +
27 + private static final String PACKAGE = "package";
28 + private static final String IMPORT = "import";
29 + private static final String STATIC = "static";
30 + private static final String SRC_ROOT = "src/main/java/";
31 + private static final String WILDCARD = "\\.*$";
32 +
33 + private final Map<String, JavaSource> sources = new HashMap<>();
34 + private final Map<String, JavaPackage> packages = new HashMap<>();
35 + private final Set<DependencyCycle> cycles = new HashSet<>();
36 + private final Set<Dependency> cycleSegments = new HashSet<>();
37 + private final Map<JavaPackage, Set<DependencyCycle>> packageCycles = new HashMap<>();
38 + private final Map<JavaPackage, Set<Dependency>> packageCycleSegments = new HashMap<>();
39 +
40 + /**
41 + * Loads the catalog from the specified catalog file.
42 + *
43 + * @param catalogPath catalog file path
44 + */
45 + public void load(String catalogPath) throws IOException {
46 + InputStream is = new FileInputStream(catalogPath);
47 + BufferedReader br = new BufferedReader(new InputStreamReader(is));
48 +
49 + String line;
50 + while ((line = br.readLine()) != null) {
51 + // Split the line into the two fields: path and pragmas
52 + String fields[] = line.trim().split(":");
53 + if (fields.length <= 1) {
54 + continue;
55 + }
56 + String path = fields[0];
57 +
58 + // Now split the pragmas on whitespace and trim punctuation
59 + String pragma[] = fields[1].trim().replaceAll("[;\n\r]", "").split("[\t ]");
60 +
61 + // Locate (or create) Java source entity based on the path
62 + JavaSource source = getOrCreateSource(path);
63 +
64 + // Now process the package or import statements
65 + if (pragma[0].equals(PACKAGE)) {
66 + processPackageDeclaration(source, pragma[1]);
67 +
68 + } else if (pragma[0].equals(IMPORT)) {
69 + if (pragma[1].equals(STATIC)) {
70 + processImportStatement(source, pragma[2]);
71 + } else {
72 + processImportStatement(source, pragma[1]);
73 + }
74 + }
75 + }
76 + }
77 +
78 + /**
79 + * Analyzes the catalog by resolving imports and identifying circular
80 + * package dependencies.
81 + */
82 + public void analyze() {
83 + resolveImports();
84 + findCircularDependencies();
85 + }
86 +
87 + /**
88 + * Identifies circular package dependencies through what amounts to be a
89 + * depth-first search rooted with each package.
90 + */
91 + private void findCircularDependencies() {
92 + cycles.clear();
93 + for (JavaPackage javaPackage : getPackages()) {
94 + findCircularDependencies(javaPackage);
95 + }
96 +
97 + cycleSegments.clear();
98 + packageCycles.clear();
99 + packageCycleSegments.clear();
100 +
101 + for (DependencyCycle cycle : getCycles()) {
102 + recordCycleForPackages(cycle);
103 + cycleSegments.addAll(cycle.getCycleSegments());
104 + }
105 + }
106 +
107 + /**
108 + * Records the specified cycle into a set for each involved package.
109 + *
110 + * @param cycle cycle to record for involved packages
111 + */
112 + private void recordCycleForPackages(DependencyCycle cycle) {
113 + for (JavaPackage javaPackage : cycle.getCycle()) {
114 + Set<DependencyCycle> cset = packageCycles.get(javaPackage);
115 + if (cset == null) {
116 + cset = new HashSet<>();
117 + packageCycles.put(javaPackage, cset);
118 + }
119 + cset.add(cycle);
120 +
121 + Set<Dependency> sset = packageCycleSegments.get(javaPackage);
122 + if (sset == null) {
123 + sset = new HashSet<>();
124 + packageCycleSegments.put(javaPackage, sset);
125 + }
126 + sset.addAll(cycle.getCycleSegments());
127 + }
128 + }
129 +
130 + /**
131 + * Identifies circular dependencies in which this package participates
132 + * using depth-first search.
133 + *
134 + * @param javaPackage Java package to inspect for dependency cycles
135 + */
136 + private void findCircularDependencies(JavaPackage javaPackage) {
137 + // Setup a depth trace anchored at the given java package.
138 + List<JavaPackage> trace = newTrace(new ArrayList<JavaPackage>(), javaPackage);
139 +
140 + Set<JavaPackage> searched = new HashSet<>();
141 + searchDependencies(javaPackage, trace, searched);
142 + }
143 +
144 + /**
145 + * Generates a new trace using the previous one and a new element
146 + *
147 + * @param trace old search trace
148 + * @param javaPackage package to add to the trace
149 + * @return new search trace
150 + */
151 + private List<JavaPackage> newTrace(List<JavaPackage> trace,
152 + JavaPackage javaPackage) {
153 + List<JavaPackage> newTrace = new ArrayList<>(trace);
154 + newTrace.add(javaPackage);
155 + return newTrace;
156 + }
157 +
158 +
159 + /**
160 + * Recursive depth-first search through dependency tree
161 + *
162 + * @param javaPackage java package being searched currently
163 + * @param trace search trace
164 + * @param searched set of java packages already searched
165 + */
166 + private void searchDependencies(JavaPackage javaPackage,
167 + List<JavaPackage> trace,
168 + Set<JavaPackage> searched) {
169 + if (!searched.contains(javaPackage)) {
170 + searched.add(javaPackage);
171 + for (JavaPackage dependency : javaPackage.getDependencies()) {
172 + if (trace.contains(dependency)) {
173 + cycles.add(new DependencyCycle(trace, dependency));
174 + } else {
175 + searchDependencies(dependency, newTrace(trace, dependency), searched);
176 + }
177 + }
178 + }
179 + }
180 +
181 + /**
182 + * Resolves import names of Java sources into imports of entities known
183 + * to this catalog. All other import names will be ignored.
184 + */
185 + private void resolveImports() {
186 + for (JavaPackage javaPackage : getPackages()) {
187 + Set<JavaPackage> dependencies = new HashSet<>();
188 + for (JavaSource source : javaPackage.getSources()) {
189 + Set<JavaEntity> imports = resolveImports(source);
190 + source.setImports(imports);
191 + dependencies.addAll(importedPackages(imports));
192 + }
193 + javaPackage.setDependencies(dependencies);
194 + }
195 + }
196 +
197 + /**
198 + * Produces a set of imported Java packages from the specified set of
199 + * Java source entities.
200 + *
201 + * @param imports list of imported Java source entities
202 + * @return list of imported Java packages
203 + */
204 + private Set<JavaPackage> importedPackages(Set<JavaEntity> imports) {
205 + Set<JavaPackage> packages = new HashSet<>();
206 + for (JavaEntity entity : imports) {
207 + packages.add(entity instanceof JavaPackage ? (JavaPackage) entity :
208 + ((JavaSource) entity).getPackage());
209 + }
210 + return packages;
211 + }
212 +
213 + /**
214 + * Resolves import names of the specified Java source into imports of
215 + * entities known to this catalog. All other import names will be ignored.
216 + *
217 + * @param source Java source
218 + * @return list of resolved imports
219 + */
220 + private Set<JavaEntity> resolveImports(JavaSource source) {
221 + Set<JavaEntity> imports = new HashSet<>();
222 + for (String importName : source.getImportNames()) {
223 + JavaEntity entity = importName.matches(WILDCARD) ?
224 + getPackage(importName.replaceAll(WILDCARD, "")) :
225 + getSource(importName);
226 + if (entity != null) {
227 + imports.add(entity);
228 + }
229 + }
230 + return imports;
231 + }
232 +
233 + /**
234 + * Returns either an existing or a newly created Java package.
235 + *
236 + * @param packageName Java package name
237 + * @return Java package
238 + */
239 + private JavaPackage getOrCreatePackage(String packageName) {
240 + JavaPackage javaPackage = packages.get(packageName);
241 + if (javaPackage == null) {
242 + javaPackage = new JavaPackage(packageName);
243 + packages.put(packageName, javaPackage);
244 + }
245 + return javaPackage;
246 + }
247 +
248 + /**
249 + * Returns either an existing or a newly created Java source.
250 + *
251 + * @param path Java source path
252 + * @return Java source
253 + */
254 + private JavaSource getOrCreateSource(String path) {
255 + String name = nameFromPath(path);
256 + JavaSource source = sources.get(name);
257 + if (source == null) {
258 + source = new JavaSource(name, path);
259 + sources.put(name, source);
260 + }
261 + return source;
262 + }
263 +
264 + /**
265 + * Extracts a fully qualified source class name from the given path.
266 + * <p/>
267 + * For now, this implementation assumes standard Maven source structure
268 + * and thus will look for start of package name under 'src/main/java/'.
269 + * If it will not find such a prefix, it will simply return the path as
270 + * the name.
271 + *
272 + * @param path source path
273 + * @return source name
274 + */
275 + private String nameFromPath(String path) {
276 + int i = path.indexOf(SRC_ROOT);
277 + String name = i < 0 ? path : path.substring(i + SRC_ROOT.length());
278 + return name.replaceAll("\\.java$", "").replace("/", ".");
279 + }
280 +
281 + /**
282 + * Processes the package declaration pragma for the given source.
283 + *
284 + * @param source Java source
285 + * @param packageName Java package name
286 + */
287 + private void processPackageDeclaration(JavaSource source, String packageName) {
288 + JavaPackage javaPackage = getOrCreatePackage(packageName);
289 + source.setPackage(javaPackage);
290 + javaPackage.addSource(source);
291 + }
292 +
293 + /**
294 + * Processes the import pragma for the given source.
295 + *
296 + * @param source Java source
297 + * @param name name of the Java entity being imported (class or package)
298 + */
299 + private void processImportStatement(JavaSource source, String name) {
300 + source.addImportName(name);
301 + }
302 +
303 + /**
304 + * Returns the collection of java sources.
305 + *
306 + * @return collection of java sources
307 + */
308 + public Collection<JavaSource> getSources() {
309 + return Collections.unmodifiableCollection(sources.values());
310 + }
311 +
312 + /**
313 + * Returns the Java source with the specified name.
314 + *
315 + * @param name Java source name
316 + * @return Java source
317 + */
318 + public JavaSource getSource(String name) {
319 + return sources.get(name);
320 + }
321 +
322 + /**
323 + * Returns the collection of all Java packages.
324 + *
325 + * @return collection of java packages
326 + */
327 + public Collection<JavaPackage> getPackages() {
328 + return Collections.unmodifiableCollection(packages.values());
329 + }
330 +
331 + /**
332 + * Returns the set of all Java package dependency cycles.
333 + *
334 + * @return set of dependency cycles
335 + */
336 + public Set<DependencyCycle> getCycles() {
337 + return Collections.unmodifiableSet(cycles);
338 + }
339 +
340 + /**
341 + * Returns the set of all Java package dependency cycle segments.
342 + *
343 + * @return set of dependency cycle segments
344 + */
345 + public Set<Dependency> getCycleSegments() {
346 + return Collections.unmodifiableSet(cycleSegments);
347 + }
348 +
349 + /**
350 + * Returns the set of dependency cycles which involve the specified package.
351 + *
352 + * @param javaPackage java package
353 + * @return set of dependency cycles
354 + */
355 + public Set<DependencyCycle> getPackageCycles(JavaPackage javaPackage) {
356 + Set<DependencyCycle> set = packageCycles.get(javaPackage);
357 + return Collections.unmodifiableSet(set == null ? new HashSet<DependencyCycle>() : set);
358 + }
359 +
360 + /**
361 + * Returns the set of dependency cycle segments which involve the specified package.
362 + *
363 + * @param javaPackage java package
364 + * @return set of dependency cycle segments
365 + */
366 + public Set<Dependency> getPackageCycleSegments(JavaPackage javaPackage) {
367 + Set<Dependency> set = packageCycleSegments.get(javaPackage);
368 + return Collections.unmodifiableSet(set == null ? new HashSet<Dependency>() : set);
369 + }
370 +
371 + /**
372 + * Returns the Java package with the specified name.
373 + *
374 + * @param name Java package name
375 + * @return Java package
376 + */
377 + public JavaPackage getPackage(String name) {
378 + return packages.get(name);
379 + }
380 +
381 + @Override
382 + public String toString() {
383 + return toStringHelper(this)
384 + .add("packages", packages.size())
385 + .add("sources", sources.size())
386 + .add("cycles", cycles.size())
387 + .add("cycleSegments", cycleSegments.size()).toString();
388 + }
389 +
390 +}
1 +package org.onlab.jdvue;
2 +
3 +import java.util.Objects;
4 +
5 +import static com.google.common.base.Objects.toStringHelper;
6 +
7 +/**
8 + * Abstraction of a dependency segment.
9 + *
10 + * @author Thomas Vachuska
11 + */
12 +public class Dependency {
13 +
14 + private final JavaPackage source;
15 + private final JavaPackage target;
16 +
17 + /**
18 + * Creates a dependency from the specified source on the given target.
19 + *
20 + * @param source source of the dependency
21 + * @param target target of the dependency
22 + */
23 + public Dependency(JavaPackage source, JavaPackage target) {
24 + this.source = source;
25 + this.target = target;
26 + }
27 +
28 + /**
29 + * Returns the dependency source.
30 + *
31 + * @return source Java package
32 + */
33 + public JavaPackage getSource() {
34 + return source;
35 + }
36 +
37 + /**
38 + * Returns the dependency target.
39 + *
40 + * @return target Java package
41 + */
42 + public JavaPackage getTarget() {
43 + return target;
44 + }
45 +
46 + @Override
47 + public boolean equals(Object obj) {
48 + if (obj instanceof Dependency) {
49 + Dependency that = (Dependency) obj;
50 + return Objects.equals(source, that.source) &&
51 + Objects.equals(target, that.target);
52 + }
53 + return false;
54 + }
55 +
56 + @Override
57 + public int hashCode() {
58 + return Objects.hash(source, target);
59 + }
60 +
61 + @Override
62 + public String toString() {
63 + return toStringHelper(this)
64 + .add("source", source).add("target", target).toString();
65 + }
66 +
67 +}
1 +package org.onlab.jdvue;
2 +
3 +import java.util.ArrayList;
4 +import java.util.Collections;
5 +import java.util.List;
6 +import java.util.Objects;
7 +
8 +import static com.google.common.base.Objects.toStringHelper;
9 +
10 +/**
11 + * Simple representation of a Java package dependency cycle.
12 + */
13 +public class DependencyCycle {
14 +
15 + private final List<JavaPackage> cycle;
16 +
17 + /**
18 + * Creates a normalized dependency cycle represented by the specified list
19 + * of Java packages, which are expected to be given in order of dependency.
20 + * List is assumed to be non-empty.
21 + *
22 + * @param cycle list of Java packages in the dependency cycle
23 + * @param cause Java package that caused the cycle
24 + */
25 + DependencyCycle(List<JavaPackage> cycle, JavaPackage cause) {
26 + this.cycle = normalize(cycle, cause);
27 + }
28 +
29 + /**
30 + * Produces a normalized dependency cycle list. Normalization is performed
31 + * by rotating the list so that the package with the least lexicographic
32 + * name is at the start of the list.
33 + *
34 + * @param cycle list of Java packages in the dependency cycle
35 + * @param cause Java package that caused the cycle
36 + * @return normalized cycle
37 + */
38 + private List<JavaPackage> normalize(List<JavaPackage> cycle, JavaPackage cause) {
39 + int start = cycle.indexOf(cause);
40 + List<JavaPackage> clone = new ArrayList<>(cycle.subList(start, cycle.size()));
41 + int leastIndex = findIndexOfLeastName(clone);
42 + Collections.rotate(clone, -leastIndex);
43 + return Collections.unmodifiableList(clone);
44 + }
45 +
46 + /**
47 + * Returns the index of the Java package with the least name.
48 + *
49 + * @param cycle list of Java packages in the dependency cycle
50 + * @return index of the least Java package name
51 + */
52 + private int findIndexOfLeastName(List<JavaPackage> cycle) {
53 + int leastIndex = 0;
54 + String leastName = cycle.get(leastIndex).name();
55 + for (int i = 1, n = cycle.size(); i < n; i++) {
56 + JavaPackage javaPackage = cycle.get(i);
57 + if (leastName.compareTo(javaPackage.name()) > 0) {
58 + leastIndex = i;
59 + leastName = javaPackage.name();
60 + }
61 + }
62 + return leastIndex;
63 + }
64 +
65 + /**
66 + * Returns the normalized Java package dependency cycle
67 + *
68 + * @return list of packages in the dependency cycle
69 + */
70 + public List<JavaPackage> getCycle() {
71 + return cycle;
72 + }
73 +
74 + /**
75 + * Returns the dependency cycle in form of individual dependencies.
76 + *
77 + * @return list of dependencies forming the cycle
78 + */
79 + public List<Dependency> getCycleSegments() {
80 + List<Dependency> dependencies = new ArrayList<>();
81 + for (int i = 0, n = cycle.size(); i < n; i++) {
82 + dependencies.add(new Dependency(cycle.get(i), cycle.get(i < n - 1 ? i + 1 : 0)));
83 + }
84 + return dependencies;
85 + }
86 +
87 + @Override
88 + public boolean equals(Object o) {
89 + if (o instanceof DependencyCycle) {
90 + DependencyCycle that = (DependencyCycle) o;
91 + return Objects.equals(cycle, that.cycle);
92 + }
93 + return false;
94 + }
95 +
96 + @Override
97 + public int hashCode() {
98 + return Objects.hash(cycle);
99 + }
100 +
101 + @Override
102 + public String toString() {
103 + return toStringHelper(this).add("cycle", cycle).toString();
104 + }
105 +
106 + public String toShortString() {
107 + StringBuilder sb = new StringBuilder("[");
108 + for (JavaPackage javaPackage : cycle) {
109 + sb.append(javaPackage.name()).append(", ");
110 + }
111 + if (sb.length() > 1) {
112 + sb.delete(sb.length() - 2, sb.length());
113 + }
114 + sb.append("]");
115 + return sb.toString();
116 + }
117 +
118 +}
1 +package org.onlab.jdvue;
2 +
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +import com.fasterxml.jackson.databind.ObjectMapper;
5 +import com.fasterxml.jackson.databind.ObjectWriter;
6 +import com.fasterxml.jackson.databind.node.ArrayNode;
7 +import com.fasterxml.jackson.databind.node.ObjectNode;
8 +
9 +import java.io.BufferedReader;
10 +import java.io.FileWriter;
11 +import java.io.IOException;
12 +import java.io.InputStream;
13 +import java.io.InputStreamReader;
14 +import 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 + */
25 +public 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 +}
1 +package org.onlab.jdvue;
2 +
3 +import java.util.Objects;
4 +
5 +/**
6 + * Abstraction of a Java source entity.
7 + */
8 +public abstract class JavaEntity {
9 +
10 + private final String name;
11 +
12 + /**
13 + * Creates a new Java source entity with the given name.
14 + *
15 + * @param name source entity name
16 + */
17 + JavaEntity(String name) {
18 + this.name = name;
19 + }
20 +
21 + /**
22 + * Returns the Java source entity name.
23 + *
24 + * @return source entity name
25 + */
26 + public String name() {
27 + return name;
28 + }
29 +
30 + @Override
31 + public boolean equals(Object o) {
32 + if (o instanceof JavaEntity) {
33 + JavaEntity that = (JavaEntity) o;
34 + return getClass().equals(that.getClass()) &&
35 + Objects.equals(name, that.name);
36 + }
37 + return false;
38 + }
39 +
40 + @Override
41 + public int hashCode() {
42 + return Objects.hash(name);
43 + }
44 +}
1 +package org.onlab.jdvue;
2 +
3 +import java.util.Collections;
4 +import java.util.HashSet;
5 +import java.util.Set;
6 +
7 +import static com.google.common.base.Objects.toStringHelper;
8 +
9 +/**
10 + * Simple abstraction of a Java package for the purpose of tracking
11 + * dependencies and requirements.
12 + *
13 + * @author Thomas Vachuska
14 + */
15 +public class JavaPackage extends JavaEntity {
16 +
17 + private final Set<JavaSource> sources = new HashSet<>();
18 + private Set<JavaPackage> dependencies;
19 +
20 + /**
21 + * Creates a new Java package.
22 + *
23 + * @param name java package file name
24 + */
25 + JavaPackage(String name) {
26 + super(name);
27 + }
28 +
29 + /**
30 + * Returns the set of sources contained in this Java package.
31 + *
32 + * @return set of Java sources
33 + */
34 + public Set<JavaSource> getSources() {
35 + return Collections.unmodifiableSet(sources);
36 + }
37 +
38 + /**
39 + * Adds the specified Java source to the package. Only possible if the
40 + * Java package of the source is the same as this Java package.
41 + *
42 + * @param source Java source to be added
43 + */
44 + void addSource(JavaSource source) {
45 + if (source.getPackage().equals(this)) {
46 + sources.add(source);
47 + }
48 + }
49 +
50 + /**
51 + * Returns the set of packages directly required by this package.
52 + *
53 + * @return set of Java package dependencies
54 + */
55 + Set<JavaPackage> getDependencies() {
56 + return dependencies;
57 + }
58 +
59 + /**
60 + * Sets the set of resolved Java packages on which this package dependens.
61 + *
62 + * @param dependencies set of resolved Java packages
63 + */
64 + void setDependencies(Set<JavaPackage> dependencies) {
65 + if (this.dependencies == null) {
66 + this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
67 + }
68 + }
69 +
70 + @Override
71 + public String toString() {
72 + return toStringHelper(this)
73 + .add("name", name())
74 + .add("sources", sources.size())
75 + .add("dependencies", (dependencies != null ? dependencies.size() : 0))
76 + .toString();
77 + }
78 +
79 +}
1 +package org.onlab.jdvue;
2 +
3 +import java.util.*;
4 +
5 +import static com.google.common.base.Objects.toStringHelper;
6 +
7 +/**
8 + * Simple abstraction of a Java source file for the purpose of tracking
9 + * dependencies and requirements.
10 + *
11 + * @author Thomas Vachuska
12 + */
13 +public class JavaSource extends JavaEntity {
14 +
15 + private String path;
16 + private JavaPackage javaPackage;
17 +
18 + private final Set<String> importNames = new HashSet<>();
19 + private Set<JavaEntity> imports;
20 +
21 + /**
22 + * Creates a new Java source entity.
23 + *
24 + * @param name java source file name
25 + */
26 + JavaSource(String name, String path) {
27 + super(name);
28 + this.path = path;
29 + }
30 +
31 + /**
32 + * Returns the Java package for this Java source.
33 + *
34 + * @return Java package
35 + */
36 + public JavaPackage getPackage() {
37 + return javaPackage;
38 + }
39 +
40 + /**
41 + * Sets the Java package for this Java source.
42 + *
43 + * @param javaPackage Java package
44 + */
45 + void setPackage(JavaPackage javaPackage) {
46 + if (this.javaPackage == null) {
47 + this.javaPackage = javaPackage;
48 + }
49 + }
50 +
51 + /**
52 + * Returns the set of resolved imports for this Java source
53 + * @return set of imports
54 + */
55 + public Set<JavaEntity> getImports() {
56 + return imports;
57 + }
58 +
59 + /**
60 + * Sets the set of resolved imported Java entities for this source.
61 + *
62 + * @param imports set of resolved Java entities imported by this source
63 + */
64 + void setImports(Set<JavaEntity> imports) {
65 + if (this.imports == null) {
66 + this.imports = Collections.unmodifiableSet(new HashSet<>(imports));
67 + }
68 + }
69 +
70 + /**
71 + * Adds a name of an imported, but unresolved, Java entity name.
72 + *
73 + * @param name name of an imported Java entity
74 + */
75 + void addImportName(String name) {
76 + importNames.add(name);
77 + }
78 +
79 + /**
80 + * Returns the set of imported, but unresolved, Java entity names.
81 + * @return set of imported Java entity names
82 + */
83 + Set<String> getImportNames() {
84 + return importNames;
85 + }
86 +
87 + @Override
88 + public String toString() {
89 + return toStringHelper(this)
90 + .add("name", name())
91 + .add("javaPackage", (javaPackage != null ? javaPackage.name() : ""))
92 + .add("importNames", importNames.size())
93 + .add("imports", (imports != null ? imports.size() : 0))
94 + .toString();
95 + }
96 +
97 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
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 + */
16 +
17 +/**
18 + * Utility to analyze Java package dependencies.
19 + */
20 +package org.onlab.jdvue;
...\ No newline at end of file ...\ No newline at end of file
This diff could not be displayed because it is too large.
1 +<!DOCTYPE html>
2 +<html>
3 +<head>
4 + <meta charset="utf-8">
5 + <title>TITLE_PLACEHOLDER</title>
6 + <style>
7 +
8 + .node {
9 + font: 300 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
10 + fill: #bbb;
11 + }
12 +
13 + .link {
14 + stroke: steelblue;
15 + stroke-opacity: .4;
16 + fill: none;
17 + pointer-events: none;
18 + }
19 +
20 + .node--focus {
21 + font-weight: 700;
22 + fill: #000;
23 + }
24 +
25 + .node:hover {
26 + fill: steelblue;
27 + }
28 +
29 + .node:hover,
30 + .node--source,
31 + .node--target {
32 + font-weight: 700;
33 + }
34 +
35 + .node--source {
36 + fill: #2ca02c;
37 + }
38 +
39 + .node--target {
40 + fill: #d59800;
41 + }
42 +
43 + .link--source,
44 + .link--target {
45 + stroke-opacity: 1;
46 + stroke-width: 3px;
47 + }
48 +
49 + .link--source {
50 + stroke: #d59800;
51 + }
52 +
53 + .link--target {
54 + stroke: #2ca02c;
55 + }
56 +
57 + .link--cycle {
58 + stroke: #ff0000;
59 + }
60 +
61 + .summary {
62 + font: 300 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
63 + position: fixed;
64 + top: 32px;
65 + right: 32px;
66 + width: 192px;
67 + background-color: #ffffff;
68 + box-shadow: 2px 2px 4px 2px #777777;
69 + padding: 5px;
70 + }
71 +
72 + .details {
73 + display: none;
74 + font: 300 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
75 + position: fixed;
76 + top: 220px;
77 + right: 32px;
78 + width: 192px;
79 + background-color: #ffffff;
80 + box-shadow: 2px 2px 4px 2px #777777;
81 + padding: 5px;
82 + }
83 +
84 + .shown {
85 + display:block;
86 + }
87 +
88 + .stat {
89 + text-align: right;
90 + width: 64px;
91 + }
92 +
93 + .title {
94 + font-size: 16px;
95 + font-weight: bold;
96 + }
97 +
98 + #package {
99 + font-size: 14px;
100 + font-weight: bold;
101 + }
102 + </style>
103 +</head>
104 +<body>
105 + <div class="summary">
106 + <div class="title">Project TITLE_PLACEHOLDER</div>
107 + <table>
108 + <tr>
109 + <td>Sources:</td>
110 + <td id="sourceCount" class="stat"></td>
111 + </tr>
112 + <tr>
113 + <td>Packages:</td>
114 + <td id="packageCount" class="stat"></td>
115 + </tr>
116 + <tr>
117 + <td>Cyclic Segments:</td>
118 + <td id="segmentCount" class="stat"></td>
119 + </tr>
120 + <tr>
121 + <td>Cycles:</td>
122 + <td id="cycleCount" class="stat"></td>
123 + </tr>
124 + </table>
125 + <div><hr size="1"></div>
126 + <div><input type="checkbox"> Highlight cycles</input></div>
127 + <div><input style="width: 95%" type="range" min="0" max="100" value="75"></div>
128 + </div>
129 + <div class="details">
130 + <div id="package">Package Details</div>
131 + <table>
132 + <tr>
133 + <td>Sources:</td>
134 + <td id="psourceCount" class="stat"></td>
135 + </tr>
136 + <tr>
137 + <td>Dependents:</td>
138 + <td id="pdependentCount" class="stat"></td>
139 + </tr>
140 + <tr>
141 + <td>Cyclic Segments:</td>
142 + <td id="psegmentCount" class="stat"></td>
143 + </tr>
144 + <tr>
145 + <td>Cycles:</td>
146 + <td id="pcycleCount" class="stat"></td>
147 + </tr>
148 + </table>
149 + </div>
150 +<script>
151 +D3JS_PLACEHOLDER
152 +
153 + var catalog =
154 +DATA_PLACEHOLDER
155 + ;
156 +
157 + var diameter = 1000,
158 + radius = diameter / 2,
159 + innerRadius = radius - 300;
160 +
161 + var cluster = d3.layout.cluster()
162 + .size([360, innerRadius])
163 + .sort(null)
164 + .value(function(d) { return d.size; });
165 +
166 + var bundle = d3.layout.bundle();
167 +
168 + var line = d3.svg.line.radial()
169 + .interpolate("bundle")
170 + .tension(.75)
171 + .radius(function(d) { return d.y; })
172 + .angle(function(d) { return d.x / 180 * Math.PI; });
173 +
174 + var svg = d3.select("body").append("svg")
175 + .attr("width", diameter)
176 + .attr("height", diameter)
177 + .append("g")
178 + .attr("transform", "translate(" + radius + "," + radius + ")");
179 +
180 + var link = svg.append("g").selectAll(".link"),
181 + node = svg.append("g").selectAll(".node"),
182 + cycles = {}, highlightCycles, selectedNode;
183 +
184 + function isCyclicLink(l) {
185 + return highlightCycles &&
186 + (cycles[l.source.key + "-" + l.target.key] || cycles[l.target.key + "-" + l.source.key]);
187 + }
188 +
189 + function isCyclicPackageLink(l, p) {
190 + var key = l.source.key + "-" + l.target.key,
191 + rKey = l.target.key + "-" + l.source.key;
192 + return isCyclicLink(l) && (p.cycleSegments[key] || p.cycleSegments[rKey]);
193 + }
194 +
195 + function refreshPaths() {
196 + svg.selectAll("path.link").classed("link--cycle", isCyclicLink);
197 + }
198 +
199 + function processCatalog() {
200 + var nodes = cluster.nodes(packageHierarchy(catalog.packages)),
201 + links = packageImports(nodes),
202 + splines = bundle(links);
203 + cycles = catalog.cycleSegments;
204 +
205 + d3.select("input[type=checkbox]").on("change", function() {
206 + highlightCycles = this.checked;
207 + refreshPaths();
208 + });
209 +
210 + link = link
211 + .data(splines)
212 + .enter().append("path")
213 + .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
214 + .attr("class", "link")
215 + .classed("link--cycle", isCyclicLink)
216 + .attr("d", function(d, i) { return line(splines[i]); });
217 +
218 +
219 + node = node
220 + .data(nodes.filter(function(n) { return !n.children; }))
221 + .enter().append("text")
222 + .attr("class", "node")
223 + .attr("dy", ".31em")
224 + .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
225 + .style("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
226 + .text(function(d) { return d.key; })
227 + .on("focus", processSelect)
228 + .on("blur", processSelect);
229 +
230 + d3.select("input[type=range]").on("change", function() {
231 + line.tension(this.value / 100);
232 + svg.selectAll("path.link")
233 + .data(splines)
234 + .attr("d", function(d, i) { return line(splines[i]); });
235 + });
236 +
237 + d3.select("#packageCount").text(catalog.summary.packages);
238 + d3.select("#sourceCount").text(catalog.summary.sources);
239 + d3.select("#segmentCount").text(catalog.summary.cycleSegments);
240 + d3.select("#cycleCount").text(catalog.summary.cycles);
241 + }
242 +
243 + function processSelect(d) {
244 + if (selectedNode === d) {
245 + deselected(d);
246 + selectedNode = null;
247 +
248 + } else if (selectedNode) {
249 + deselected(selectedNode);
250 + selectedNode = null;
251 + selected(d);
252 +
253 + } else {
254 + selected(d);
255 + selectedNode = d;
256 + }
257 + }
258 +
259 + function selected(d) {
260 + node
261 + .each(function(n) { n.target = n.source = false; })
262 + .classed("node--focus", function(n) { return n === d; });
263 +
264 + link
265 + .classed("link--cycle", function(l) { return isCyclicPackageLink(l, d); })
266 + .classed("link--target", function(l) { if (l.target === d) return l.source.source = true; })
267 + .classed("link--source", function(l) { if (l.source === d) return l.target.target = true; })
268 + .filter(function(l) { return l.target === d || l.source === d; })
269 + .each(function() { this.parentNode.appendChild(this); });
270 +
271 + node
272 + .classed("node--target", function(n) { return n.target; })
273 + .classed("node--source", function(n) { return n.source; });
274 +
275 + d3.select("#psourceCount").text(d.size);
276 + d3.select("#pdependentCount").text(d.imports.length);
277 + d3.select("#psegmentCount").text(d.cycleSegmentCount);
278 + d3.select("#pcycleCount").text(d.cycleCount);
279 + d3.select(".details").classed("shown", function() { return true; });
280 + }
281 +
282 + function deselected(d) {
283 + link
284 + .classed("link--cycle", isCyclicLink)
285 + .classed("link--target", false)
286 + .classed("link--source", false);
287 +
288 + node
289 + .classed("node--target", false)
290 + .classed("node--source", false)
291 + .classed("node--focus", false);
292 + d3.select(".details").classed("shown", function() { return false; });
293 + }
294 +
295 + d3.select(self.frameElement).style("height", diameter + "px");
296 +
297 + // Lazily construct the package hierarchy.
298 + function packageHierarchy(packages) {
299 + var map = {}, cnt = 0;
300 +
301 + // Builds the structure top-down to the specified leaf or until
302 + // another leaf in which case hook this leaf to the same parent
303 + function buildHierarchy(leaf, i) {
304 + var leafName = leaf.name,
305 + node, name, parent = map[""], start = 0;
306 + while (start < leafName.length) {
307 + name = parentName(leafName, start);
308 + node = map[name];
309 + if (!node) {
310 + node = map[name] = parentNode(name, parent);
311 + parent.children.push(node);
312 +
313 + } else if (node.imports) {
314 + leaf.parent = parent;
315 + parent.children.push(leaf);
316 + break;
317 + }
318 + parent = node;
319 + start = name.length + 1;
320 + }
321 + }
322 +
323 + function parentNode(name, parent) {
324 + return {name: name, parent: parent, key: name, children: []};
325 + }
326 +
327 + function parentName(leafName, start) {
328 + var i = leafName.indexOf(".", start);
329 + return i > 0 ? leafName.substring(0, i) : leafName;
330 + }
331 +
332 + // First populate all packages as leafs
333 + packages.forEach(function(d) {
334 + map[d.name] = d;
335 + d.key = d.name;
336 + });
337 +
338 + // Next synthesize the intermediate structure, by-passing any leafs
339 + map[""] = parentNode("", null);
340 + var i = 0;
341 + packages.forEach(function(d) {
342 + buildHierarchy(d, i++);
343 + });
344 +
345 + return map[""];
346 + }
347 +
348 + // Return a list of imports for the given array of nodes.
349 + function packageImports(nodes) {
350 + var map = {},
351 + imports = [];
352 +
353 + // Compute a map from name to node.
354 + nodes.forEach(function(d) {
355 + map[d.name] = d;
356 + });
357 +
358 + // For each import, construct a link from the source to target node.
359 + nodes.forEach(function(d) {
360 + if (d.imports) d.imports.forEach(function(i) {
361 + imports.push({source: map[d.name], target: map[i]});
362 + });
363 + });
364 +
365 + return imports;
366 + }
367 +
368 + processCatalog();
369 +</script>
370 +</body>
371 +</html>
1 +package org.onlab.jdvue;
2 +
3 +import org.junit.Test;
4 +import org.onlab.jdvue.Catalog;
5 +import org.onlab.jdvue.JavaPackage;
6 +import org.onlab.jdvue.JavaSource;
7 +
8 +import java.io.IOException;
9 +
10 +import static org.junit.Assert.assertEquals;
11 +import static org.junit.Assert.assertNotNull;
12 +
13 +/**
14 + * Unit test for the source catalog.
15 + *
16 + * @author Thomas Vachuska
17 + */
18 +public class CatalogTest {
19 +
20 + @Test
21 + public void basics() throws IOException {
22 + Catalog cat = new Catalog();
23 + cat.load("src/test/resources/catalog.db");
24 + cat.analyze();
25 +
26 + assertEquals("incorrect package count", 12, cat.getPackages().size());
27 + assertEquals("incorrect source count", 14, cat.getSources().size());
28 +
29 + JavaPackage pkg = cat.getPackage("k");
30 + assertNotNull("package should be found", pkg);
31 +
32 + JavaSource src = cat.getSource("k.K");
33 + assertNotNull("source should be found", src);
34 +
35 + assertEquals("incorrect package source count", 1, pkg.getSources().size());
36 + assertEquals("incorrect package dependency count", 1, pkg.getDependencies().size());
37 + assertEquals("incorrect package cycle count", 3, cat.getPackageCycles(pkg).size());
38 +
39 + assertEquals("incorrect segment count", 11, cat.getCycleSegments().size());
40 + assertEquals("incorrect cycle count", 5, cat.getCycles().size());
41 + }
42 +
43 +}
1 +package org.onlab.jdvue;
2 +
3 +import org.junit.Test;
4 +import org.onlab.jdvue.DependencyCycle;
5 +import org.onlab.jdvue.JavaPackage;
6 +
7 +import java.util.Arrays;
8 +
9 +import static org.junit.Assert.*;
10 +
11 +/**
12 + * Unit test for the dependency cycle entity.
13 + *
14 + * @author Thomas Vachuska
15 + */
16 +public class DependencyCycleTest {
17 +
18 + @Test
19 + public void normalize() {
20 + JavaPackage x = new JavaPackage("x");
21 + JavaPackage y = new JavaPackage("y");
22 + JavaPackage z = new JavaPackage("z");
23 +
24 + DependencyCycle a = new DependencyCycle(Arrays.asList(new JavaPackage[] {x, y, z}), x);
25 + DependencyCycle b = new DependencyCycle(Arrays.asList(new JavaPackage[] {y, z, x}), y);
26 + DependencyCycle c = new DependencyCycle(Arrays.asList(new JavaPackage[] {z, x, y}), z);
27 +
28 + assertEquals("incorrect normalization", a, b);
29 + assertEquals("incorrect normalization", a, c);
30 + }
31 +
32 + @Test
33 + public void testToString() {
34 + JavaPackage x = new JavaPackage("x");
35 + JavaPackage y = new JavaPackage("y");
36 + JavaPackage z = new JavaPackage("z");
37 +
38 + DependencyCycle a = new DependencyCycle(Arrays.asList(new JavaPackage[] {x, y, z}), x);
39 + assertEquals("incorrect toString", "[x, y, z]", a.toShortString());
40 + assertEquals("incorrect toString",
41 + "DependencyCycle{cycle=[" +
42 + "JavaPackage{name=x, sources=0, dependencies=0}, " +
43 + "JavaPackage{name=y, sources=0, dependencies=0}, " +
44 + "JavaPackage{name=z, sources=0, dependencies=0}]}",
45 + a.toString());
46 + }
47 +}
1 +package org.onlab.jdvue;
2 +
3 +import com.google.common.testing.EqualsTester;
4 +import org.junit.Test;
5 +import org.onlab.jdvue.Dependency;
6 +import org.onlab.jdvue.JavaPackage;
7 +
8 +import static org.junit.Assert.assertEquals;
9 +import static org.junit.Assert.assertNotEquals;
10 +
11 +/**
12 + * Unit test for the dependency entity.
13 + *
14 + * @author Thomas Vachuska
15 + */
16 +public class DependencyTest {
17 +
18 + @Test
19 + public void basics() {
20 + JavaPackage x = new JavaPackage("x");
21 + JavaPackage y = new JavaPackage("y");
22 +
23 + new EqualsTester()
24 + .addEqualityGroup(new Dependency(x, y), new Dependency(x, y))
25 + .addEqualityGroup(new Dependency(y, x), new Dependency(y, x))
26 + .testEquals();
27 + }
28 +
29 +}
1 +package org.onlab.jdvue;
2 +
3 +import org.junit.Test;
4 +import org.onlab.jdvue.DependencyViewer;
5 +
6 +import java.io.FileInputStream;
7 +import java.io.IOException;
8 +
9 +import static org.junit.Assert.assertEquals;
10 +import static org.junit.Assert.assertNotEquals;
11 +import static org.onlab.jdvue.DependencyViewer.slurp;
12 +
13 +/**
14 + * Unit test for the dependency viewer.
15 + *
16 + * @author Thomas Vachuska
17 + */
18 +public class DependencyViewerTest {
19 +
20 + @Test
21 + public void basics() throws IOException {
22 + DependencyViewer.main(new String[]{"src/test/resources/catalog"});
23 +
24 + String expected = slurp(new FileInputStream("src/test/resources/expected.html"));
25 + String actual = slurp(new FileInputStream("src/test/resources/catalog.html"));
26 +
27 + // FIXME: add more manageable assertions here
28 +// assertEquals("incorrect html", expected, actual);
29 + }
30 +
31 +}
1 +src/main/java/a/A.java:package a;
2 +src/main/java/a/A.java:import b.B;
3 +src/main/java/a/A2.java:package a;
4 +src/main/java/a/A2.java:import c.C;
5 +src/main/java/b/B.java:package b;
6 +src/main/java/b/B.java:import c.C;
7 +src/main/java/c/C.java:package c;
8 +
9 +src/main/java/x/X.java:package x;
10 +src/main/java/x/X.java:import y.Y;
11 +src/main/java/y/Y.java:package y;
12 +src/main/java/y/Y.java:import z.Z;
13 +src/main/java/z/Z.java:package z;
14 +src/main/java/z/Z.java:import x.X;
15 +
16 +src/main/java/u/U.java:package u;
17 +src/main/java/u/U.java:import v.V;
18 +src/main/java/u/U2.java:package u;
19 +src/main/java/u/U2.java:import v.V;
20 +src/main/java/v/V.java:package v;
21 +src/main/java/v/V.java:import u.U;
22 +
23 +src/main/java/k/K.java:package k;
24 +src/main/java/k/K.java:import l.L;
25 +src/main/java/l/L.java:package l;
26 +src/main/java/l/L.java:import k.K;
27 +src/main/java/l/L.java:import m.M;
28 +src/main/java/l/L.java:import n.N;
29 +src/main/java/m/M.java:package m;
30 +src/main/java/m/M.java:import n.N;
31 +src/main/java/n/N.java:package n;
32 +src/main/java/n/N.java:import k.K;
33 +
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
39 <module>osgi</module> 39 <module>osgi</module>
40 <module>rest</module> 40 <module>rest</module>
41 <module>thirdparty</module> 41 <module>thirdparty</module>
42 + <module>jdvue</module>
42 <module>jnc</module> <!-- FIXME publish and remove before release --> 43 <module>jnc</module> <!-- FIXME publish and remove before release -->
43 </modules> 44 </modules>
44 45
......