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

Change-Id: I0bc1cef80541075c800c5309cb642a244a79fa0b
diff --git a/utils/jdvue/src/main/java/org/onlab/jdvue/Catalog.java b/utils/jdvue/src/main/java/org/onlab/jdvue/Catalog.java
new file mode 100644
index 0000000..c24a8a5
--- /dev/null
+++ b/utils/jdvue/src/main/java/org/onlab/jdvue/Catalog.java
@@ -0,0 +1,390 @@
+package org.onlab.jdvue;
+
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Objects.toStringHelper;
+
+/**
+ * Produces a package & source catalogue.
+ *
+ * @author Thomas Vachuska
+ */
+public class Catalog {
+
+    private static final String PACKAGE = "package";
+    private static final String IMPORT = "import";
+    private static final String STATIC = "static";
+    private static final String SRC_ROOT = "src/main/java/";
+    private static final String WILDCARD = "\\.*$";
+
+    private final Map<String, JavaSource> sources = new HashMap<>();
+    private final Map<String, JavaPackage> packages = new HashMap<>();
+    private final Set<DependencyCycle> cycles = new HashSet<>();
+    private final Set<Dependency> cycleSegments = new HashSet<>();
+    private final Map<JavaPackage, Set<DependencyCycle>> packageCycles = new HashMap<>();
+    private final Map<JavaPackage, Set<Dependency>> packageCycleSegments = new HashMap<>();
+
+    /**
+     * Loads the catalog from the specified catalog file.
+     *
+     * @param catalogPath catalog file path
+     */
+    public void load(String catalogPath) throws IOException {
+        InputStream is = new FileInputStream(catalogPath);
+        BufferedReader br = new BufferedReader(new InputStreamReader(is));
+
+        String line;
+        while ((line = br.readLine()) != null) {
+            // Split the line into the two fields: path and pragmas
+            String fields[] = line.trim().split(":");
+            if (fields.length <= 1) {
+                continue;
+            }
+            String path = fields[0];
+
+            // Now split the pragmas on whitespace and trim punctuation
+            String pragma[] = fields[1].trim().replaceAll("[;\n\r]", "").split("[\t ]");
+
+            // Locate (or create) Java source entity based on the path
+            JavaSource source = getOrCreateSource(path);
+
+            // Now process the package or import statements
+            if (pragma[0].equals(PACKAGE)) {
+                processPackageDeclaration(source, pragma[1]);
+
+            } else if (pragma[0].equals(IMPORT)) {
+                if (pragma[1].equals(STATIC)) {
+                    processImportStatement(source, pragma[2]);
+                } else {
+                    processImportStatement(source, pragma[1]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Analyzes the catalog by resolving imports and identifying circular
+     * package dependencies.
+     */
+    public void analyze() {
+        resolveImports();
+        findCircularDependencies();
+    }
+
+    /**
+     * Identifies circular package dependencies through what amounts to be a
+     * depth-first search rooted with each package.
+     */
+    private void findCircularDependencies() {
+        cycles.clear();
+        for (JavaPackage javaPackage : getPackages()) {
+            findCircularDependencies(javaPackage);
+        }
+
+        cycleSegments.clear();
+        packageCycles.clear();
+        packageCycleSegments.clear();
+
+        for (DependencyCycle cycle : getCycles()) {
+            recordCycleForPackages(cycle);
+            cycleSegments.addAll(cycle.getCycleSegments());
+        }
+    }
+
+    /**
+     * Records the specified cycle into a set for each involved package.
+     *
+     * @param cycle cycle to record for involved packages
+     */
+    private void recordCycleForPackages(DependencyCycle cycle) {
+        for (JavaPackage javaPackage : cycle.getCycle()) {
+            Set<DependencyCycle> cset = packageCycles.get(javaPackage);
+            if (cset == null) {
+                cset = new HashSet<>();
+                packageCycles.put(javaPackage, cset);
+            }
+            cset.add(cycle);
+
+            Set<Dependency> sset = packageCycleSegments.get(javaPackage);
+            if (sset == null) {
+                sset = new HashSet<>();
+                packageCycleSegments.put(javaPackage, sset);
+            }
+            sset.addAll(cycle.getCycleSegments());
+        }
+    }
+
+    /**
+     * Identifies circular dependencies in which this package participates
+     * using depth-first search.
+     *
+     * @param javaPackage Java package to inspect for dependency cycles
+     */
+    private void findCircularDependencies(JavaPackage javaPackage) {
+        // Setup a depth trace anchored at the given java package.
+        List<JavaPackage> trace = newTrace(new ArrayList<JavaPackage>(), javaPackage);
+
+        Set<JavaPackage> searched = new HashSet<>();
+        searchDependencies(javaPackage, trace, searched);
+    }
+
+    /**
+     * Generates a new trace using the previous one and a new element
+     *
+     * @param trace       old search trace
+     * @param javaPackage package to add to the trace
+     * @return new search trace
+     */
+    private List<JavaPackage> newTrace(List<JavaPackage> trace,
+                                       JavaPackage javaPackage) {
+        List<JavaPackage> newTrace = new ArrayList<>(trace);
+        newTrace.add(javaPackage);
+        return newTrace;
+    }
+
+
+    /**
+     * Recursive depth-first search through dependency tree
+     *
+     * @param javaPackage java package being searched currently
+     * @param trace       search trace
+     * @param searched    set of java packages already searched
+     */
+    private void searchDependencies(JavaPackage javaPackage,
+                                    List<JavaPackage> trace,
+                                    Set<JavaPackage> searched) {
+        if (!searched.contains(javaPackage)) {
+            searched.add(javaPackage);
+            for (JavaPackage dependency : javaPackage.getDependencies()) {
+                if (trace.contains(dependency)) {
+                    cycles.add(new DependencyCycle(trace, dependency));
+                } else {
+                    searchDependencies(dependency, newTrace(trace, dependency), searched);
+                }
+            }
+        }
+    }
+
+    /**
+     * Resolves import names of Java sources into imports of entities known
+     * to this catalog. All other import names will be ignored.
+     */
+    private void resolveImports() {
+        for (JavaPackage javaPackage : getPackages()) {
+            Set<JavaPackage> dependencies = new HashSet<>();
+            for (JavaSource source : javaPackage.getSources()) {
+                Set<JavaEntity> imports = resolveImports(source);
+                source.setImports(imports);
+                dependencies.addAll(importedPackages(imports));
+            }
+            javaPackage.setDependencies(dependencies);
+        }
+    }
+
+    /**
+     * Produces a set of imported Java packages from the specified set of
+     * Java source entities.
+     *
+     * @param imports list of imported Java source entities
+     * @return list of imported Java packages
+     */
+    private Set<JavaPackage> importedPackages(Set<JavaEntity> imports) {
+        Set<JavaPackage> packages = new HashSet<>();
+        for (JavaEntity entity : imports) {
+            packages.add(entity instanceof JavaPackage ? (JavaPackage) entity :
+                                 ((JavaSource) entity).getPackage());
+        }
+        return packages;
+    }
+
+    /**
+     * Resolves import names of the specified Java source into imports of
+     * entities known to this catalog. All other import names will be ignored.
+     *
+     * @param source Java source
+     * @return list of resolved imports
+     */
+    private Set<JavaEntity> resolveImports(JavaSource source) {
+        Set<JavaEntity> imports = new HashSet<>();
+        for (String importName : source.getImportNames()) {
+            JavaEntity entity = importName.matches(WILDCARD) ?
+                    getPackage(importName.replaceAll(WILDCARD, "")) :
+                    getSource(importName);
+            if (entity != null) {
+                imports.add(entity);
+            }
+        }
+        return imports;
+    }
+
+    /**
+     * Returns either an existing or a newly created Java package.
+     *
+     * @param packageName Java package name
+     * @return Java package
+     */
+    private JavaPackage getOrCreatePackage(String packageName) {
+        JavaPackage javaPackage = packages.get(packageName);
+        if (javaPackage == null) {
+            javaPackage = new JavaPackage(packageName);
+            packages.put(packageName, javaPackage);
+        }
+        return javaPackage;
+    }
+
+    /**
+     * Returns either an existing or a newly created Java source.
+     *
+     * @param path Java source path
+     * @return Java source
+     */
+    private JavaSource getOrCreateSource(String path) {
+        String name = nameFromPath(path);
+        JavaSource source = sources.get(name);
+        if (source == null) {
+            source = new JavaSource(name, path);
+            sources.put(name, source);
+        }
+        return source;
+    }
+
+    /**
+     * Extracts a fully qualified source class name from the given path.
+     * <p/>
+     * For now, this implementation assumes standard Maven source structure
+     * and thus will look for start of package name under 'src/main/java/'.
+     * If it will not find such a prefix, it will simply return the path as
+     * the name.
+     *
+     * @param path source path
+     * @return source name
+     */
+    private String nameFromPath(String path) {
+        int i = path.indexOf(SRC_ROOT);
+        String name = i < 0 ? path : path.substring(i + SRC_ROOT.length());
+        return name.replaceAll("\\.java$", "").replace("/", ".");
+    }
+
+    /**
+     * Processes the package declaration pragma for the given source.
+     *
+     * @param source      Java source
+     * @param packageName Java package name
+     */
+    private void processPackageDeclaration(JavaSource source, String packageName) {
+        JavaPackage javaPackage = getOrCreatePackage(packageName);
+        source.setPackage(javaPackage);
+        javaPackage.addSource(source);
+    }
+
+    /**
+     * Processes the import pragma for the given source.
+     *
+     * @param source Java source
+     * @param name   name of the Java entity being imported (class or package)
+     */
+    private void processImportStatement(JavaSource source, String name) {
+        source.addImportName(name);
+    }
+
+    /**
+     * Returns the collection of java sources.
+     *
+     * @return collection of java sources
+     */
+    public Collection<JavaSource> getSources() {
+        return Collections.unmodifiableCollection(sources.values());
+    }
+
+    /**
+     * Returns the Java source with the specified name.
+     *
+     * @param name Java source name
+     * @return Java source
+     */
+    public JavaSource getSource(String name) {
+        return sources.get(name);
+    }
+
+    /**
+     * Returns the collection of all Java packages.
+     *
+     * @return collection of java packages
+     */
+    public Collection<JavaPackage> getPackages() {
+        return Collections.unmodifiableCollection(packages.values());
+    }
+
+    /**
+     * Returns the set of all Java package dependency cycles.
+     *
+     * @return set of dependency cycles
+     */
+    public Set<DependencyCycle> getCycles() {
+        return Collections.unmodifiableSet(cycles);
+    }
+
+    /**
+     * Returns the set of all Java package dependency cycle segments.
+     *
+     * @return set of dependency cycle segments
+     */
+    public Set<Dependency> getCycleSegments() {
+        return Collections.unmodifiableSet(cycleSegments);
+    }
+
+    /**
+     * Returns the set of dependency cycles which involve the specified package.
+     *
+     * @param javaPackage java package
+     * @return set of dependency cycles
+     */
+    public Set<DependencyCycle> getPackageCycles(JavaPackage javaPackage) {
+        Set<DependencyCycle> set = packageCycles.get(javaPackage);
+        return Collections.unmodifiableSet(set == null ? new HashSet<DependencyCycle>() : set);
+    }
+
+    /**
+     * Returns the set of dependency cycle segments which involve the specified package.
+     *
+     * @param javaPackage java package
+     * @return set of dependency cycle segments
+     */
+    public Set<Dependency> getPackageCycleSegments(JavaPackage javaPackage) {
+        Set<Dependency> set = packageCycleSegments.get(javaPackage);
+        return Collections.unmodifiableSet(set == null ? new HashSet<Dependency>() : set);
+    }
+
+    /**
+     * Returns the Java package with the specified name.
+     *
+     * @param name Java package name
+     * @return Java package
+     */
+    public JavaPackage getPackage(String name) {
+        return packages.get(name);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("packages", packages.size())
+                .add("sources", sources.size())
+                .add("cycles", cycles.size())
+                .add("cycleSegments", cycleSegments.size()).toString();
+    }
+
+}
diff --git a/utils/jdvue/src/main/java/org/onlab/jdvue/Dependency.java b/utils/jdvue/src/main/java/org/onlab/jdvue/Dependency.java
new file mode 100644
index 0000000..e767a56
--- /dev/null
+++ b/utils/jdvue/src/main/java/org/onlab/jdvue/Dependency.java
@@ -0,0 +1,67 @@
+package org.onlab.jdvue;
+
+import java.util.Objects;
+
+import static com.google.common.base.Objects.toStringHelper;
+
+/**
+ * Abstraction of a dependency segment.
+ *
+ * @author Thomas Vachuska
+ */
+public class Dependency {
+
+    private final JavaPackage source;
+    private final JavaPackage target;
+
+    /**
+     * Creates a dependency from the specified source on the given target.
+     *
+     * @param source source of the dependency
+     * @param target target of the dependency
+     */
+    public Dependency(JavaPackage source, JavaPackage target) {
+        this.source = source;
+        this.target = target;
+    }
+
+    /**
+     * Returns the dependency source.
+     *
+     * @return source Java package
+     */
+    public JavaPackage getSource() {
+        return source;
+    }
+
+    /**
+     * Returns the dependency target.
+     *
+     * @return target Java package
+     */
+    public JavaPackage getTarget() {
+        return target;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Dependency) {
+            Dependency that = (Dependency) obj;
+            return Objects.equals(source, that.source) &&
+                    Objects.equals(target, that.target);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(source, target);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("source", source).add("target", target).toString();
+    }
+
+}
diff --git a/utils/jdvue/src/main/java/org/onlab/jdvue/DependencyCycle.java b/utils/jdvue/src/main/java/org/onlab/jdvue/DependencyCycle.java
new file mode 100644
index 0000000..ced9305
--- /dev/null
+++ b/utils/jdvue/src/main/java/org/onlab/jdvue/DependencyCycle.java
@@ -0,0 +1,118 @@
+package org.onlab.jdvue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Objects.toStringHelper;
+
+/**
+ * Simple representation of a Java package dependency cycle.
+ */
+public class DependencyCycle {
+
+    private final List<JavaPackage> cycle;
+
+    /**
+     * Creates a normalized dependency cycle represented by the specified list
+     * of Java packages, which are expected to be given in order of dependency.
+     * List is assumed to be non-empty.
+     *
+     * @param cycle list of Java packages in the dependency cycle
+     * @param cause Java package that caused the cycle
+     */
+    DependencyCycle(List<JavaPackage> cycle, JavaPackage cause) {
+        this.cycle = normalize(cycle, cause);
+    }
+
+    /**
+     * Produces a normalized dependency cycle list. Normalization is performed
+     * by rotating the list so that the package with the least lexicographic
+     * name is at the start of the list.
+     *
+     * @param cycle list of Java packages in the dependency cycle
+     * @param cause Java package that caused the cycle
+     * @return normalized cycle
+     */
+    private List<JavaPackage> normalize(List<JavaPackage> cycle, JavaPackage cause) {
+        int start = cycle.indexOf(cause);
+        List<JavaPackage> clone = new ArrayList<>(cycle.subList(start, cycle.size()));
+        int leastIndex = findIndexOfLeastName(clone);
+        Collections.rotate(clone, -leastIndex);
+        return Collections.unmodifiableList(clone);
+    }
+
+    /**
+     * Returns the index of the Java package with the least name.
+     *
+     * @param cycle list of Java packages in the dependency cycle
+     * @return index of the least Java package name
+     */
+    private int findIndexOfLeastName(List<JavaPackage> cycle) {
+        int leastIndex = 0;
+        String leastName = cycle.get(leastIndex).name();
+        for (int i = 1, n = cycle.size(); i < n; i++) {
+            JavaPackage javaPackage = cycle.get(i);
+            if (leastName.compareTo(javaPackage.name()) > 0) {
+                leastIndex = i;
+                leastName = javaPackage.name();
+            }
+        }
+        return leastIndex;
+    }
+
+    /**
+     * Returns the normalized Java package dependency cycle
+     *
+     * @return list of packages in the dependency cycle
+     */
+    public List<JavaPackage> getCycle() {
+        return cycle;
+    }
+
+    /**
+     * Returns the dependency cycle in form of individual dependencies.
+     *
+     * @return list of dependencies forming the cycle
+     */
+    public List<Dependency> getCycleSegments() {
+        List<Dependency> dependencies = new ArrayList<>();
+        for (int i = 0, n = cycle.size(); i < n; i++) {
+            dependencies.add(new Dependency(cycle.get(i), cycle.get(i < n - 1 ? i + 1 : 0)));
+        }
+        return dependencies;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof DependencyCycle) {
+            DependencyCycle that = (DependencyCycle) o;
+            return Objects.equals(cycle, that.cycle);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(cycle);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this).add("cycle", cycle).toString();
+    }
+
+    public String toShortString() {
+        StringBuilder sb = new StringBuilder("[");
+        for (JavaPackage javaPackage : cycle) {
+            sb.append(javaPackage.name()).append(", ");
+        }
+        if (sb.length() > 1) {
+            sb.delete(sb.length() - 2, sb.length());
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+}
diff --git a/utils/jdvue/src/main/java/org/onlab/jdvue/DependencyViewer.java b/utils/jdvue/src/main/java/org/onlab/jdvue/DependencyViewer.java
new file mode 100644
index 0000000..a210d71
--- /dev/null
+++ b/utils/jdvue/src/main/java/org/onlab/jdvue/DependencyViewer.java
@@ -0,0 +1,188 @@
+package org.onlab.jdvue;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Set;
+
+/**
+ * Generator of a self-contained HTML file which serves as a GUI for
+ * visualizing Java package dependencies carried in the supplied catalog.
+ *
+ * The HTML file is an adaptation of D3.js Hierarchical Edge Bundling as
+ * shown at http://bl.ocks.org/mbostock/7607999.
+ *
+ * @author Thomas Vachuska
+ */
+public class DependencyViewer {
+
+    private static final String JPD_EXT = ".db";
+    private static final String HTML_EXT = ".html";
+
+    private static final String INDEX = "index.html";
+    private static final String D3JS = "d3.v3.min.js";
+
+    private static final String TITLE_PLACEHOLDER = "TITLE_PLACEHOLDER";
+    private static final String D3JS_PLACEHOLDER = "D3JS_PLACEHOLDER";
+    private static final String DATA_PLACEHOLDER = "DATA_PLACEHOLDER";
+
+    private final Catalog catalog;
+
+    /**
+     * Creates a Java package dependency viewer.
+     *
+     * @param catalog dependency catalog
+     */
+    public DependencyViewer(Catalog catalog) {
+        this.catalog = catalog;
+    }
+
+    /**
+     * Main program entry point.
+     *
+     * @param args command line arguments
+     */
+    public static void main(String[] args) {
+        Catalog cat = new Catalog();
+        DependencyViewer viewer = new DependencyViewer(cat);
+        try {
+            String path = args[0];
+            cat.load(path + JPD_EXT);
+            cat.analyze();
+
+            System.err.println(cat);
+            viewer.dumpLongestCycle(cat);
+            viewer.writeHTMLFile(path);
+        } catch (IOException e) {
+            System.err.println("Unable to process catalog: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Prints out the longest cycle; just for kicks.
+     * @param cat catalog
+     */
+    private void dumpLongestCycle(Catalog cat) {
+        DependencyCycle longest = null;
+        for (DependencyCycle cycle : cat.getCycles()) {
+            if (longest == null || longest.getCycleSegments().size() < cycle.getCycleSegments().size()) {
+                longest = cycle;
+            }
+        }
+
+        if (longest != null) {
+            for (Dependency dependency : longest.getCycleSegments()) {
+                System.out.println(dependency);
+            }
+        }
+    }
+
+    /**
+     * Writes the HTML catalog file for the given viewer.
+     *
+     * @param path base file path
+     * @throws IOException if issues encountered writing the HTML file
+     */
+    public void writeHTMLFile(String path) throws IOException {
+        String index = slurp(getClass().getResourceAsStream(INDEX));
+        String d3js = slurp(getClass().getResourceAsStream(D3JS));
+
+        FileWriter fw = new FileWriter(path + HTML_EXT);
+        ObjectWriter writer = new ObjectMapper().writer(); // .writerWithDefaultPrettyPrinter();
+        fw.write(index.replace(TITLE_PLACEHOLDER, path)
+                         .replace(D3JS_PLACEHOLDER, d3js)
+                         .replace(DATA_PLACEHOLDER, writer.writeValueAsString(toJson())));
+        fw.close();
+    }
+
+    /**
+     * Slurps the specified input stream into a string.
+     *
+     * @param stream input stream to be read
+     * @return string containing the contents of the input stream
+     * @throws IOException if issues encountered reading from the stream
+     */
+     static String slurp(InputStream stream) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        BufferedReader br = new BufferedReader(new InputStreamReader(stream));
+        String line;
+        while ((line = br.readLine()) != null) {
+            sb.append(line).append(System.lineSeparator());
+        }
+        br.close();
+        return sb.toString();
+    }
+
+    // Produces a JSON structure designed to drive the hierarchical visual
+    // representation of Java package dependencies and any dependency cycles
+     private JsonNode toJson() {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode root = mapper.createObjectNode();
+        root.put("packages", jsonPackages(mapper));
+        root.put("cycleSegments", jsonCycleSegments(mapper, catalog.getCycleSegments()));
+        root.put("summary", jsonSummary(mapper));
+        return root;
+    }
+
+    // Produces a JSON summary of dependencies
+    private JsonNode jsonSummary(ObjectMapper mapper) {
+        ObjectNode summary = mapper.createObjectNode();
+        summary.put("packages", catalog.getPackages().size());
+        summary.put("sources", catalog.getSources().size());
+        summary.put("cycles", catalog.getCycles().size());
+        summary.put("cycleSegments", catalog.getCycleSegments().size());
+        return summary;
+    }
+
+    // Produces a JSON structure with package dependency data
+    private JsonNode jsonPackages(ObjectMapper mapper) {
+        ArrayNode packages = mapper.createArrayNode();
+        for (JavaPackage javaPackage : catalog.getPackages()) {
+            packages.add(json(mapper, javaPackage));
+        }
+        return packages;
+    }
+
+    // Produces a JSON structure with all cyclic segments
+    private JsonNode jsonCycleSegments(ObjectMapper mapper,
+                                       Set<Dependency> segments) {
+        ObjectNode cyclicSegments = mapper.createObjectNode();
+        for (Dependency dependency : segments) {
+            String s = dependency.getSource().name();
+            String t = dependency.getTarget().name();
+            cyclicSegments.put(t + "-" + s,
+                               mapper.createObjectNode().put("s", s).put("t", t));
+        }
+        return cyclicSegments;
+    }
+
+    // Produces a JSON object structure describing the specified Java package.
+    private JsonNode json(ObjectMapper mapper, JavaPackage javaPackage) {
+        ObjectNode node = mapper.createObjectNode();
+
+        ArrayNode imports = mapper.createArrayNode();
+        for (JavaPackage dependency : javaPackage.getDependencies()) {
+            imports.add(dependency.name());
+        }
+
+        Set<DependencyCycle> packageCycles = catalog.getPackageCycles(javaPackage);
+        Set<Dependency> packageCycleSegments = catalog.getPackageCycleSegments(javaPackage);
+
+        node.put("name", javaPackage.name());
+        node.put("size", javaPackage.getSources().size());
+        node.put("imports", imports);
+        node.put("cycleSegments", jsonCycleSegments(mapper, packageCycleSegments));
+        node.put("cycleCount", packageCycles.size());
+        node.put("cycleSegmentCount", packageCycleSegments.size());
+        return node;
+    }
+
+}
diff --git a/utils/jdvue/src/main/java/org/onlab/jdvue/JavaEntity.java b/utils/jdvue/src/main/java/org/onlab/jdvue/JavaEntity.java
new file mode 100644
index 0000000..3195930
--- /dev/null
+++ b/utils/jdvue/src/main/java/org/onlab/jdvue/JavaEntity.java
@@ -0,0 +1,44 @@
+package org.onlab.jdvue;
+
+import java.util.Objects;
+
+/**
+ * Abstraction of a Java source entity.
+ */
+public abstract class JavaEntity {
+
+    private final String name;
+
+    /**
+     * Creates a new Java source entity with the given name.
+     *
+     * @param name source entity name
+     */
+    JavaEntity(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the Java source entity name.
+     *
+     * @return source entity name
+     */
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof JavaEntity) {
+            JavaEntity that = (JavaEntity) o;
+            return getClass().equals(that.getClass()) &&
+                    Objects.equals(name, that.name);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name);
+    }
+}
diff --git a/utils/jdvue/src/main/java/org/onlab/jdvue/JavaPackage.java b/utils/jdvue/src/main/java/org/onlab/jdvue/JavaPackage.java
new file mode 100644
index 0000000..9dcd95c
--- /dev/null
+++ b/utils/jdvue/src/main/java/org/onlab/jdvue/JavaPackage.java
@@ -0,0 +1,79 @@
+package org.onlab.jdvue;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.google.common.base.Objects.toStringHelper;
+
+/**
+ * Simple abstraction of a Java package for the purpose of tracking
+ * dependencies and requirements.
+ *
+ * @author Thomas Vachuska
+ */
+public class JavaPackage extends JavaEntity {
+
+    private final Set<JavaSource> sources = new HashSet<>();
+    private Set<JavaPackage> dependencies;
+
+    /**
+     * Creates a new Java package.
+     *
+     * @param name java package file name
+     */
+    JavaPackage(String name) {
+        super(name);
+    }
+
+    /**
+     * Returns the set of sources contained in this Java package.
+     *
+     * @return set of Java sources
+     */
+    public Set<JavaSource> getSources() {
+        return Collections.unmodifiableSet(sources);
+    }
+
+    /**
+     * Adds the specified Java source to the package. Only possible if the
+     * Java package of the source is the same as this Java package.
+     *
+     * @param source Java source to be added
+     */
+    void addSource(JavaSource source) {
+        if (source.getPackage().equals(this)) {
+            sources.add(source);
+        }
+    }
+
+    /**
+     * Returns the set of packages directly required by this package.
+     *
+     * @return set of Java package dependencies
+     */
+    Set<JavaPackage> getDependencies() {
+        return dependencies;
+    }
+
+    /**
+     * Sets the set of resolved Java packages on which this package dependens.
+     *
+     * @param dependencies set of resolved Java packages
+     */
+    void setDependencies(Set<JavaPackage> dependencies) {
+        if (this.dependencies == null) {
+            this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
+        }
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("name", name())
+                .add("sources", sources.size())
+                .add("dependencies", (dependencies != null ? dependencies.size() : 0))
+                .toString();
+    }
+
+}
diff --git a/utils/jdvue/src/main/java/org/onlab/jdvue/JavaSource.java b/utils/jdvue/src/main/java/org/onlab/jdvue/JavaSource.java
new file mode 100644
index 0000000..13c06ca
--- /dev/null
+++ b/utils/jdvue/src/main/java/org/onlab/jdvue/JavaSource.java
@@ -0,0 +1,97 @@
+package org.onlab.jdvue;
+
+import java.util.*;
+
+import static com.google.common.base.Objects.toStringHelper;
+
+/**
+ * Simple abstraction of a Java source file for the purpose of tracking
+ * dependencies and requirements.
+ *
+ * @author Thomas Vachuska
+ */
+public class JavaSource extends JavaEntity {
+
+    private String path;
+    private JavaPackage javaPackage;
+
+    private final Set<String> importNames = new HashSet<>();
+    private Set<JavaEntity> imports;
+
+    /**
+     * Creates a new Java source entity.
+     *
+     * @param name java source file name
+     */
+    JavaSource(String name, String path) {
+        super(name);
+        this.path = path;
+    }
+
+    /**
+     * Returns the Java package for this Java source.
+     *
+     * @return Java package
+     */
+    public JavaPackage getPackage() {
+        return javaPackage;
+    }
+
+    /**
+     * Sets the Java package for this Java source.
+     *
+     * @param javaPackage Java package
+     */
+    void setPackage(JavaPackage javaPackage) {
+        if (this.javaPackage == null) {
+            this.javaPackage = javaPackage;
+        }
+    }
+
+    /**
+     * Returns the set of resolved imports for this Java source
+     * @return set of imports
+     */
+    public Set<JavaEntity> getImports() {
+        return imports;
+    }
+
+    /**
+     * Sets the set of resolved imported Java entities for this source.
+     *
+     * @param imports set of resolved Java entities imported by this source
+     */
+    void setImports(Set<JavaEntity> imports) {
+        if (this.imports == null) {
+            this.imports = Collections.unmodifiableSet(new HashSet<>(imports));
+        }
+    }
+
+    /**
+     * Adds a name of an imported, but unresolved, Java entity name.
+     *
+     * @param name name of an imported Java entity
+     */
+    void addImportName(String name) {
+        importNames.add(name);
+    }
+
+    /**
+     * Returns the set of imported, but unresolved, Java entity names.
+     * @return set of imported Java entity names
+     */
+    Set<String> getImportNames() {
+        return importNames;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("name", name())
+                .add("javaPackage", (javaPackage != null ? javaPackage.name() : ""))
+                .add("importNames", importNames.size())
+                .add("imports", (imports != null ? imports.size() : 0))
+                .toString();
+    }
+
+}
diff --git a/utils/jdvue/src/main/java/org/onlab/jdvue/package-info.java b/utils/jdvue/src/main/java/org/onlab/jdvue/package-info.java
new file mode 100644
index 0000000..2f77ef2
--- /dev/null
+++ b/utils/jdvue/src/main/java/org/onlab/jdvue/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Utility to analyze Java package dependencies.
+ */
+package org.onlab.jdvue;
\ No newline at end of file