001: package jdepend.framework;
002:
003: import java.io.IOException;
004: import java.util.*;
005:
006: /**
007: * The <code>JDepend</code> class analyzes directories of Java class files
008: * and generates the following metrics for each Java package.
009: * <p>
010: * <ul>
011: * <li>Afferent Coupling (Ca)
012: * <p>
013: * The number of packages that depend upon the classes within the analyzed
014: * package.
015: * </p>
016: * </li>
017: * <li>Efferent Coupling (Ce)
018: * <p>
019: * The number of packages that the classes in the analyzed package depend upon.
020: * </p>
021: * </li>
022: * <li>Abstractness (A)
023: * <p>
024: * The ratio of the number of abstract classes (and interfaces) in the analyzed
025: * package to the total number of classes in the analyzed package.
026: * </p>
027: * <p>
028: * The range for this metric is 0 to 1, with A=0 indicating a completely
029: * concrete package and A=1 indicating a completely abstract package.
030: * </p>
031: * </li>
032: * <li>Instability (I)
033: * <p>
034: * The ratio of efferent coupling (Ce) to total coupling (Ce + Ca) such that I =
035: * Ce / (Ce + Ca).
036: * </p>
037: * <p>
038: * The range for this metric is 0 to 1, with I=0 indicating a completely stable
039: * package and I=1 indicating a completely instable package.
040: * </p>
041: * </li>
042: * <li>Distance from the Main Sequence (D)
043: * <p>
044: * The perpendicular distance of a package from the idealized line A + I = 1. A
045: * package coincident with the main sequence is optimally balanced with respect
046: * to its abstractness and stability. Ideal packages are either completely
047: * abstract and stable (x=0, y=1) or completely concrete and instable (x=1,
048: * y=0).
049: * </p>
050: * <p>
051: * The range for this metric is 0 to 1, with D=0 indicating a package that is
052: * coincident with the main sequence and D=1 indicating a package that is as far
053: * from the main sequence as possible.
054: * </p>
055: * </li>
056: * <li>Package Dependency Cycle
057: * <p>
058: * Package dependency cycles are reported along with the paths of packages
059: * participating in package dependency cycles.
060: * </p>
061: * </li>
062: * </ul>
063: * <p>
064: * These metrics are hereafter referred to as the "Martin Metrics", as they are
065: * credited to Robert Martin (Object Mentor Inc.) and referenced in the book
066: * "Designing Object Oriented C++ Applications using the Booch Method", by
067: * Robert C. Martin, Prentice Hall, 1995.
068: * </p>
069: * <p>
070: * Example API use:
071: * <p>
072: * <blockquote>
073: *
074: * <pre>
075: * JDepend jdepend = new JDepend();
076: * jdepend.addDirectory("/path/to/classes");
077: * Collection packages = jdepend.analyze();
078: *
079: * Iterator i = packages.iterator();
080: * while (i.hasNext()) {
081: * JavaPackage jPackage = (JavaPackage) i.next();
082: * String name = jPackage.getName();
083: * int Ca = jPackage.afferentCoupling();
084: * int Ce = jPackage.efferentCoupling();
085: * float A = jPackage.abstractness();
086: * float I = jPackage.instability();
087: * float D = jPackage.distance();
088: * boolean b = jPackage.containsCycle();
089: * }
090: * </pre>
091: *
092: * </blockquote>
093: * </p>
094: * <p>
095: * This class is the data model used by the <code>jdepend.textui.JDepend</code>
096: * and <code>jdepend.swingui.JDepend</code> views.
097: * </p>
098: *
099: * @author <b>Mike Clark</b>
100: * @author Clarkware Consulting, Inc.
101: */
102:
103: public class JDepend {
104:
105: private HashMap packages;
106: private FileManager fileManager;
107: private PackageFilter filter;
108: private ClassFileParser parser;
109: private JavaClassBuilder builder;
110: private Collection components;
111:
112: public JDepend() {
113: this (new PackageFilter());
114: }
115:
116: public JDepend(PackageFilter filter) {
117:
118: setFilter(filter);
119:
120: this .packages = new HashMap();
121: this .fileManager = new FileManager();
122:
123: this .parser = new ClassFileParser(filter);
124: this .builder = new JavaClassBuilder(parser, fileManager);
125:
126: PropertyConfigurator config = new PropertyConfigurator();
127: addPackages(config.getConfiguredPackages());
128: analyzeInnerClasses(config.getAnalyzeInnerClasses());
129: }
130:
131: /**
132: * Analyzes the registered directories and returns the collection of
133: * analyzed packages.
134: *
135: * @return Collection of analyzed packages.
136: */
137: public Collection analyze() {
138:
139: Collection classes = builder.build();
140:
141: for (Iterator i = classes.iterator(); i.hasNext();) {
142: analyzeClass((JavaClass) i.next());
143: }
144:
145: return getPackages();
146: }
147:
148: /**
149: * Adds the specified directory name to the collection of directories to be
150: * analyzed.
151: *
152: * @param name Directory name.
153: * @throws IOException If the directory is invalid.
154: */
155: public void addDirectory(String name) throws IOException {
156: fileManager.addDirectory(name);
157: }
158:
159: /**
160: * Sets the list of components.
161: *
162: * @param components Comma-separated list of components.
163: */
164: public void setComponents(String components) {
165: this .components = new ArrayList();
166: StringTokenizer st = new StringTokenizer(components, ",");
167: while (st.hasMoreTokens()) {
168: String component = st.nextToken();
169: this .components.add(component);
170: }
171: }
172:
173: /**
174: * Determines whether inner classes are analyzed.
175: *
176: * @param b <code>true</code> to analyze inner classes;
177: * <code>false</code> otherwise.
178: */
179: public void analyzeInnerClasses(boolean b) {
180: fileManager.acceptInnerClasses(b);
181: }
182:
183: /**
184: * Returns the collection of analyzed packages.
185: *
186: * @return Collection of analyzed packages.
187: */
188: public Collection getPackages() {
189: return packages.values();
190: }
191:
192: /**
193: * Returns the analyzed package of the specified name.
194: *
195: * @param name Package name.
196: * @return Package, or <code>null</code> if the package was not analyzed.
197: */
198: public JavaPackage getPackage(String name) {
199: return (JavaPackage) packages.get(name);
200: }
201:
202: /**
203: * Returns the number of analyzed Java packages.
204: *
205: * @return Number of Java packages.
206: */
207: public int countPackages() {
208: return getPackages().size();
209: }
210:
211: /**
212: * Returns the number of registered Java classes to be analyzed.
213: *
214: * @return Number of classes.
215: */
216: public int countClasses() {
217: return builder.countClasses();
218: }
219:
220: /**
221: * Indicates whether the packages contain one or more dependency cycles.
222: *
223: * @return <code>true</code> if one or more dependency cycles exist.
224: */
225: public boolean containsCycles() {
226: for (Iterator i = getPackages().iterator(); i.hasNext();) {
227: JavaPackage jPackage = (JavaPackage) i.next();
228: if (jPackage.containsCycle()) {
229: return true;
230: }
231: }
232:
233: return false;
234: }
235:
236: /**
237: * Indicates whether the analyzed packages match the specified
238: * dependency constraint.
239: *
240: * @return <code>true</code> if the packages match the dependency
241: * constraint
242: */
243: public boolean dependencyMatch(DependencyConstraint constraint) {
244: return constraint.match(getPackages());
245: }
246:
247: /**
248: * Registers the specified parser listener.
249: *
250: * @param listener Parser listener.
251: */
252: public void addParseListener(ParserListener listener) {
253: parser.addParseListener(listener);
254: }
255:
256: /**
257: * Adds the specified Java package name to the collection of analyzed
258: * packages.
259: *
260: * @param name Java package name.
261: * @return Added Java package.
262: */
263: public JavaPackage addPackage(String name) {
264: name = toComponent(name);
265: JavaPackage pkg = (JavaPackage) packages.get(name);
266: if (pkg == null) {
267: pkg = new JavaPackage(name);
268: addPackage(pkg);
269: }
270:
271: return pkg;
272: }
273:
274: private String toComponent(String packageName) {
275: if (components != null) {
276: for (Iterator i = components.iterator(); i.hasNext();) {
277: String component = (String) i.next();
278: if (packageName.startsWith(component + ".")) {
279: return component;
280: }
281: }
282: }
283: return packageName;
284: }
285:
286: /**
287: * Adds the specified collection of packages to the collection
288: * of analyzed packages.
289: *
290: * @param packages Collection of packages.
291: */
292: public void addPackages(Collection packages) {
293: for (Iterator i = packages.iterator(); i.hasNext();) {
294: JavaPackage pkg = (JavaPackage) i.next();
295: addPackage(pkg);
296: }
297: }
298:
299: /**
300: * Adds the specified Java package to the collection of
301: * analyzed packages.
302: *
303: * @param pkg Java package.
304: */
305: public void addPackage(JavaPackage pkg) {
306: if (!packages.containsValue(pkg)) {
307: packages.put(pkg.getName(), pkg);
308: }
309: }
310:
311: public PackageFilter getFilter() {
312: if (filter == null) {
313: filter = new PackageFilter();
314: }
315:
316: return filter;
317: }
318:
319: public void setFilter(PackageFilter filter) {
320: if (parser != null) {
321: parser.setFilter(filter);
322: }
323: this .filter = filter;
324: }
325:
326: private void analyzeClass(JavaClass clazz) {
327:
328: String packageName = clazz.getPackageName();
329:
330: if (!getFilter().accept(packageName)) {
331: return;
332: }
333:
334: JavaPackage clazzPackage = addPackage(packageName);
335: clazzPackage.addClass(clazz);
336:
337: Collection imports = clazz.getImportedPackages();
338: for (Iterator i = imports.iterator(); i.hasNext();) {
339: JavaPackage importedPackage = (JavaPackage) i.next();
340: importedPackage = addPackage(importedPackage.getName());
341: clazzPackage.dependsUpon(importedPackage);
342: }
343: }
344: }
|