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 lock.lock();
086 try
087 {
088 LineData lineData = getLineData(lineNumber);
089 if (lineData == null)
090 {
091 lineData = new LineData(lineNumber);
092 // Each key is a line number in this class, stored as an Integer object.
093 // Each value is information about the line, stored as a LineData object.
094 children.put(new Integer(lineNumber), lineData);
095 }
096 lineData.setMethodNameAndDescriptor(methodName, methodDescriptor);
097
098 // methodName and methodDescriptor can be null when cobertura.ser with
099 // no line information was loaded (or was not loaded at all).
100 if( methodName!=null && methodDescriptor!=null)
101 methodNamesAndDescriptors.add(methodName + methodDescriptor);
102 return lineData;
103 }
104 finally
105 {
106 lock.unlock();
107 }
108 }
109
110 /**
111 * This is required because we implement Comparable.
112 */
113 public int compareTo(Object o)
114 {
115 if (!o.getClass().equals(ClassData.class))
116 return Integer.MAX_VALUE;
117 return this.name.compareTo(((ClassData)o).name);
118 }
119
120 public boolean containsInstrumentationInfo()
121 {
122 lock.lock();
123 try
124 {
125 return this.containsInstrumentationInfo;
126 }
127 finally
128 {
129 lock.unlock();
130 }
131 }
132
133 /**
134 * Returns true if the given object is an instance of the
135 * ClassData class, and it contains the same data as this
136 * class.
137 */
138 public boolean equals(Object obj)
139 {
140 if (this == obj)
141 return true;
142 if ((obj == null) || !(obj.getClass().equals(this.getClass())))
143 return false;
144
145 ClassData classData = (ClassData)obj;
146 getBothLocks(classData);
147 try
148 {
149 return super.equals(obj)
150 && this.branches.equals(classData.branches)
151 && this.methodNamesAndDescriptors
152 .equals(classData.methodNamesAndDescriptors)
153 && this.name.equals(classData.name)
154 && this.sourceFileName.equals(classData.sourceFileName);
155 }
156 finally
157 {
158 lock.unlock();
159 classData.lock.unlock();
160 }
161 }
162
163 public String getBaseName()
164 {
165 int lastDot = this.name.lastIndexOf('.');
166 if (lastDot == -1)
167 {
168 return this.name;
169 }
170 return this.name.substring(lastDot + 1);
171 }
172
173 /**
174 * @return The branch coverage rate for a particular method.
175 */
176 public double getBranchCoverageRate(String methodNameAndDescriptor)
177 {
178 int total = 0;
179 int covered = 0;
180
181 lock.lock();
182 try
183 {
184 for (Iterator iter = branches.values().iterator(); iter.hasNext();) {
185 LineData next = (LineData) iter.next();
186 if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor()))
187 {
188 total += next.getNumberOfValidBranches();
189 covered += next.getNumberOfCoveredBranches();
190 }
191 }
192 if (total == 0) return 1.0;
193 return (double) covered / total;
194 }
195 finally
196 {
197 lock.unlock();
198 }
199 }
200
201 public Collection getBranches()
202 {
203 lock.lock();
204 try
205 {
206 return Collections.unmodifiableCollection(branches.keySet());
207 }
208 finally
209 {
210 lock.unlock();
211 }
212 }
213
214 /**
215 * @param lineNumber The source code line number.
216 * @return The coverage of the line
217 */
218 public LineData getLineCoverage(int lineNumber)
219 {
220 Integer lineObject = new Integer(lineNumber);
221 lock.lock();
222 try
223 {
224 if (!children.containsKey(lineObject))
225 {
226 return null;
227 }
228
229 return (LineData) children.get(lineObject);
230 }
231 finally
232 {
233 lock.unlock();
234 }
235 }
236
237 /**
238 * @return The line coverage rate for particular method
239 */
240 public double getLineCoverageRate(String methodNameAndDescriptor)
241 {
242 int total = 0;
243 int hits = 0;
244
245 lock.lock();
246 try
247 {
248 Iterator iter = children.values().iterator();
249 while (iter.hasNext())
250 {
251 LineData next = (LineData) iter.next();
252 if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor()))
253 {
254 total++;
255 if (next.getHits() > 0) {
256 hits++;
257 }
258 }
259 }
260 if (total == 0) return 1d;
261 return (double) hits / total;
262 }
263 finally
264 {
265 lock.unlock();
266 }
267 }
268
269 private LineData getLineData(int lineNumber)
270 {
271 lock.lock();
272 try
273 {
274 return (LineData)children.get(Integer.valueOf(lineNumber));
275 }
276 finally
277 {
278 lock.unlock();
279 }
280 }
281
282 public SortedSet getLines()
283 {
284 lock.lock();
285 try
286 {
287 return new TreeSet(this.children.values());
288 }
289 finally
290 {
291 lock.unlock();
292 }
293 }
294
295 public Collection getLines(String methodNameAndDescriptor)
296 {
297 Collection lines = new HashSet();
298 lock.lock();
299 try
300 {
301 Iterator iter = children.values().iterator();
302 while (iter.hasNext())
303 {
304 LineData next = (LineData)iter.next();
305 if (methodNameAndDescriptor.equals(next.getMethodName()
306 + next.getMethodDescriptor()))
307 {
308 lines.add(next);
309 }
310 }
311 return lines;
312 }
313 finally
314 {
315 lock.unlock();
316 }
317 }
318
319 /**
320 * @return The method name and descriptor of each method found in the
321 * class represented by this instrumentation.
322 */
323 public Set getMethodNamesAndDescriptors()
324 {
325 lock.lock();
326 try
327 {
328 return methodNamesAndDescriptors;
329 }
330 finally
331 {
332 lock.unlock();
333 }
334 }
335
336 public String getName()
337 {
338 return name;
339 }
340
341 /**
342 * @return The number of branches in this class.
343 */
344 public int getNumberOfValidBranches()
345 {
346 int number = 0;
347 lock.lock();
348 try
349 {
350 for (Iterator i = branches.values().iterator();
351 i.hasNext();
352 number += ((LineData) i.next()).getNumberOfValidBranches())
353 ;
354 return number;
355 }
356 finally
357 {
358 lock.unlock();
359 }
360 }
361
362 /**
363 * @see net.sourceforge.cobertura.coveragedata.CoverageData#getNumberOfCoveredBranches()
364 */
365 public int getNumberOfCoveredBranches()
366 {
367 int number = 0;
368 lock.lock();
369 try
370 {
371 for (Iterator i = branches.values().iterator();
372 i.hasNext();
373 number += ((LineData) i.next()).getNumberOfCoveredBranches())
374 ;
375 return number;
376 }
377 finally
378 {
379 lock.unlock();
380 }
381 }
382
383 public String getPackageName()
384 {
385 int lastDot = this.name.lastIndexOf('.');
386 if (lastDot == -1)
387 {
388 return "";
389 }
390 return this.name.substring(0, lastDot);
391 }
392
393 /**
394 * Return the name of the file containing this class. If this
395 * class' sourceFileName has not been set (for whatever reason)
396 * then this method will attempt to infer the name of the source
397 * file using the class name.
398 *
399 * @return The name of the source file, for example
400 * net/sourceforge/cobertura/coveragedata/ClassData.java
401 */
402 public String getSourceFileName()
403 {
404 String baseName;
405 lock.lock();
406 try
407 {
408 if (sourceFileName != null)
409 baseName = sourceFileName;
410 else
411 {
412 baseName = getBaseName();
413 int firstDollarSign = baseName.indexOf('$');
414 if (firstDollarSign == -1 || firstDollarSign == 0)
415 baseName += ".java";
416 else
417 baseName = baseName.substring(0, firstDollarSign)
418 + ".java";
419 }
420
421 String packageName = getPackageName();
422 if (packageName.equals(""))
423 return baseName;
424 return packageName.replace('.', '/') + '/' + baseName;
425 }
426 finally
427 {
428 lock.unlock();
429 }
430 }
431
432 public int hashCode()
433 {
434 return this.name.hashCode();
435 }
436
437 /**
438 * @return True if the line contains at least one condition jump (branch)
439 */
440 public boolean hasBranch(int lineNumber)
441 {
442 lock.lock();
443 try
444 {
445 return branches.containsKey(Integer.valueOf(lineNumber));
446 }
447 finally
448 {
449 lock.unlock();
450 }
451 }
452
453 /**
454 * Determine if a given line number is a valid line of code.
455 *
456 * @return True if the line contains executable code. False
457 * if the line is empty, or a comment, etc.
458 */
459 public boolean isValidSourceLineNumber(int lineNumber)
460 {
461 lock.lock();
462 try
463 {
464 return children.containsKey(Integer.valueOf(lineNumber));
465 }
466 finally
467 {
468 lock.unlock();
469 }
470 }
471
472 public void addLineJump(int lineNumber, int branchNumber)
473 {
474 lock.lock();
475 try
476 {
477 LineData lineData = getLineData(lineNumber);
478 if (lineData != null)
479 {
480 lineData.addJump(branchNumber);
481 this.branches.put(Integer.valueOf(lineNumber), lineData);
482 }
483 }
484 finally
485 {
486 lock.unlock();
487 }
488 }
489
490 public void addLineSwitch(int lineNumber, int switchNumber, int[] keys)
491 {
492 lock.lock();
493 try
494 {
495 LineData lineData = getLineData(lineNumber);
496 if (lineData != null)
497 {
498 lineData.addSwitch(switchNumber, keys);
499 this.branches.put(Integer.valueOf(lineNumber), lineData);
500 }
501 }
502 finally
503 {
504 lock.unlock();
505 }
506 }
507
508 public void addLineSwitch(int lineNumber, int switchNumber, int min, int max)
509 {
510 lock.lock();
511 try
512 {
513 LineData lineData = getLineData(lineNumber);
514 if (lineData != null)
515 {
516 lineData.addSwitch(switchNumber, min, max);
517 this.branches.put(Integer.valueOf(lineNumber), lineData);
518 }
519 }
520 finally
521 {
522 lock.unlock();
523 }
524 }
525
526 /**
527 * Merge some existing instrumentation with this instrumentation.
528 *
529 * @param coverageData Some existing coverage data.
530 */
531 public void merge(CoverageData coverageData)
532 {
533 ClassData classData = (ClassData)coverageData;
534
535 // If objects contain data for different classes then don't merge
536 if (!this.getName().equals(classData.getName()))
537 return;
538
539 getBothLocks(classData);
540 try
541 {
542 super.merge(coverageData);
543
544 // We can't just call this.branches.putAll(classData.branches);
545 // Why not? If we did a putAll, then the LineData objects from
546 // the coverageData class would overwrite the LineData objects
547 // that are already in "this.branches" And we don't need to
548 // update the LineData objects that are already in this.branches
549 // because they are shared between this.branches and this.children,
550 // so the object hit counts will be moved when we called
551 // super.merge() above.
552 for (Iterator iter = classData.branches.keySet().iterator(); iter.hasNext();)
553 {
554 Object key = iter.next();
555 if (!this.branches.containsKey(key))
556 {
557 this.branches.put(key, classData.branches.get(key));
558 }
559 }
560
561 this.containsInstrumentationInfo |= classData.containsInstrumentationInfo;
562 this.methodNamesAndDescriptors.addAll(classData
563 .getMethodNamesAndDescriptors());
564 if (classData.sourceFileName != null)
565 this.sourceFileName = classData.sourceFileName;
566 }
567 finally
568 {
569 lock.unlock();
570 classData.lock.unlock();
571 }
572 }
573
574 public void removeLine(int lineNumber)
575 {
576 Integer lineObject = Integer.valueOf(lineNumber);
577 lock.lock();
578 try
579 {
580 children.remove(lineObject);
581 branches.remove(lineObject);
582 }
583 finally
584 {
585 lock.unlock();
586 }
587 }
588
589 public void setContainsInstrumentationInfo()
590 {
591 lock.lock();
592 try
593 {
594 this.containsInstrumentationInfo = true;
595 }
596 finally
597 {
598 lock.unlock();
599 }
600 }
601
602 public void setSourceFileName(String sourceFileName)
603 {
604 lock.lock();
605 try
606 {
607 this.sourceFileName = sourceFileName;
608 }
609 finally
610 {
611 lock.unlock();
612 }
613 }
614
615 /**
616 * Increment the number of hits for a particular line of code.
617 *
618 * @param lineNumber the line of code to increment the number of hits.
619 */
620 public void touch(int lineNumber)
621 {
622 lock.lock();
623 try
624 {
625 LineData lineData = getLineData(lineNumber);
626 if (lineData == null)
627 lineData = addLine(lineNumber, null, null);
628 lineData.touch();
629 }
630 finally
631 {
632 lock.unlock();
633 }
634 }
635
636 /**
637 * Increments the number of hits for particular hit counter of particular branch on particular line number.
638 *
639 * @param lineNumber The line of code where the branch is
640 * @param branchNumber The branch on the line to change the hit counter
641 * @param branch The hit counter (true or false)
642 */
643 public void touchJump(int lineNumber, int branchNumber, boolean branch) {
644 lock.lock();
645 try
646 {
647 LineData lineData = getLineData(lineNumber);
648 if (lineData == null)
649 lineData = addLine(lineNumber, null, null);
650 lineData.touchJump(branchNumber, branch);
651 }
652 finally
653 {
654 lock.unlock();
655 }
656 }
657
658 /**
659 * Increments the number of hits for particular hit counter of particular switch branch on particular line number.
660 *
661 * @param lineNumber The line of code where the branch is
662 * @param switchNumber The switch on the line to change the hit counter
663 * @param branch The hit counter
664 */
665 public void touchSwitch(int lineNumber, int switchNumber, int branch) {
666 lock.lock();
667 try
668 {
669 LineData lineData = getLineData(lineNumber);
670 if (lineData == null)
671 lineData = addLine(lineNumber, null, null);
672 lineData.touchSwitch(switchNumber, branch);
673 }
674 finally
675 {
676 lock.unlock();
677 }
678 }
679
680 }