001: /*
002: * @(#)ClassfileCheckerCLI.java
003: *
004: * Copyright (C) 2004 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.codecoverage.v2.util;
028:
029: import java.io.BufferedReader;
030: import java.io.File;
031: import java.io.FileReader;
032: import java.io.IOException;
033: import java.io.PrintStream;
034: import java.util.ArrayList;
035: import java.util.HashMap;
036: import java.util.HashSet;
037: import java.util.Iterator;
038: import java.util.List;
039: import java.util.Map;
040: import java.util.Set;
041:
042: import net.sourceforge.groboutils.codecoverage.v2.IAnalysisModule;
043: import net.sourceforge.groboutils.codecoverage.v2.datastore.AnalysisModuleSet;
044: import net.sourceforge.groboutils.codecoverage.v2.datastore.ClassRecord;
045: import net.sourceforge.groboutils.codecoverage.v2.datastore.DirMetaDataReader;
046: import net.sourceforge.groboutils.codecoverage.v2.datastore.IClassMetaDataReader;
047: import net.sourceforge.groboutils.codecoverage.v2.datastore.IMetaDataReader;
048: import net.sourceforge.groboutils.codecoverage.v2.datastore.MarkRecord;
049:
050: import org.apache.bcel.classfile.ClassParser;
051: import org.apache.bcel.classfile.CodeException;
052: import org.apache.bcel.classfile.ConstantPool;
053: import org.apache.bcel.classfile.JavaClass;
054: import org.apache.bcel.classfile.LineNumberTable;
055: import org.apache.bcel.classfile.Method;
056: import org.apache.bcel.generic.InstructionHandle;
057: import org.apache.bcel.generic.InstructionList;
058:
059: /**
060: * This file joins the class detail files, original class files, and covered
061: * class files with the source, into a STDOUT report.
062: *
063: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
064: * @version $Date: 2004/04/15 05:48:26 $
065: * @since January 15, 2004
066: */
067: public class ClassfileCheckerCLI {
068: public static void main(String args[]) throws IOException {
069: if (args.length <= 1) {
070: System.out.println("ClassfileCheckerCLI:");
071: System.out
072: .println(" Compares original vs. covered class files");
073: System.out.println("");
074: System.out.println(" Usage:");
075: System.out
076: .println(" Report the lines in source code that are marked:");
077: System.out.println(" <jdk stuff> "
078: + ClassfileCheckerCLI.class.getName()
079: + " -m classname datadir srcdir");
080: System.out
081: .println(" Report the differences between the original and covered class files:");
082: System.out.println(" <jdk stuff> "
083: + ClassfileCheckerCLI.class.getName()
084: + " -c classname origclassdir covclassdir");
085: System.out.println(" Both reports:");
086: System.out
087: .println(" <jdk stuff> "
088: + ClassfileCheckerCLI.class.getName()
089: + " -mc classname datadir srcdir origclassdir covclassdir");
090: }
091:
092: // note: this needs to be improved
093: String type = args[0].toLowerCase();
094: String cname = args[1];
095: String datadir = null;
096: String srcdir = null;
097: String origclassdir = null;
098: String covclassdir = null;
099: boolean doMarkReport = false;
100: boolean doClassReport = false;
101:
102: int pos = 2;
103: if (type.indexOf('m') >= 0) {
104: doMarkReport = true;
105: datadir = args[pos++];
106: srcdir = args[pos++];
107: }
108: if (type.indexOf('c') >= 0) {
109: doClassReport = true;
110: origclassdir = args[pos++];
111: covclassdir = args[pos++];
112: }
113:
114: ClassfileCheckerCLI ccc = new ClassfileCheckerCLI();
115:
116: if (doMarkReport) {
117: ccc.reportMarkedSource(cname, new File(datadir), new File(
118: srcdir), System.out);
119: }
120:
121: if (doClassReport) {
122: ccc.reportClassDiff(cname, new File(origclassdir),
123: new File(covclassdir), System.out);
124: }
125: }
126:
127: public void reportMarkedSource(String classname,
128: File coveredDataDir, File srcDir, PrintStream out)
129: throws IOException {
130: IMetaDataReader mdr = createMetaDataReader(coveredDataDir);
131: File srcFile = getSourceFileForClass(classname, srcDir);
132: reportMarkedSource(classname, mdr, srcFile, out);
133: }
134:
135: /**
136: * Comment the source code for the given class with the line number and
137: * which analysis module marked each line.
138: */
139: public void reportMarkedSource(String classname,
140: IMetaDataReader mdr, File srcFile, PrintStream out)
141: throws IOException {
142: out.println("Marks on source for class " + classname);
143: out.println("");
144:
145: MarkRecord mrL[] = getMarkRecords(classname, mdr);
146: Map amIndex = createAnalysisModuleIndexMap(mdr);
147: int maxAM = printLegend(amIndex, out);
148:
149: FileReader fr = new FileReader(srcFile);
150: try {
151: BufferedReader br = new BufferedReader(fr);
152: int lineNo = 0;
153: String line = br.readLine();
154: while (line != null) {
155: ++lineNo;
156:
157: out.print(' ' + format8(lineNo));
158: out.print(" | ");
159:
160: // find all the AM measure names that marked this line
161: String amnL[] = markedLine(mrL, lineNo);
162: for (int i = 1; i <= maxAM; ++i) {
163: boolean notfound = true;
164: Integer idx = new Integer(i);
165: for (int j = 0; j < amnL.length; ++j) {
166: if (idx.equals(amIndex.get(amnL[j]))) {
167: out.print(indexCode(i));
168: notfound = false;
169: break;
170: }
171: }
172: if (notfound) {
173: out.print(" ");
174: }
175: }
176: out.println(" | " + line);
177:
178: line = br.readLine();
179: }
180: } finally {
181: fr.close();
182: }
183: }
184:
185: /**
186: * Perform a diff on the covered and original class files for a
187: * specific class. This only does a diff on the code, not the
188: * fields.
189: */
190: public void reportClassDiff(String classname,
191: File originalClassDir, File coveredClassDir, PrintStream out)
192: throws IOException {
193: File origClassF = getClassFileForClass(classname,
194: originalClassDir);
195: File covClassF = getClassFileForClass(classname,
196: coveredClassDir);
197:
198: JavaClass origClass = createJavaClass(origClassF);
199: JavaClass covClass = createJavaClass(covClassF);
200:
201: Method omL[] = origClass.getMethods();
202: Method cmL[] = covClass.getMethods();
203:
204: out
205: .println("Comparing covered and original classes for class named "
206: + classname);
207: out
208: .println("(Original is on the left side, covered is on the right)");
209:
210: // compare each method
211: Set covNotFound = new HashSet();
212: for (int i = 0; i < cmL.length; ++i) {
213: covNotFound.add(cmL[i]);
214: }
215: for (int i = 0; i < omL.length; ++i) {
216: String omName = omL[i].getName();
217: String omSig = omL[i].getSignature();
218: boolean notFound = true;
219: for (int j = 0; notFound && j < cmL.length; ++j) {
220: if (omName.equals(cmL[j].getName())
221: && omSig.equals(cmL[j].getSignature())) {
222: notFound = false;
223: covNotFound.remove(cmL[j]);
224: out.println("");
225: reportMethodDiff(omL[i], cmL[j], origClass
226: .getConstantPool(), covClass
227: .getConstantPool(), out);
228: }
229: }
230: if (notFound) {
231: out.println("");
232: reportRemovedMethod(omL[i], out);
233: }
234: }
235: Iterator iter = covNotFound.iterator();
236: while (iter.hasNext()) {
237: out.println("");
238: reportAddedMethod((Method) iter.next(), out);
239: }
240: }
241:
242: public void reportMethodDiff(Method orig, Method cov,
243: ConstantPool origCP, ConstantPool covCP, PrintStream out) {
244: out.println(" = Method " + orig.getName() + " "
245: + orig.getSignature());
246: LineNumberTable ot = orig.getLineNumberTable();
247: LineNumberTable ct = cov.getLineNumberTable();
248: CodeException oce[] = orig.getCode().getExceptionTable();
249: CodeException cce[] = cov.getCode().getExceptionTable();
250:
251: // assume that bytecode instructions were only added, not removed
252: InstructionHandle oList[] = (new InstructionList(orig.getCode()
253: .getCode())).getInstructionHandles();
254: InstructionHandle cList[] = (new InstructionList(cov.getCode()
255: .getCode())).getInstructionHandles();
256: int oNextI = 0;
257: int cNextI = 0;
258: int lastOLine = -2;
259: int lastCLine = -2;
260: while (oNextI < oList.length && cNextI < cList.length) {
261: InstructionHandle oNext = null;
262: InstructionHandle cNext = null;
263: if (oNextI < oList.length) {
264: oNext = oList[oNextI];
265: }
266: if (cNextI < cList.length) {
267: cNext = cList[cNextI];
268: }
269: if (oNext == null
270: || !oNext.getInstruction().equals(
271: cNext.getInstruction())) {
272: lastOLine = -2;
273: int lineNo = ct.getSourceLine(cNext.getPosition());
274: if (lastCLine != lineNo) {
275: out.println(" + Line: Orig: N/A Covered: "
276: + format8(lineNo));
277: }
278: printCodeException("Covered", cce, cNext.getPosition(),
279: covCP, out);
280: printSideBySide(null, cNext.getInstruction().toString(
281: true), 36, " | ", " ", out);
282: lastCLine = lineNo;
283: ++cNextI;
284: } else if (cNext == null) {
285: lastCLine = -2;
286: int lineNo = ot.getSourceLine(oNext.getPosition());
287: if (lastOLine != lineNo) {
288: out.println(" - Line: Orig: " + format8(lineNo)
289: + " Covered: N/A");
290: }
291: printCodeException("Original", oce,
292: oNext.getPosition(), origCP, out);
293: printSideBySide(oNext.getInstruction().toString(true),
294: null, 36, " | ", " ", out);
295: lastOLine = lineNo;
296: ++oNextI;
297: } else {
298: int olineNo = ot.getSourceLine(oNext.getPosition());
299: int clineNo = ct.getSourceLine(cNext.getPosition());
300: if (lastOLine != olineNo && lastCLine != clineNo) {
301: out.println(" = Line: Orig: " + format8(olineNo)
302: + " Covered: " + format8(clineNo));
303: }
304: printCodeException("Original", oce,
305: oNext.getPosition(), origCP, out);
306: printCodeException("Covered", cce, cNext.getPosition(),
307: covCP, out);
308: printSideBySide(oNext.getInstruction().toString(true),
309: cNext.getInstruction().toString(true), 36,
310: " | ", " ", out);
311: lastOLine = olineNo;
312: lastCLine = clineNo;
313: ++oNextI;
314: ++cNextI;
315: }
316: }
317: }
318:
319: public void reportRemovedMethod(Method orig, PrintStream out) {
320: out.println(" < Method Removed in covered version: "
321: + orig.getName() + " " + orig.getSignature());
322: LineNumberTable ot = orig.getLineNumberTable();
323:
324: InstructionHandle oNext = (new InstructionList(orig.getCode()
325: .getCode())).getStart();
326: while (oNext != null) {
327: out.println(" - Line: Orig: "
328: + format8(ot.getSourceLine(oNext.getPosition()))
329: + " Covered: N/A");
330: printSideBySide(oNext.getInstruction().toString(true),
331: null, 36, " | ", " ", out);
332: oNext = oNext.getNext();
333: }
334: }
335:
336: public void reportAddedMethod(Method cov, PrintStream out) {
337: out.println(" > Method Added in covered version: "
338: + cov.getName() + " " + cov.getSignature());
339: LineNumberTable ct = cov.getLineNumberTable();
340:
341: InstructionHandle cNext = (new InstructionList(cov.getCode()
342: .getCode())).getStart();
343: while (cNext != null) {
344: out.println(" + Line: Orig: N/A Covered: "
345: + format8(ct.getSourceLine(cNext.getPosition())));
346: printSideBySide(null,
347: cNext.getInstruction().toString(true), 36, " | ",
348: " ", out);
349: cNext = cNext.getNext();
350: }
351: }
352:
353: /**
354: * Returns the measure names of the analysis modules that marked this
355: * particular line.
356: */
357: public String[] markedLine(MarkRecord mrL[], int lineNo) {
358: Set ret = new HashSet();
359: for (int i = 0; i < mrL.length; ++i) {
360: if (mrL[i].getLineNumber() == lineNo) {
361: ret.add(mrL[i].getAnalysisModule());
362: }
363: }
364:
365: return (String[]) ret.toArray(new String[ret.size()]);
366: }
367:
368: /**
369: * Returns all class files related to the given classname. That is,
370: * the class and its inner classes.
371: */
372: public File[] getClassFilesForClass(String classname, File classdir) {
373: String pkgname = getPackageName(classname).replace('.',
374: File.separatorChar);
375: String cname = getJustClassName(classname);
376: List ret = new ArrayList();
377: if (classdir != null) {
378: String match1 = cname + ".class";
379: String match2 = cname + '$';
380: File pkgdir = new File(classdir, pkgname);
381: if (pkgdir.exists() && pkgdir.isDirectory()) {
382: String files[] = pkgdir.list();
383: for (int i = 0; i < files.length; ++i) {
384: if (files[i].equals(match1)
385: || (files[i].startsWith(match2) && files[i]
386: .endsWith(".class"))) {
387: ret.add(new File(pkgdir, files[i]));
388: }
389: }
390: }
391: }
392: return (File[]) ret.toArray(new File[ret.size()]);
393: }
394:
395: /**
396: * Returns only the class file for the given class name.
397: */
398: public File getClassFileForClass(String classname, File classdir)
399: throws IOException {
400: String fname = classname.replace('.', File.separatorChar)
401: + ".class";
402: File cf = new File(classdir, fname);
403: if (!cf.exists() || !cf.isFile()) {
404: throw new java.io.FileNotFoundException(
405: "No such class file '" + fname + "' in directory '"
406: + classdir + "'");
407: }
408: return cf;
409: }
410:
411: /**
412: * Returns only the class file for the given class name.
413: */
414: public File getSourceFileForClass(String classname, File srcdir)
415: throws IOException {
416: int pos = classname.indexOf('$');
417: if (pos > 0) {
418: classname = classname.substring(0, pos);
419: }
420: String fname = classname.replace('.', File.separatorChar)
421: + ".java";
422: File cf = new File(srcdir, fname);
423: //System.err.println("Checking file ["+cf+"]: exists="+
424: // cf.exists()+", isfile="+cf.isFile());
425: if (!cf.exists() || !cf.isFile()) {
426: throw new java.io.FileNotFoundException(
427: "No such source file '" + fname
428: + "' in directory '" + srcdir);
429: }
430: return cf;
431: }
432:
433: public MarkRecord[] getMarkRecords(String classname,
434: IMetaDataReader mdr) throws IOException {
435: AnalysisModuleSet ams = mdr.getAnalysisModuleSet();
436: IAnalysisModule amL[] = ams.getAnalysisModules();
437: List ret = new ArrayList();
438: for (int i = 0; i < amL.length; ++i) {
439: IClassMetaDataReader cmdr = mdr.getClassReader(amL[i]);
440: String[] sigs = getMachingClassSignatures(classname, cmdr
441: .getClassSignatures());
442: for (int j = 0; j < sigs.length; ++j) {
443: ClassRecord cr = cmdr.readClass(sigs[j]);
444: if (cr != null) {
445: MarkRecord[] mrL = cr
446: .getMarksForAnalysisModule(amL[i]);
447: for (int k = 0; k < mrL.length; ++k) {
448: ret.add(mrL[k]);
449: }
450: }
451: }
452: }
453:
454: return (MarkRecord[]) ret.toArray(new MarkRecord[ret.size()]);
455: }
456:
457: protected JavaClass createJavaClass(File filename)
458: throws IOException {
459: ClassParser cp = new ClassParser(filename.getAbsolutePath());
460: return cp.parse();
461: }
462:
463: protected String[] getMachingClassSignatures(String classname,
464: String sigs[]) {
465: List ret = new ArrayList();
466: String match1 = classname + '-';
467: String match2 = classname + '$';
468: if (sigs != null) {
469: for (int i = 0; i < sigs.length; ++i) {
470: if (sigs[i].startsWith(match1)
471: || sigs[i].startsWith(match2)) {
472: ret.add(sigs[i]);
473: }
474: }
475: }
476: return (String[]) ret.toArray(new String[ret.size()]);
477: }
478:
479: protected String getPackageName(String classname) {
480: String pkgname = "";
481: int i = classname.lastIndexOf('.');
482: if (i >= 0) {
483: pkgname = classname.substring(0, i - 1);
484: }
485: return pkgname;
486: }
487:
488: protected String getJustClassName(String classname) {
489: String cname = classname;
490: int i = classname.lastIndexOf('.');
491: if (i >= 0) {
492: cname = classname.substring(i + 1);
493: }
494: return cname;
495: }
496:
497: protected IMetaDataReader createMetaDataReader(File datadir)
498: throws IOException {
499: return new DirMetaDataReader(datadir);
500: }
501:
502: protected Map createAnalysisModuleIndexMap(IMetaDataReader mdr)
503: throws IOException {
504: HashMap map = new HashMap();
505: int count = 0;
506: AnalysisModuleSet ams = mdr.getAnalysisModuleSet();
507: IAnalysisModule amL[] = ams.getAnalysisModules();
508: for (int j = 0; j < amL.length; ++j) {
509: if (!map.containsKey(amL[j].getMeasureName())) {
510: map.put(amL[j].getMeasureName(), new Integer(++count));
511: }
512: }
513: return map;
514: }
515:
516: protected int printLegend(Map amToIndex, PrintStream out) {
517: out.println("Legend of Analysis Modules:");
518: int index = 0;
519: Set entries = amToIndex.entrySet();
520: boolean found = true;
521: while (found) {
522: Integer x = new Integer(++index);
523: found = false;
524: Iterator iter = entries.iterator();
525: while (iter.hasNext()) {
526: Map.Entry e = (Map.Entry) iter.next();
527: if (e.getValue().equals(x)) {
528: found = true;
529: out.println(" " + indexCode(index) + " = "
530: + (String) e.getKey());
531: }
532: }
533: }
534:
535: out.println("");
536: out.print(" lineno | ");
537: for (int i = 1; i < index; ++i) {
538: out.print(indexCode(i));
539: }
540: out.println(" | source code");
541: for (int i = 0; i < 78; ++i) {
542: out.print("-");
543: }
544: out.println("-");
545:
546: return index - 1;
547: }
548:
549: protected String indexCode(int i) {
550: return "" + (char) ('A' + i - 1);
551: }
552:
553: protected String format8(int i) {
554: StringBuffer sb = new StringBuffer();
555: for (int top = 1000000; top > 1 && i < top; top /= 10) {
556: sb.append(' ');
557: }
558: sb.append(i);
559: return sb.toString();
560: }
561:
562: protected String[] formatWidth(String t, int width) {
563: List ret = new ArrayList();
564: while (t.length() > width) {
565: ret.add(t.substring(0, width));
566: t = t.substring(width);
567: }
568: StringBuffer sb = new StringBuffer(t);
569: while (sb.length() < width) {
570: sb.append(" ");
571: }
572: ret.add(sb.toString());
573: return (String[]) ret.toArray(new String[ret.size()]);
574: }
575:
576: protected void printCodeException(String type, CodeException[] ce,
577: int pos, ConstantPool cp, PrintStream out) {
578: for (int i = 0; i < ce.length; ++i) {
579: if (pos == ce[i].getStartPC()) {
580: out.println(" { " + type
581: + ": start try block for exception "
582: + getClassName(cp, ce[i].getCatchType()));
583: }
584: if (pos == ce[i].getEndPC()) {
585: out.println(" } " + type
586: + ": end try block for exception "
587: + getClassName(cp, ce[i].getCatchType()));
588: }
589: if (pos == ce[i].getHandlerPC()) {
590: out.println(" > " + type
591: + ": exception handler block for "
592: + getClassName(cp, ce[i].getCatchType()));
593: }
594: }
595: }
596:
597: protected void printSideBySide(String left, String right,
598: int width, String sep, String indent, PrintStream out) {
599: if (left == null) {
600: left = "";
601: }
602: if (right == null) {
603: right = "";
604: }
605: String blank = formatWidth("", width)[0];
606: String fl[] = formatWidth(left, width);
607: String rl[] = formatWidth(right, width);
608: int len = (fl.length > rl.length) ? fl.length : rl.length;
609: for (int i = 0; i < len; ++i) {
610: out.print(indent);
611: if (i >= fl.length) {
612: out.print(blank);
613: } else {
614: out.print(fl[i]);
615: }
616: out.print(sep);
617: if (i >= rl.length) {
618: out.println(blank);
619: } else {
620: out.println(rl[i]);
621: }
622: }
623: }
624:
625: protected String getClassName(ConstantPool cp, int typeIndex) {
626: String name;
627: try {
628: name = org.apache.bcel.classfile.Utility.compactClassName(
629: cp.getConstantString(typeIndex,
630: org.apache.bcel.Constants.CONSTANT_Class),
631: false);
632: } catch (org.apache.bcel.classfile.ClassFormatException ex) {
633: // ignore
634: name = "<unknown: " + typeIndex + ">";
635: }
636: return name;
637: }
638: }
|