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 024package net.sourceforge.cobertura.coveragedata; 025 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.Iterator; 031import java.util.Map; 032import java.util.Set; 033import java.util.SortedSet; 034import 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 051public 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}