Moved jdvue utility from ONOS-tools repo into onos repo.
Change-Id: I0bc1cef80541075c800c5309cb642a244a79fa0b
Showing
21 changed files
with
1681 additions
and
0 deletions
utils/jdvue/bin/jdvue
0 → 100755
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 |
utils/jdvue/bin/jdvue-scan
0 → 100755
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 |
utils/jdvue/pom.xml
0 → 100644
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 & 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 & 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 | +} |
utils/jdvue/src/test/resources/catalog.db
0 → 100644
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 | + |
utils/jdvue/src/test/resources/catalog.html
0 → 100644
This diff could not be displayed because it is too large.
utils/jdvue/src/test/resources/expected.html
0 → 100644
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 | ... | ... |
-
Please register or login to post a comment