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 }