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
052 implements Comparable, HasBeenInstrumented
053 {
054
055 private static final long serialVersionUID = 5;
056
057 /**
058 * Each key is a line number in this class, stored as an Integer object.
059 * Each value is information about the line, stored as a LineData object.
060 */
061 private Map branches = new HashMap();
062
063 private boolean containsInstrumentationInfo = false;
064
065 private Set methodNamesAndDescriptors = new HashSet();
066
067 private String name = null;
068
069 private String sourceFileName = null;
070
071 /**
072 * @param name In the format "net.sourceforge.cobertura.coveragedata.ClassData"
073 */
074 public ClassData(String name)
075 {
076 if (name == null)
077 throw new IllegalArgumentException(
078 "Class name must be specified.");
079 this.name = name;
080 }
081
082 public LineData addLine(int lineNumber, String methodName,
083 String methodDescriptor)
084 {
085 LineData lineData = getLineData(lineNumber);
086 if (lineData == null)
087 {
088 lineData = new LineData(lineNumber);
089 // Each key is a line number in this class, stored as an Integer object.
090 // Each value is information about the line, stored as a LineData object.
091 children.put(new Integer(lineNumber), lineData);
092 }
093 lineData.setMethodNameAndDescriptor(methodName, methodDescriptor);
094
095 // methodName and methodDescriptor can be null when cobertura.ser with
096 // no line information was loaded (or was not loaded at all).
097 if( methodName!=null && methodDescriptor!=null)
098 methodNamesAndDescriptors.add(methodName + methodDescriptor);
099 return lineData;
100 }
101
102 /**
103 * This is required because we implement Comparable.
104 */
105 public int compareTo(Object o)
106 {
107 if (!o.getClass().equals(ClassData.class))
108 return Integer.MAX_VALUE;
109 return this.name.compareTo(((ClassData)o).name);
110 }
111
112 public boolean containsInstrumentationInfo()
113 {
114 return this.containsInstrumentationInfo;
115 }
116
117 /**
118 * Returns true if the given object is an instance of the
119 * ClassData class, and it contains the same data as this
120 * class.
121 */
122 public boolean equals(Object obj)
123 {
124 if (this == obj)
125 return true;
126 if ((obj == null) || !(obj.getClass().equals(this.getClass())))
127 return false;
128
129 ClassData classData = (ClassData)obj;
130 return super.equals(obj)
131 && this.branches.equals(classData.branches)
132 && this.methodNamesAndDescriptors
133 .equals(classData.methodNamesAndDescriptors)
134 && this.name.equals(classData.name)
135 && this.sourceFileName.equals(classData.sourceFileName);
136 }
137
138 public String getBaseName()
139 {
140 int lastDot = this.name.lastIndexOf('.');
141 if (lastDot == -1)
142 {
143 return this.name;
144 }
145 return this.name.substring(lastDot + 1);
146 }
147
148 /**
149 * @return The branch coverage rate for a particular method.
150 */
151 public double getBranchCoverageRate(String methodNameAndDescriptor)
152 {
153 int total = 0;
154 int covered = 0;
155
156 for (Iterator iter = branches.values().iterator(); iter.hasNext();) {
157 LineData next = (LineData) iter.next();
158 if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor()))
159 {
160 total += next.getNumberOfValidBranches();
161 covered += next.getNumberOfCoveredBranches();
162 }
163 }
164 if (total == 0) return 1.0;
165 return (double) covered / total;
166 }
167
168 public Collection getBranches()
169 {
170 return Collections.unmodifiableCollection(branches.keySet());
171 }
172
173 /**
174 * @param lineNumber The source code line number.
175 * @return The coverage of the line
176 */
177 public LineData getLineCoverage(int lineNumber)
178 {
179 Integer lineObject = new Integer(lineNumber);
180 if (!children.containsKey(lineObject))
181 {
182 return null;
183 }
184
185 return (LineData) children.get(lineObject);
186 }
187
188 /**
189 * @return The line coverage rate for particular method
190 */
191 public double getLineCoverageRate(String methodNameAndDescriptor)
192 {
193 int total = 0;
194 int hits = 0;
195
196 Iterator iter = children.values().iterator();
197 while (iter.hasNext())
198 {
199 LineData next = (LineData) iter.next();
200 if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor()))
201 {
202 total++;
203 if (next.getHits() > 0) {
204 hits++;
205 }
206 }
207 }
208 if (total == 0) return 1d;
209 return (double) hits / total;
210 }
211
212 private LineData getLineData(int lineNumber)
213 {
214 return (LineData)children.get(new Integer(lineNumber));
215 }
216
217 public SortedSet getLines()
218 {
219 return new TreeSet(this.children.values());
220 }
221
222 public Collection getLines(String methodNameAndDescriptor)
223 {
224 Collection lines = new HashSet();
225 Iterator iter = children.values().iterator();
226 while (iter.hasNext())
227 {
228 LineData next = (LineData)iter.next();
229 if (methodNameAndDescriptor.equals(next.getMethodName()
230 + next.getMethodDescriptor()))
231 {
232 lines.add(next);
233 }
234 }
235 return lines;
236 }
237
238 /**
239 * @return The method name and descriptor of each method found in the
240 * class represented by this instrumentation.
241 */
242 public Set getMethodNamesAndDescriptors()
243 {
244 return methodNamesAndDescriptors;
245 }
246
247 public String getName()
248 {
249 return name;
250 }
251
252 /**
253 * @return The number of branches in this class.
254 */
255 public int getNumberOfValidBranches()
256 {
257 int number = 0;
258 for (Iterator i = branches.values().iterator();
259 i.hasNext();
260 number += ((LineData) i.next()).getNumberOfValidBranches())
261 ;
262 return number;
263 }
264
265 /**
266 * @see net.sourceforge.cobertura.coveragedata.CoverageData#getNumberOfCoveredBranches()
267 */
268 public int getNumberOfCoveredBranches()
269 {
270 int number = 0;
271 for (Iterator i = branches.values().iterator();
272 i.hasNext();
273 number += ((LineData) i.next()).getNumberOfCoveredBranches())
274 ;
275 return number;
276 }
277
278 public String getPackageName()
279 {
280 int lastDot = this.name.lastIndexOf('.');
281 if (lastDot == -1)
282 {
283 return "";
284 }
285 return this.name.substring(0, lastDot);
286 }
287
288 /**
289 * Return the name of the file containing this class. If this
290 * class' sourceFileName has not been set (for whatever reason)
291 * then this method will attempt to infer the name of the source
292 * file using the class name.
293 *
294 * @return The name of the source file, for example
295 * net/sourceforge/cobertura/coveragedata/ClassData.java
296 */
297 public String getSourceFileName()
298 {
299 String baseName;
300 if (sourceFileName != null)
301 baseName = sourceFileName;
302 else
303 {
304 baseName = getBaseName();
305 int firstDollarSign = baseName.indexOf('$');
306 if (firstDollarSign == -1 || firstDollarSign == 0)
307 baseName += ".java";
308 else
309 baseName = baseName.substring(0, firstDollarSign)
310 + ".java";
311 }
312
313 String packageName = getPackageName();
314 if (packageName.equals(""))
315 return baseName;
316 return packageName.replace('.', '/') + '/' + baseName;
317 }
318
319 public int hashCode()
320 {
321 return this.name.hashCode();
322 }
323
324 /**
325 * @return True if the line contains at least one condition jump (branch)
326 */
327 public boolean hasBranch(int lineNumber)
328 {
329 return branches.containsKey(new Integer(lineNumber));
330 }
331
332 /**
333 * Determine if a given line number is a valid line of code.
334 *
335 * @return True if the line contains executable code. False
336 * if the line is empty, or a comment, etc.
337 */
338 public boolean isValidSourceLineNumber(int lineNumber)
339 {
340 return children.containsKey(new Integer(lineNumber));
341 }
342
343 public void addLineJump(int lineNumber, int branchNumber)
344 {
345 LineData lineData = getLineData(lineNumber);
346 if (lineData != null)
347 {
348 lineData.addJump(branchNumber);
349 this.branches.put(new Integer(lineNumber), lineData);
350 }
351 }
352
353 public void addLineSwitch(int lineNumber, int switchNumber, int[] keys)
354 {
355 LineData lineData = getLineData(lineNumber);
356 if (lineData != null)
357 {
358 lineData.addSwitch(switchNumber, keys);
359 this.branches.put(new Integer(lineNumber), lineData);
360 }
361 }
362
363 public void addLineSwitch(int lineNumber, int switchNumber, int min, int max)
364 {
365 LineData lineData = getLineData(lineNumber);
366 if (lineData != null)
367 {
368 lineData.addSwitch(switchNumber, min, max);
369 this.branches.put(new Integer(lineNumber), lineData);
370 }
371 }
372
373 /**
374 * Merge some existing instrumentation with this instrumentation.
375 *
376 * @param coverageData Some existing coverage data.
377 */
378 public void merge(CoverageData coverageData)
379 {
380 ClassData classData = (ClassData)coverageData;
381
382 // If objects contain data for different classes then don't merge
383 if (!this.getName().equals(classData.getName()))
384 return;
385
386 super.merge(coverageData);
387
388 // We can't just call this.branches.putAll(classData.branches);
389 // Why not? If we did a putAll, then the LineData objects from
390 // the coverageData class would overwrite the LineData objects
391 // that are already in "this.branches" And we don't need to
392 // update the LineData objects that are already in this.branches
393 // because they are shared between this.branches and this.children,
394 // so the object hit counts will be moved when we called
395 // super.merge() above.
396 for (Iterator iter = classData.branches.keySet().iterator(); iter.hasNext();)
397 {
398 Object key = iter.next();
399 if (!this.branches.containsKey(key))
400 {
401 this.branches.put(key, classData.branches.get(key));
402 }
403 }
404
405 this.containsInstrumentationInfo |= classData.containsInstrumentationInfo;
406 this.methodNamesAndDescriptors.addAll(classData
407 .getMethodNamesAndDescriptors());
408 if (classData.sourceFileName != null)
409 this.sourceFileName = classData.sourceFileName;
410 }
411
412 public void removeLine(int lineNumber)
413 {
414 Integer lineObject = new Integer(lineNumber);
415 children.remove(lineObject);
416 branches.remove(lineObject);
417 }
418
419 public void setContainsInstrumentationInfo()
420 {
421 this.containsInstrumentationInfo = true;
422 }
423
424 public void setSourceFileName(String sourceFileName)
425 {
426 this.sourceFileName = sourceFileName;
427 }
428
429 /**
430 * Increment the number of hits for a particular line of code.
431 *
432 * @param lineNumber the line of code to increment the number of hits.
433 */
434 public void touch(int lineNumber)
435 {
436 LineData lineData = getLineData(lineNumber);
437 if (lineData == null)
438 lineData = addLine(lineNumber, null, null);
439 lineData.touch();
440 }
441
442 /**
443 * Increments the number of hits for particular hit counter of particular branch on particular line number.
444 *
445 * @param lineNumber The line of code where the branch is
446 * @param branchNumber The branch on the line to change the hit counter
447 * @param branch The hit counter (true or false)
448 */
449 public void touchJump(int lineNumber, int branchNumber, boolean branch) {
450 LineData lineData = getLineData(lineNumber);
451 if (lineData == null)
452 lineData = addLine(lineNumber, null, null);
453 lineData.touchJump(branchNumber, branch);
454 }
455
456 /**
457 * Increments the number of hits for particular hit counter of particular switch branch on particular line number.
458 *
459 * @param lineNumber The line of code where the branch is
460 * @param switchNumber The switch on the line to change the hit counter
461 * @param branch The hit counter
462 */
463 public void touchSwitch(int lineNumber, int switchNumber, int branch) {
464 LineData lineData = getLineData(lineNumber);
465 if (lineData == null)
466 lineData = addLine(lineNumber, null, null);
467 lineData.touchSwitch(switchNumber, branch);
468 }
469
470 }