001: package jdepend.textui;
002:
003: import java.io.*;
004: import java.util.*;
005: import java.text.NumberFormat;
006:
007: import jdepend.framework.JavaClass;
008: import jdepend.framework.JavaPackage;
009: import jdepend.framework.PackageComparator;
010: import jdepend.framework.PackageFilter;
011:
012: /**
013: * The <code>JDepend</code> class analyzes directories of Java class files,
014: * generates metrics for each Java package, and reports the metrics in a textual
015: * format.
016: *
017: * @author <b>Mike Clark</b>
018: * @author Clarkware Consulting, Inc.
019: */
020:
021: public class JDepend {
022:
023: private jdepend.framework.JDepend analyzer;
024:
025: private PrintWriter writer;
026:
027: protected NumberFormat formatter;
028:
029: /**
030: * Constructs a <code>JDepend</code> instance using standard output.
031: */
032: public JDepend() {
033: this (new PrintWriter(System.out));
034: }
035:
036: /**
037: * Constructs a <code>JDepend</code> instance with the specified writer.
038: *
039: * @param writer Writer.
040: */
041: public JDepend(PrintWriter writer) {
042: analyzer = new jdepend.framework.JDepend();
043:
044: formatter = NumberFormat.getInstance();
045: formatter.setMaximumFractionDigits(2);
046:
047: setWriter(writer);
048: }
049:
050: /**
051: * Sets the output writer.
052: *
053: * @param writer Output writer.
054: */
055: public void setWriter(PrintWriter writer) {
056: this .writer = writer;
057: }
058:
059: protected PrintWriter getWriter() {
060: return writer;
061: }
062:
063: /**
064: * Sets the package filter.
065: *
066: * @param filter Package filter.
067: */
068: public void setFilter(PackageFilter filter) {
069: analyzer.setFilter(filter);
070: }
071:
072: /**
073: * Sets the comma-separated list of components.
074: */
075: public void setComponents(String components) {
076: analyzer.setComponents(components);
077: }
078:
079: /**
080: * Adds the specified directory name to the collection of directories to be
081: * analyzed.
082: *
083: * @param name Directory name.
084: * @throws IOException If the directory does not exist.
085: */
086: public void addDirectory(String name) throws IOException {
087: analyzer.addDirectory(name);
088: }
089:
090: /**
091: * Determines whether inner classes are analyzed.
092: *
093: * @param b <code>true</code> to analyze inner classes; <code>false</code>
094: * otherwise.
095: */
096: public void analyzeInnerClasses(boolean b) {
097: analyzer.analyzeInnerClasses(b);
098: }
099:
100: /**
101: * Analyzes the registered directories, generates metrics for each Java
102: * package, and reports the metrics.
103: */
104: public void analyze() {
105:
106: printHeader();
107:
108: Collection packages = analyzer.analyze();
109:
110: ArrayList packageList = new ArrayList(packages);
111:
112: Collections.sort(packageList, new PackageComparator(
113: PackageComparator.byName()));
114:
115: printPackages(packageList);
116:
117: printCycles(packageList);
118:
119: printSummary(packageList);
120:
121: printFooter();
122:
123: getWriter().flush();
124: }
125:
126: protected void printPackages(Collection packages) {
127: printPackagesHeader();
128:
129: Iterator i = packages.iterator();
130: while (i.hasNext()) {
131: printPackage((JavaPackage) i.next());
132: }
133:
134: printPackagesFooter();
135: }
136:
137: protected void printPackage(JavaPackage jPackage) {
138:
139: printPackageHeader(jPackage);
140:
141: if (jPackage.getClasses().size() == 0) {
142: printNoStats();
143: printPackageFooter(jPackage);
144: return;
145: }
146:
147: printStatistics(jPackage);
148:
149: printSectionBreak();
150:
151: printAbstractClasses(jPackage);
152:
153: printSectionBreak();
154:
155: printConcreteClasses(jPackage);
156:
157: printSectionBreak();
158:
159: printEfferents(jPackage);
160:
161: printSectionBreak();
162:
163: printAfferents(jPackage);
164:
165: printPackageFooter(jPackage);
166: }
167:
168: protected void printAbstractClasses(JavaPackage jPackage) {
169: printAbstractClassesHeader();
170:
171: ArrayList members = new ArrayList(jPackage.getClasses());
172: Collections.sort(members, new JavaClass.ClassComparator());
173: Iterator memberIter = members.iterator();
174: while (memberIter.hasNext()) {
175: JavaClass jClass = (JavaClass) memberIter.next();
176: if (jClass.isAbstract()) {
177: printClassName(jClass);
178: }
179: }
180:
181: printAbstractClassesFooter();
182: }
183:
184: protected void printConcreteClasses(JavaPackage jPackage) {
185: printConcreteClassesHeader();
186:
187: ArrayList members = new ArrayList(jPackage.getClasses());
188: Collections.sort(members, new JavaClass.ClassComparator());
189: Iterator memberIter = members.iterator();
190: while (memberIter.hasNext()) {
191: JavaClass concrete = (JavaClass) memberIter.next();
192: if (!concrete.isAbstract()) {
193: printClassName(concrete);
194: }
195: }
196:
197: printConcreteClassesFooter();
198: }
199:
200: protected void printEfferents(JavaPackage jPackage) {
201: printEfferentsHeader();
202:
203: ArrayList efferents = new ArrayList(jPackage.getEfferents());
204: Collections.sort(efferents, new PackageComparator(
205: PackageComparator.byName()));
206: Iterator efferentIter = efferents.iterator();
207: while (efferentIter.hasNext()) {
208: JavaPackage efferent = (JavaPackage) efferentIter.next();
209: printPackageName(efferent);
210: }
211: if (efferents.size() == 0) {
212: printEfferentsError();
213: }
214:
215: printEfferentsFooter();
216: }
217:
218: protected void printAfferents(JavaPackage jPackage) {
219: printAfferentsHeader();
220:
221: ArrayList afferents = new ArrayList(jPackage.getAfferents());
222: Collections.sort(afferents, new PackageComparator(
223: PackageComparator.byName()));
224: Iterator afferentIter = afferents.iterator();
225: while (afferentIter.hasNext()) {
226: JavaPackage afferent = (JavaPackage) afferentIter.next();
227: printPackageName(afferent);
228: }
229: if (afferents.size() == 0) {
230: printAfferentsError();
231: }
232:
233: printAfferentsFooter();
234: }
235:
236: protected void printCycles(Collection packages) {
237: printCyclesHeader();
238:
239: Iterator i = packages.iterator();
240: while (i.hasNext()) {
241: printCycle((JavaPackage) i.next());
242: }
243:
244: printCyclesFooter();
245: }
246:
247: protected void printCycle(JavaPackage jPackage) {
248:
249: List list = new ArrayList();
250: jPackage.collectCycle(list);
251:
252: if (!jPackage.containsCycle()) {
253: return;
254: }
255:
256: JavaPackage cyclePackage = (JavaPackage) list
257: .get(list.size() - 1);
258: String cyclePackageName = cyclePackage.getName();
259:
260: int i = 0;
261: Iterator pkgIter = list.iterator();
262: while (pkgIter.hasNext()) {
263: i++;
264:
265: JavaPackage pkg = (JavaPackage) pkgIter.next();
266:
267: if (i == 1) {
268: printCycleHeader(pkg);
269: } else {
270: if (pkg.getName().equals(cyclePackageName)) {
271: printCycleTarget(pkg);
272: } else {
273: printCycleContributor(pkg);
274: }
275: }
276: }
277:
278: printCycleFooter();
279: }
280:
281: protected void printHeader() {
282: // do nothing
283: }
284:
285: protected void printFooter() {
286: // do nothing
287: }
288:
289: protected void printPackagesHeader() {
290: // do nothing
291: }
292:
293: protected void printPackagesFooter() {
294: // do nothing
295: }
296:
297: protected void printNoStats() {
298: getWriter()
299: .println(
300: "No stats available: package referenced, but not analyzed.");
301: }
302:
303: protected void printPackageHeader(JavaPackage jPackage) {
304: getWriter().println(
305: "\n--------------------------------------------------");
306: getWriter().println("- Package: " + jPackage.getName());
307: getWriter().println(
308: "--------------------------------------------------");
309: }
310:
311: protected void printPackageFooter(JavaPackage jPackage) {
312: // do nothing
313: }
314:
315: protected void printStatistics(JavaPackage jPackage) {
316: getWriter().println("\nStats:");
317: getWriter().println(
318: tab() + "Total Classes: " + jPackage.getClassCount());
319: getWriter().println(
320: tab() + "Concrete Classes: "
321: + jPackage.getConcreteClassCount());
322: getWriter().println(
323: tab() + "Abstract Classes: "
324: + jPackage.getAbstractClassCount());
325: getWriter().println("");
326: getWriter().println(
327: tab() + "Ca: " + jPackage.afferentCoupling());
328: getWriter().println(
329: tab() + "Ce: " + jPackage.efferentCoupling());
330: getWriter().println("");
331: getWriter().println(
332: tab() + "A: "
333: + toFormattedString(jPackage.abstractness()));
334: getWriter().println(
335: tab() + "I: "
336: + toFormattedString(jPackage.instability()));
337: getWriter().println(
338: tab() + "D: " + toFormattedString(jPackage.distance()));
339: }
340:
341: protected void printClassName(JavaClass jClass) {
342: getWriter().println(tab() + jClass.getName());
343: }
344:
345: protected void printPackageName(JavaPackage jPackage) {
346: getWriter().println(tab() + jPackage.getName());
347: }
348:
349: protected void printAbstractClassesHeader() {
350: getWriter().println("Abstract Classes:");
351: }
352:
353: protected void printAbstractClassesFooter() {
354: // do nothing
355: }
356:
357: protected void printConcreteClassesHeader() {
358: getWriter().println("Concrete Classes:");
359: }
360:
361: protected void printConcreteClassesFooter() {
362: // do nothing
363: }
364:
365: protected void printEfferentsHeader() {
366: getWriter().println("Depends Upon:");
367: }
368:
369: protected void printEfferentsFooter() {
370: // do nothing
371: }
372:
373: protected void printEfferentsError() {
374: getWriter().println(tab() + "Not dependent on any packages.");
375: }
376:
377: protected void printAfferentsHeader() {
378: getWriter().println("Used By:");
379: }
380:
381: protected void printAfferentsFooter() {
382: // do nothing
383: }
384:
385: protected void printAfferentsError() {
386: getWriter().println(tab() + "Not used by any packages.");
387: }
388:
389: protected void printCyclesHeader() {
390: printSectionBreak();
391: getWriter().println(
392: "\n--------------------------------------------------");
393: getWriter().println("- Package Dependency Cycles:");
394: getWriter().println(
395: "--------------------------------------------------\n");
396: }
397:
398: protected void printCyclesFooter() {
399: // do nothing
400: }
401:
402: protected void printCycleHeader(JavaPackage jPackage) {
403: getWriter().println(jPackage.getName());
404: getWriter().println(tab() + "|");
405: }
406:
407: protected void printCycleTarget(JavaPackage jPackage) {
408: getWriter().println(tab() + "|-> " + jPackage.getName());
409: }
410:
411: protected void printCycleContributor(JavaPackage jPackage) {
412: getWriter().println(tab() + "| " + jPackage.getName());
413: }
414:
415: protected void printCycleFooter() {
416: printSectionBreak();
417: }
418:
419: protected void printSummary(Collection packages) {
420: getWriter().println(
421: "\n--------------------------------------------------");
422: getWriter().println("- Summary:");
423: getWriter().println(
424: "--------------------------------------------------\n");
425:
426: getWriter()
427: .println(
428: "Name, Class Count, Abstract Class Count, Ca, Ce, A, I, D, V:\n");
429:
430: Iterator i = packages.iterator();
431: while (i.hasNext()) {
432: JavaPackage jPackage = (JavaPackage) i.next();
433: getWriter().print(jPackage.getName() + ",");
434: getWriter().print(jPackage.getClassCount() + ",");
435: getWriter().print(jPackage.getAbstractClassCount() + ",");
436: getWriter().print(jPackage.afferentCoupling() + ",");
437: getWriter().print(jPackage.efferentCoupling() + ",");
438: getWriter().print(
439: toFormattedString(jPackage.abstractness()) + ",");
440: getWriter().print(
441: toFormattedString(jPackage.instability()) + ",");
442: getWriter().print(
443: toFormattedString(jPackage.distance()) + ",");
444: getWriter().println(jPackage.getVolatility());
445: }
446: }
447:
448: protected void printSectionBreak() {
449: getWriter().println("");
450: }
451:
452: protected String toFormattedString(float f) {
453: return formatter.format(f);
454: }
455:
456: protected String tab() {
457: return " ";
458: }
459:
460: protected String tab(int n) {
461: StringBuffer s = new StringBuffer();
462: for (int i = 0; i < n; i++) {
463: s.append(tab());
464: }
465:
466: return s.toString();
467: }
468:
469: protected void usage(String message) {
470: if (message != null) {
471: System.err.println("\n" + message);
472: }
473: String baseUsage = "\nJDepend ";
474:
475: System.err.println("");
476: System.err.println("usage: ");
477: System.err.println(baseUsage + "[-components <components>]"
478: + " [-file <output file>] <directory> "
479: + "[directory2 [directory 3] ...]");
480: System.exit(1);
481: }
482:
483: protected void instanceMain(String[] args) {
484:
485: if (args.length < 1) {
486: usage("Must specify at least one directory.");
487: }
488:
489: int directoryCount = 0;
490:
491: for (int i = 0; i < args.length; i++) {
492: if (args[i].startsWith("-")) {
493: if (args[i].equalsIgnoreCase("-file")) {
494:
495: if (args.length <= i + 1) {
496: usage("Output file name not specified.");
497: }
498:
499: try {
500: setWriter(new PrintWriter(
501: new OutputStreamWriter(
502: new FileOutputStream(args[++i]),
503: "UTF8")));
504: } catch (IOException ioe) {
505: usage(ioe.getMessage());
506: }
507:
508: } else if (args[i].equalsIgnoreCase("-components")) {
509: if (args.length <= i + 1) {
510: usage("Components not specified.");
511: }
512: setComponents(args[++i]);
513: } else {
514: usage("Invalid argument: " + args[i]);
515: }
516: } else {
517: try {
518: addDirectory(args[i]);
519: directoryCount++;
520: } catch (IOException ioe) {
521: usage("Directory does not exist: " + args[i]);
522: }
523: }
524: }
525:
526: if (directoryCount == 0) {
527: usage("Must specify at least one directory.");
528: }
529:
530: analyze();
531: }
532:
533: public static void main(String args[]) {
534: new JDepend().instanceMain(args);
535: }
536: }
|