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 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 102 /** 103 * This is required because we implement Comparable. 104 */ 105 public int compareTo(Object o) 106 { 107 if (!o.getClass().equals(ClassData.class)) 108 return Integer.MAX_VALUE; 109 return this.name.compareTo(((ClassData)o).name); 110 } 111 112 public boolean containsInstrumentationInfo() 113 { 114 return this.containsInstrumentationInfo; 115 } 116 117 /** 118 * Returns true if the given object is an instance of the 119 * ClassData class, and it contains the same data as this 120 * class. 121 */ 122 public boolean equals(Object obj) 123 { 124 if (this == obj) 125 return true; 126 if ((obj == null) || !(obj.getClass().equals(this.getClass()))) 127 return false; 128 129 ClassData classData = (ClassData)obj; 130 return super.equals(obj) 131 && this.branches.equals(classData.branches) 132 && this.methodNamesAndDescriptors 133 .equals(classData.methodNamesAndDescriptors) 134 && this.name.equals(classData.name) 135 && this.sourceFileName.equals(classData.sourceFileName); 136 } 137 138 public String getBaseName() 139 { 140 int lastDot = this.name.lastIndexOf('.'); 141 if (lastDot == -1) 142 { 143 return this.name; 144 } 145 return this.name.substring(lastDot + 1); 146 } 147 148 /** 149 * @return The branch coverage rate for a particular method. 150 */ 151 public double getBranchCoverageRate(String methodNameAndDescriptor) 152 { 153 int total = 0; 154 int covered = 0; 155 156 for (Iterator iter = branches.values().iterator(); iter.hasNext();) { 157 LineData next = (LineData) iter.next(); 158 if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor())) 159 { 160 total += next.getNumberOfValidBranches(); 161 covered += next.getNumberOfCoveredBranches(); 162 } 163 } 164 if (total == 0) return 1.0; 165 return (double) covered / total; 166 } 167 168 public Collection getBranches() 169 { 170 return Collections.unmodifiableCollection(branches.keySet()); 171 } 172 173 /** 174 * @param lineNumber The source code line number. 175 * @return The coverage of the line 176 */ 177 public LineData getLineCoverage(int lineNumber) 178 { 179 Integer lineObject = new Integer(lineNumber); 180 if (!children.containsKey(lineObject)) 181 { 182 return null; 183 } 184 185 return (LineData) children.get(lineObject); 186 } 187 188 /** 189 * @return The line coverage rate for particular method 190 */ 191 public double getLineCoverageRate(String methodNameAndDescriptor) 192 { 193 int total = 0; 194 int hits = 0; 195 196 Iterator iter = children.values().iterator(); 197 while (iter.hasNext()) 198 { 199 LineData next = (LineData) iter.next(); 200 if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor())) 201 { 202 total++; 203 if (next.getHits() > 0) { 204 hits++; 205 } 206 } 207 } 208 if (total == 0) return 1d; 209 return (double) hits / total; 210 } 211 212 private LineData getLineData(int lineNumber) 213 { 214 return (LineData)children.get(new Integer(lineNumber)); 215 } 216 217 public SortedSet getLines() 218 { 219 return new TreeSet(this.children.values()); 220 } 221 222 public Collection getLines(String methodNameAndDescriptor) 223 { 224 Collection lines = new HashSet(); 225 Iterator iter = children.values().iterator(); 226 while (iter.hasNext()) 227 { 228 LineData next = (LineData)iter.next(); 229 if (methodNameAndDescriptor.equals(next.getMethodName() 230 + next.getMethodDescriptor())) 231 { 232 lines.add(next); 233 } 234 } 235 return lines; 236 } 237 238 /** 239 * @return The method name and descriptor of each method found in the 240 * class represented by this instrumentation. 241 */ 242 public Set getMethodNamesAndDescriptors() 243 { 244 return methodNamesAndDescriptors; 245 } 246 247 public String getName() 248 { 249 return name; 250 } 251 252 /** 253 * @return The number of branches in this class. 254 */ 255 public int getNumberOfValidBranches() 256 { 257 int number = 0; 258 for (Iterator i = branches.values().iterator(); 259 i.hasNext(); 260 number += ((LineData) i.next()).getNumberOfValidBranches()) 261 ; 262 return number; 263 } 264 265 /** 266 * @see net.sourceforge.cobertura.coveragedata.CoverageData#getNumberOfCoveredBranches() 267 */ 268 public int getNumberOfCoveredBranches() 269 { 270 int number = 0; 271 for (Iterator i = branches.values().iterator(); 272 i.hasNext(); 273 number += ((LineData) i.next()).getNumberOfCoveredBranches()) 274 ; 275 return number; 276 } 277 278 public String getPackageName() 279 { 280 int lastDot = this.name.lastIndexOf('.'); 281 if (lastDot == -1) 282 { 283 return ""; 284 } 285 return this.name.substring(0, lastDot); 286 } 287 288 /** 289 * Return the name of the file containing this class. If this 290 * class' sourceFileName has not been set (for whatever reason) 291 * then this method will attempt to infer the name of the source 292 * file using the class name. 293 * 294 * @return The name of the source file, for example 295 * net/sourceforge/cobertura/coveragedata/ClassData.java 296 */ 297 public String getSourceFileName() 298 { 299 String baseName; 300 if (sourceFileName != null) 301 baseName = sourceFileName; 302 else 303 { 304 baseName = getBaseName(); 305 int firstDollarSign = baseName.indexOf('$'); 306 if (firstDollarSign == -1 || firstDollarSign == 0) 307 baseName += ".java"; 308 else 309 baseName = baseName.substring(0, firstDollarSign) 310 + ".java"; 311 } 312 313 String packageName = getPackageName(); 314 if (packageName.equals("")) 315 return baseName; 316 return packageName.replace('.', '/') + '/' + baseName; 317 } 318 319 public int hashCode() 320 { 321 return this.name.hashCode(); 322 } 323 324 /** 325 * @return True if the line contains at least one condition jump (branch) 326 */ 327 public boolean hasBranch(int lineNumber) 328 { 329 return branches.containsKey(new Integer(lineNumber)); 330 } 331 332 /** 333 * Determine if a given line number is a valid line of code. 334 * 335 * @return True if the line contains executable code. False 336 * if the line is empty, or a comment, etc. 337 */ 338 public boolean isValidSourceLineNumber(int lineNumber) 339 { 340 return children.containsKey(new Integer(lineNumber)); 341 } 342 343 public void addLineJump(int lineNumber, int branchNumber) 344 { 345 LineData lineData = getLineData(lineNumber); 346 if (lineData != null) 347 { 348 lineData.addJump(branchNumber); 349 this.branches.put(new Integer(lineNumber), lineData); 350 } 351 } 352 353 public void addLineSwitch(int lineNumber, int switchNumber, int[] keys) 354 { 355 LineData lineData = getLineData(lineNumber); 356 if (lineData != null) 357 { 358 lineData.addSwitch(switchNumber, keys); 359 this.branches.put(new Integer(lineNumber), lineData); 360 } 361 } 362 363 public void addLineSwitch(int lineNumber, int switchNumber, int min, int max) 364 { 365 LineData lineData = getLineData(lineNumber); 366 if (lineData != null) 367 { 368 lineData.addSwitch(switchNumber, min, max); 369 this.branches.put(new Integer(lineNumber), lineData); 370 } 371 } 372 373 /** 374 * Merge some existing instrumentation with this instrumentation. 375 * 376 * @param coverageData Some existing coverage data. 377 */ 378 public void merge(CoverageData coverageData) 379 { 380 ClassData classData = (ClassData)coverageData; 381 382 // If objects contain data for different classes then don't merge 383 if (!this.getName().equals(classData.getName())) 384 return; 385 386 super.merge(coverageData); 387 388 // We can't just call this.branches.putAll(classData.branches); 389 // Why not? If we did a putAll, then the LineData objects from 390 // the coverageData class would overwrite the LineData objects 391 // that are already in "this.branches" And we don't need to 392 // update the LineData objects that are already in this.branches 393 // because they are shared between this.branches and this.children, 394 // so the object hit counts will be moved when we called 395 // super.merge() above. 396 for (Iterator iter = classData.branches.keySet().iterator(); iter.hasNext();) 397 { 398 Object key = iter.next(); 399 if (!this.branches.containsKey(key)) 400 { 401 this.branches.put(key, classData.branches.get(key)); 402 } 403 } 404 405 this.containsInstrumentationInfo |= classData.containsInstrumentationInfo; 406 this.methodNamesAndDescriptors.addAll(classData 407 .getMethodNamesAndDescriptors()); 408 if (classData.sourceFileName != null) 409 this.sourceFileName = classData.sourceFileName; 410 } 411 412 public void removeLine(int lineNumber) 413 { 414 Integer lineObject = new Integer(lineNumber); 415 children.remove(lineObject); 416 branches.remove(lineObject); 417 } 418 419 public void setContainsInstrumentationInfo() 420 { 421 this.containsInstrumentationInfo = true; 422 } 423 424 public void setSourceFileName(String sourceFileName) 425 { 426 this.sourceFileName = sourceFileName; 427 } 428 429 /** 430 * Increment the number of hits for a particular line of code. 431 * 432 * @param lineNumber the line of code to increment the number of hits. 433 */ 434 public void touch(int lineNumber) 435 { 436 LineData lineData = getLineData(lineNumber); 437 if (lineData == null) 438 lineData = addLine(lineNumber, null, null); 439 lineData.touch(); 440 } 441 442 /** 443 * Increments the number of hits for particular hit counter of particular branch on particular line number. 444 * 445 * @param lineNumber The line of code where the branch is 446 * @param branchNumber The branch on the line to change the hit counter 447 * @param branch The hit counter (true or false) 448 */ 449 public void touchJump(int lineNumber, int branchNumber, boolean branch) { 450 LineData lineData = getLineData(lineNumber); 451 if (lineData == null) 452 lineData = addLine(lineNumber, null, null); 453 lineData.touchJump(branchNumber, branch); 454 } 455 456 /** 457 * Increments the number of hits for particular hit counter of particular switch branch on particular line number. 458 * 459 * @param lineNumber The line of code where the branch is 460 * @param switchNumber The switch on the line to change the hit counter 461 * @param branch The hit counter 462 */ 463 public void touchSwitch(int lineNumber, int switchNumber, int branch) { 464 LineData lineData = getLineData(lineNumber); 465 if (lineData == null) 466 lineData = addLine(lineNumber, null, null); 467 lineData.touchSwitch(switchNumber, branch); 468 } 469 470 }