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