diff options
Diffstat (limited to 'framework/src/onos/utils/jdvue/src/main/java/org/onlab/jdvue/Catalog.java')
-rw-r--r-- | framework/src/onos/utils/jdvue/src/main/java/org/onlab/jdvue/Catalog.java | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/framework/src/onos/utils/jdvue/src/main/java/org/onlab/jdvue/Catalog.java b/framework/src/onos/utils/jdvue/src/main/java/org/onlab/jdvue/Catalog.java new file mode 100644 index 00000000..8f611c52 --- /dev/null +++ b/framework/src/onos/utils/jdvue/src/main/java/org/onlab/jdvue/Catalog.java @@ -0,0 +1,391 @@ +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.MoreObjects.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 + * @throws IOException if unable to read the catalog file + */ + 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(); + } + +} |