001: /*
002: * Cobertura - http://cobertura.sourceforge.net/
003: *
004: * Copyright (C) 2003 jcoverage ltd.
005: * Copyright (C) 2005 Mark Doliner
006: * Copyright (C) 2006 Jiri Mares
007: *
008: * Cobertura is free software; you can redistribute it and/or modify
009: * it under the terms of the GNU General Public License as published
010: * by the Free Software Foundation; either version 2 of the License,
011: * or (at your option) any later version.
012: *
013: * Cobertura is distributed in the hope that it will be useful, but
014: * WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with Cobertura; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
021: * USA
022: */
023:
024: package net.sourceforge.cobertura.coveragedata;
025:
026: import java.util.Collection;
027: import java.util.Collections;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.Map;
032: import java.util.Set;
033: import java.util.SortedSet;
034: import java.util.TreeSet;
035:
036: /**
037: * <p>
038: * ProjectData information is typically serialized to a file. An
039: * instance of this class records coverage information for a single
040: * class that has been instrumented.
041: * </p>
042: *
043: * <p>
044: * This class implements HasBeenInstrumented so that when cobertura
045: * instruments itself, it will omit this class. It does this to
046: * avoid an infinite recursion problem because instrumented classes
047: * make use of this class.
048: * </p>
049: */
050:
051: public class ClassData extends CoverageDataContainer implements
052: Comparable, HasBeenInstrumented {
053:
054: private static final long serialVersionUID = 5;
055:
056: /**
057: * Each key is a line number in this class, stored as an Integer object.
058: * Each value is information about the line, stored as a LineData object.
059: */
060: private Map branches = new HashMap();
061:
062: private boolean containsInstrumentationInfo = false;
063:
064: private Set methodNamesAndDescriptors = new HashSet();
065:
066: private String name = null;
067:
068: private String sourceFileName = null;
069:
070: /**
071: * @param name In the format "net.sourceforge.cobertura.coveragedata.ClassData"
072: */
073: public ClassData(String name) {
074: if (name == null)
075: throw new IllegalArgumentException(
076: "Class name must be specified.");
077: this .name = name;
078: }
079:
080: public LineData addLine(int lineNumber, String methodName,
081: String methodDescriptor) {
082: LineData lineData = getLineData(lineNumber);
083: if (lineData == null) {
084: lineData = new LineData(lineNumber);
085: // Each key is a line number in this class, stored as an Integer object.
086: // Each value is information about the line, stored as a LineData object.
087: children.put(new Integer(lineNumber), lineData);
088: }
089: lineData.setMethodNameAndDescriptor(methodName,
090: methodDescriptor);
091:
092: // methodName and methodDescriptor can be null when cobertura.ser with
093: // no line information was loaded (or was not loaded at all).
094: if (methodName != null && methodDescriptor != null)
095: methodNamesAndDescriptors
096: .add(methodName + methodDescriptor);
097: return lineData;
098: }
099:
100: /**
101: * This is required because we implement Comparable.
102: */
103: public int compareTo(Object o) {
104: if (!o.getClass().equals(ClassData.class))
105: return Integer.MAX_VALUE;
106: return this .name.compareTo(((ClassData) o).name);
107: }
108:
109: public boolean containsInstrumentationInfo() {
110: return this .containsInstrumentationInfo;
111: }
112:
113: /**
114: * Returns true if the given object is an instance of the
115: * ClassData class, and it contains the same data as this
116: * class.
117: */
118: public boolean equals(Object obj) {
119: if (this == obj)
120: return true;
121: if ((obj == null) || !(obj.getClass().equals(this .getClass())))
122: return false;
123:
124: ClassData classData = (ClassData) obj;
125: return super .equals(obj)
126: && this .branches.equals(classData.branches)
127: && this .methodNamesAndDescriptors
128: .equals(classData.methodNamesAndDescriptors)
129: && this .name.equals(classData.name)
130: && this .sourceFileName.equals(classData.sourceFileName);
131: }
132:
133: public String getBaseName() {
134: int lastDot = this .name.lastIndexOf('.');
135: if (lastDot == -1) {
136: return this .name;
137: }
138: return this .name.substring(lastDot + 1);
139: }
140:
141: /**
142: * @return The branch coverage rate for a particular method.
143: */
144: public double getBranchCoverageRate(String methodNameAndDescriptor) {
145: int total = 0;
146: int covered = 0;
147:
148: for (Iterator iter = branches.values().iterator(); iter
149: .hasNext();) {
150: LineData next = (LineData) iter.next();
151: if (methodNameAndDescriptor.equals(next.getMethodName()
152: + next.getMethodDescriptor())) {
153: total += next.getNumberOfValidBranches();
154: covered += next.getNumberOfCoveredBranches();
155: }
156: }
157: if (total == 0)
158: return 1.0;
159: return (double) covered / total;
160: }
161:
162: public Collection getBranches() {
163: return Collections.unmodifiableCollection(branches.keySet());
164: }
165:
166: /**
167: * @param lineNumber The source code line number.
168: * @return The coverage of the line
169: */
170: public LineData getLineCoverage(int lineNumber) {
171: Integer lineObject = new Integer(lineNumber);
172: if (!children.containsKey(lineObject)) {
173: return null;
174: }
175:
176: return (LineData) children.get(lineObject);
177: }
178:
179: /**
180: * @return The line coverage rate for particular method
181: */
182: public double getLineCoverageRate(String methodNameAndDescriptor) {
183: int total = 0;
184: int hits = 0;
185:
186: Iterator iter = children.values().iterator();
187: while (iter.hasNext()) {
188: LineData next = (LineData) iter.next();
189: if (methodNameAndDescriptor.equals(next.getMethodName()
190: + next.getMethodDescriptor())) {
191: total++;
192: if (next.getHits() > 0) {
193: hits++;
194: }
195: }
196: }
197: if (total == 0)
198: return 1d;
199: return (double) hits / total;
200: }
201:
202: private LineData getLineData(int lineNumber) {
203: return (LineData) children.get(new Integer(lineNumber));
204: }
205:
206: public SortedSet getLines() {
207: return new TreeSet(this .children.values());
208: }
209:
210: public Collection getLines(String methodNameAndDescriptor) {
211: Collection lines = new HashSet();
212: Iterator iter = children.values().iterator();
213: while (iter.hasNext()) {
214: LineData next = (LineData) iter.next();
215: if (methodNameAndDescriptor.equals(next.getMethodName()
216: + next.getMethodDescriptor())) {
217: lines.add(next);
218: }
219: }
220: return lines;
221: }
222:
223: /**
224: * @return The method name and descriptor of each method found in the
225: * class represented by this instrumentation.
226: */
227: public Set getMethodNamesAndDescriptors() {
228: return methodNamesAndDescriptors;
229: }
230:
231: public String getName() {
232: return name;
233: }
234:
235: /**
236: * @return The number of branches in this class.
237: */
238: public int getNumberOfValidBranches() {
239: int number = 0;
240: for (Iterator i = branches.values().iterator(); i.hasNext(); number += ((LineData) i
241: .next()).getNumberOfValidBranches())
242: ;
243: return number;
244: }
245:
246: /**
247: * @see net.sourceforge.cobertura.coveragedata.CoverageData#getNumberOfCoveredBranches()
248: */
249: public int getNumberOfCoveredBranches() {
250: int number = 0;
251: for (Iterator i = branches.values().iterator(); i.hasNext(); number += ((LineData) i
252: .next()).getNumberOfCoveredBranches())
253: ;
254: return number;
255: }
256:
257: public String getPackageName() {
258: int lastDot = this .name.lastIndexOf('.');
259: if (lastDot == -1) {
260: return "";
261: }
262: return this .name.substring(0, lastDot);
263: }
264:
265: /**
266: * Return the name of the file containing this class. If this
267: * class' sourceFileName has not been set (for whatever reason)
268: * then this method will attempt to infer the name of the source
269: * file using the class name.
270: *
271: * @return The name of the source file, for example
272: * net/sourceforge/cobertura/coveragedata/ClassData.java
273: */
274: public String getSourceFileName() {
275: String baseName;
276: if (sourceFileName != null)
277: baseName = sourceFileName;
278: else {
279: baseName = getBaseName();
280: int firstDollarSign = baseName.indexOf('$');
281: if (firstDollarSign == -1 || firstDollarSign == 0)
282: baseName += ".java";
283: else
284: baseName = baseName.substring(0, firstDollarSign)
285: + ".java";
286: }
287:
288: String packageName = getPackageName();
289: if (packageName.equals(""))
290: return baseName;
291: return packageName.replace('.', '/') + '/' + baseName;
292: }
293:
294: public int hashCode() {
295: return this .name.hashCode();
296: }
297:
298: /**
299: * @return True if the line contains at least one condition jump (branch)
300: */
301: public boolean hasBranch(int lineNumber) {
302: return branches.containsKey(new Integer(lineNumber));
303: }
304:
305: /**
306: * Determine if a given line number is a valid line of code.
307: *
308: * @return True if the line contains executable code. False
309: * if the line is empty, or a comment, etc.
310: */
311: public boolean isValidSourceLineNumber(int lineNumber) {
312: return children.containsKey(new Integer(lineNumber));
313: }
314:
315: public void addLineJump(int lineNumber, int branchNumber) {
316: LineData lineData = getLineData(lineNumber);
317: if (lineData != null) {
318: lineData.addJump(branchNumber);
319: this .branches.put(new Integer(lineNumber), lineData);
320: }
321: }
322:
323: public void addLineSwitch(int lineNumber, int switchNumber,
324: int[] keys) {
325: LineData lineData = getLineData(lineNumber);
326: if (lineData != null) {
327: lineData.addSwitch(switchNumber, keys);
328: this .branches.put(new Integer(lineNumber), lineData);
329: }
330: }
331:
332: public void addLineSwitch(int lineNumber, int switchNumber,
333: int min, int max) {
334: LineData lineData = getLineData(lineNumber);
335: if (lineData != null) {
336: lineData.addSwitch(switchNumber, min, max);
337: this .branches.put(new Integer(lineNumber), lineData);
338: }
339: }
340:
341: /**
342: * Merge some existing instrumentation with this instrumentation.
343: *
344: * @param coverageData Some existing coverage data.
345: */
346: public void merge(CoverageData coverageData) {
347: ClassData classData = (ClassData) coverageData;
348:
349: // If objects contain data for different classes then don't merge
350: if (!this .getName().equals(classData.getName()))
351: return;
352:
353: super .merge(coverageData);
354:
355: // We can't just call this.branches.putAll(classData.branches);
356: // Why not? If we did a putAll, then the LineData objects from
357: // the coverageData class would overwrite the LineData objects
358: // that are already in "this.branches" And we don't need to
359: // update the LineData objects that are already in this.branches
360: // because they are shared between this.branches and this.children,
361: // so the object hit counts will be moved when we called
362: // super.merge() above.
363: for (Iterator iter = classData.branches.keySet().iterator(); iter
364: .hasNext();) {
365: Object key = iter.next();
366: if (!this .branches.containsKey(key)) {
367: this .branches.put(key, classData.branches.get(key));
368: }
369: }
370:
371: this .containsInstrumentationInfo |= classData.containsInstrumentationInfo;
372: this .methodNamesAndDescriptors.addAll(classData
373: .getMethodNamesAndDescriptors());
374: if (classData.sourceFileName != null)
375: this .sourceFileName = classData.sourceFileName;
376: }
377:
378: public void removeLine(int lineNumber) {
379: Integer lineObject = new Integer(lineNumber);
380: children.remove(lineObject);
381: branches.remove(lineObject);
382: }
383:
384: public void setContainsInstrumentationInfo() {
385: this .containsInstrumentationInfo = true;
386: }
387:
388: public void setSourceFileName(String sourceFileName) {
389: this .sourceFileName = sourceFileName;
390: }
391:
392: /**
393: * Increment the number of hits for a particular line of code.
394: *
395: * @param lineNumber the line of code to increment the number of hits.
396: */
397: public void touch(int lineNumber) {
398: LineData lineData = getLineData(lineNumber);
399: if (lineData == null)
400: lineData = addLine(lineNumber, null, null);
401: lineData.touch();
402: }
403:
404: /**
405: * Increments the number of hits for particular hit counter of particular branch on particular line number.
406: *
407: * @param lineNumber The line of code where the branch is
408: * @param branchNumber The branch on the line to change the hit counter
409: * @param branch The hit counter (true or false)
410: */
411: public void touchJump(int lineNumber, int branchNumber,
412: boolean branch) {
413: LineData lineData = getLineData(lineNumber);
414: if (lineData == null)
415: lineData = addLine(lineNumber, null, null);
416: lineData.touchJump(branchNumber, branch);
417: }
418:
419: /**
420: * Increments the number of hits for particular hit counter of particular switch branch on particular line number.
421: *
422: * @param lineNumber The line of code where the branch is
423: * @param switchNumber The switch on the line to change the hit counter
424: * @param branch The hit counter
425: */
426: public void touchSwitch(int lineNumber, int switchNumber, int branch) {
427: LineData lineData = getLineData(lineNumber);
428: if (lineData == null)
429: lineData = addLine(lineNumber, null, null);
430: lineData.touchSwitch(switchNumber, branch);
431: }
432:
433: }
|