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