package jdepend.framework; import java.io.IOException; import java.util.*; /** * The <code>JDepend</code> class analyzes directories of Java class files * and generates the following metrics for each Java package. * <p> * <ul> * <li>Afferent Coupling (Ca) * <p> * The number of packages that depend upon the classes within the analyzed * package. * </p> * </li> * <li>Efferent Coupling (Ce) * <p> * The number of packages that the classes in the analyzed package depend upon. * </p> * </li> * <li>Abstractness (A) * <p> * The ratio of the number of abstract classes (and interfaces) in the analyzed * package to the total number of classes in the analyzed package. * </p> * <p> * The range for this metric is 0 to 1, with A=0 indicating a completely * concrete package and A=1 indicating a completely abstract package. * </p> * </li> * <li>Instability (I) * <p> * The ratio of efferent coupling (Ce) to total coupling (Ce + Ca) such that I = * Ce / (Ce + Ca). * </p> * <p> * The range for this metric is 0 to 1, with I=0 indicating a completely stable * package and I=1 indicating a completely instable package. * </p> * </li> * <li>Distance from the Main Sequence (D) * <p> * The perpendicular distance of a package from the idealized line A + I = 1. A * package coincident with the main sequence is optimally balanced with respect * to its abstractness and stability. Ideal packages are either completely * abstract and stable (x=0, y=1) or completely concrete and instable (x=1, * y=0). * </p> * <p> * The range for this metric is 0 to 1, with D=0 indicating a package that is * coincident with the main sequence and D=1 indicating a package that is as far * from the main sequence as possible. * </p> * </li> * <li>Package Dependency Cycle * <p> * Package dependency cycles are reported along with the paths of packages * participating in package dependency cycles. * </p> * </li> * </ul> * <p> * These metrics are hereafter referred to as the "Martin Metrics", as they are * credited to Robert Martin (Object Mentor Inc.) and referenced in the book * "Designing Object Oriented C++ Applications using the Booch Method", by * Robert C. Martin, Prentice Hall, 1995. * </p> * <p> * Example API use: * <p> * <blockquote> * * <pre> * JDepend jdepend = new JDepend(); * jdepend.addDirectory("/path/to/classes"); * Collection packages = jdepend.analyze(); * * Iterator i = packages.iterator(); * while (i.hasNext()) { * JavaPackage jPackage = (JavaPackage) i.next(); * String name = jPackage.getName(); * int Ca = jPackage.afferentCoupling(); * int Ce = jPackage.efferentCoupling(); * float A = jPackage.abstractness(); * float I = jPackage.instability(); * float D = jPackage.distance(); * boolean b = jPackage.containsCycle(); * } * </pre> * * </blockquote> * </p> * <p> * This class is the data model used by the <code>jdepend.textui.JDepend</code> * and <code>jdepend.swingui.JDepend</code> views. * </p> * * @author <b>Mike Clark</b> * @author Clarkware Consulting, Inc. */ public class JDepend { private HashMap packages; private FileManager fileManager; private PackageFilter filter; private ClassFileParser parser; private JavaClassBuilder builder; private Collection components; public JDepend() { this(new PackageFilter()); } public JDepend(PackageFilter filter) { setFilter(filter); this.packages = new HashMap(); this.fileManager = new FileManager(); this.parser = new ClassFileParser(filter); this.builder = new JavaClassBuilder(parser, fileManager); PropertyConfigurator config = new PropertyConfigurator(); addPackages(config.getConfiguredPackages()); analyzeInnerClasses(config.getAnalyzeInnerClasses()); } /** * Analyzes the registered directories and returns the collection of * analyzed packages. * * @return Collection of analyzed packages. */ public Collection analyze() { Collection classes = builder.build(); for (Iterator i = classes.iterator(); i.hasNext();) { analyzeClass((JavaClass)i.next()); } return getPackages(); } /** * Adds the specified directory name to the collection of directories to be * analyzed. * * @param name Directory name. * @throws IOException If the directory is invalid. */ public void addDirectory(String name) throws IOException { fileManager.addDirectory(name); } /** * Sets the list of components. * * @param components Comma-separated list of components. */ public void setComponents(String components) { this.components = new ArrayList(); StringTokenizer st = new StringTokenizer(components, ","); while (st.hasMoreTokens()) { String component = st.nextToken(); this.components.add(component); } } /** * Determines whether inner classes are analyzed. * * @param b <code>true</code> to analyze inner classes; * <code>false</code> otherwise. */ public void analyzeInnerClasses(boolean b) { fileManager.acceptInnerClasses(b); } /** * Returns the collection of analyzed packages. * * @return Collection of analyzed packages. */ public Collection getPackages() { return packages.values(); } /** * Returns the analyzed package of the specified name. * * @param name Package name. * @return Package, or <code>null</code> if the package was not analyzed. */ public JavaPackage getPackage(String name) { return (JavaPackage)packages.get(name); } /** * Returns the number of analyzed Java packages. * * @return Number of Java packages. */ public int countPackages() { return getPackages().size(); } /** * Returns the number of registered Java classes to be analyzed. * * @return Number of classes. */ public int countClasses() { return builder.countClasses(); } /** * Indicates whether the packages contain one or more dependency cycles. * * @return <code>true</code> if one or more dependency cycles exist. */ public boolean containsCycles() { for (Iterator i = getPackages().iterator(); i.hasNext();) { JavaPackage jPackage = (JavaPackage)i.next(); if (jPackage.containsCycle()) { return true; } } return false; } /** * Indicates whether the analyzed packages match the specified * dependency constraint. * * @return <code>true</code> if the packages match the dependency * constraint */ public boolean dependencyMatch(DependencyConstraint constraint) { return constraint.match(getPackages()); } /** * Registers the specified parser listener. * * @param listener Parser listener. */ public void addParseListener(ParserListener listener) { parser.addParseListener(listener); } /** * Adds the specified Java package name to the collection of analyzed * packages. * * @param name Java package name. * @return Added Java package. */ public JavaPackage addPackage(String name) { name = toComponent(name); JavaPackage pkg = (JavaPackage)packages.get(name); if (pkg == null) { pkg = new JavaPackage(name); addPackage(pkg); } return pkg; } private String toComponent(String packageName) { if (components != null) { for (Iterator i = components.iterator(); i.hasNext();) { String component = (String)i.next(); if (packageName.startsWith(component + ".")) { return component; } } } return packageName; } /** * Adds the specified collection of packages to the collection * of analyzed packages. * * @param packages Collection of packages. */ public void addPackages(Collection packages) { for (Iterator i = packages.iterator(); i.hasNext();) { JavaPackage pkg = (JavaPackage)i.next(); addPackage(pkg); } } /** * Adds the specified Java package to the collection of * analyzed packages. * * @param pkg Java package. */ public void addPackage(JavaPackage pkg) { if (!packages.containsValue(pkg)) { packages.put(pkg.getName(), pkg); } } public PackageFilter getFilter() { if (filter == null) { filter = new PackageFilter(); } return filter; } public void setFilter(PackageFilter filter) { if (parser != null) { parser.setFilter(filter); } this.filter = filter; } private void analyzeClass(JavaClass clazz) { String packageName = clazz.getPackageName(); if (!getFilter().accept(packageName)) { return; } JavaPackage clazzPackage = addPackage(packageName); clazzPackage.addClass(clazz); Collection imports = clazz.getImportedPackages(); for (Iterator i = imports.iterator(); i.hasNext();) { JavaPackage importedPackage = (JavaPackage)i.next(); importedPackage = addPackage(importedPackage.getName()); clazzPackage.dependsUpon(importedPackage); } } }