001 /* 002 * Cobertura - http://cobertura.sourceforge.net/ 003 * 004 * Copyright (C) 2003 jcoverage ltd. 005 * Copyright (C) 2005 Mark Doliner 006 * Copyright (C) 2005 Grzegorz Lukasik 007 * Copyright (C) 2005 Bj?rn Beskow 008 * Copyright (C) 2006 John Lewis 009 * Copyright (C) 2009 Chris van Es 010 * Copyright (C) 2009 Ed Randall 011 * 012 * Cobertura is free software; you can redistribute it and/or modify 013 * it under the terms of the GNU General Public License as published 014 * by the Free Software Foundation; either version 2 of the License, 015 * or (at your option) any later version. 016 * 017 * Cobertura is distributed in the hope that it will be useful, but 018 * WITHOUT ANY WARRANTY; without even the implied warranty of 019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 020 * General Public License for more details. 021 * 022 * You should have received a copy of the GNU General Public License 023 * along with Cobertura; if not, write to the Free Software 024 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 025 * USA 026 */ 027 028 package net.sourceforge.cobertura.coveragedata; 029 030 import java.io.File; 031 import java.util.Collection; 032 import java.util.Collections; 033 import java.util.HashMap; 034 import java.util.Iterator; 035 import java.util.Map; 036 import java.util.SortedSet; 037 import java.util.TreeSet; 038 import java.util.concurrent.locks.Lock; 039 import java.util.concurrent.locks.ReentrantLock; 040 041 import net.sourceforge.cobertura.util.FileLocker; 042 043 public class ProjectData extends CoverageDataContainer implements HasBeenInstrumented 044 { 045 046 private static final long serialVersionUID = 6; 047 048 private static ProjectData globalProjectData = null; 049 private static final transient Lock globalProjectDataLock = new ReentrantLock(); 050 051 private static SaveTimer saveTimer = null; 052 053 /** This collection is used for quicker access to the list of classes. */ 054 private Map classes = new HashMap(); 055 056 public void addClassData(ClassData classData) 057 { 058 lock.lock(); 059 try 060 { 061 String packageName = classData.getPackageName(); 062 PackageData packageData = (PackageData)children.get(packageName); 063 if (packageData == null) 064 { 065 packageData = new PackageData(packageName); 066 // Each key is a package name, stored as an String object. 067 // Each value is information about the package, stored as a PackageData object. 068 this.children.put(packageName, packageData); 069 } 070 packageData.addClassData(classData); 071 this.classes.put(classData.getName(), classData); 072 } 073 finally 074 { 075 lock.unlock(); 076 } 077 } 078 079 public ClassData getClassData(String name) 080 { 081 lock.lock(); 082 try 083 { 084 return (ClassData)this.classes.get(name); 085 } 086 finally 087 { 088 lock.unlock(); 089 } 090 } 091 092 /** 093 * This is called by instrumented bytecode. 094 */ 095 public ClassData getOrCreateClassData(String name) 096 { 097 lock.lock(); 098 try 099 { 100 ClassData classData = (ClassData)this.classes.get(name); 101 if (classData == null) 102 { 103 classData = new ClassData(name); 104 addClassData(classData); 105 } 106 return classData; 107 } 108 finally 109 { 110 lock.unlock(); 111 } 112 } 113 114 public Collection getClasses() 115 { 116 lock.lock(); 117 try 118 { 119 return this.classes.values(); 120 } 121 finally 122 { 123 lock.unlock(); 124 } 125 } 126 127 public int getNumberOfClasses() 128 { 129 lock.lock(); 130 try 131 { 132 return this.classes.size(); 133 } 134 finally 135 { 136 lock.unlock(); 137 } 138 } 139 140 public int getNumberOfSourceFiles() 141 { 142 return getSourceFiles().size(); 143 } 144 145 public SortedSet getPackages() 146 { 147 lock.lock(); 148 try 149 { 150 return new TreeSet(this.children.values()); 151 } 152 finally 153 { 154 lock.unlock(); 155 } 156 } 157 158 public Collection getSourceFiles() 159 { 160 SortedSet sourceFileDatas = new TreeSet(); 161 lock.lock(); 162 try 163 { 164 Iterator iter = this.children.values().iterator(); 165 while (iter.hasNext()) 166 { 167 PackageData packageData = (PackageData)iter.next(); 168 sourceFileDatas.addAll(packageData.getSourceFiles()); 169 } 170 } 171 finally 172 { 173 lock.unlock(); 174 } 175 return sourceFileDatas; 176 } 177 178 /** 179 * Get all subpackages of the given package. Includes also specified package if 180 * it exists. 181 * 182 * @param packageName The package name to find subpackages for. 183 * For example, "com.example" 184 * @return A collection containing PackageData objects. Each one 185 * has a name beginning with the given packageName. For 186 * example: "com.example.io", "com.example.io.internal" 187 */ 188 public SortedSet getSubPackages(String packageName) 189 { 190 SortedSet subPackages = new TreeSet(); 191 lock.lock(); 192 try 193 { 194 Iterator iter = this.children.values().iterator(); 195 while (iter.hasNext()) 196 { 197 PackageData packageData = (PackageData)iter.next(); 198 if (packageData.getName().startsWith(packageName)) 199 subPackages.add(packageData); 200 } 201 } 202 finally 203 { 204 lock.unlock(); 205 } 206 return subPackages; 207 } 208 209 public void merge(CoverageData coverageData) 210 { 211 if (coverageData == null) { 212 return; 213 } 214 ProjectData projectData = (ProjectData)coverageData; 215 getBothLocks(projectData); 216 try 217 { 218 super.merge(coverageData); 219 220 for (Iterator iter = projectData.classes.keySet().iterator(); iter.hasNext();) 221 { 222 Object key = iter.next(); 223 if (!this.classes.containsKey(key)) 224 { 225 this.classes.put(key, projectData.classes.get(key)); 226 } 227 } 228 } 229 finally 230 { 231 lock.unlock(); 232 projectData.lock.unlock(); 233 } 234 } 235 236 /** 237 * Get a reference to a ProjectData object in order to increase the 238 * coverage count for a specific line. 239 * 240 * This method is only called by code that has been instrumented. It 241 * is not called by any of the Cobertura code or ant tasks. 242 */ 243 public static ProjectData getGlobalProjectData() 244 { 245 globalProjectDataLock.lock(); 246 try 247 { 248 if (globalProjectData != null) 249 return globalProjectData; 250 251 globalProjectData = new ProjectData(); 252 initialize(); 253 return globalProjectData; 254 } 255 finally 256 { 257 globalProjectDataLock.unlock(); 258 } 259 } 260 261 // TODO: Is it possible to do this as a static initializer? 262 private static void initialize() 263 { 264 // Hack for Tomcat - by saving project data right now we force loading 265 // of classes involved in this process (like ObjectOutputStream) 266 // so that it won't be necessary to load them on JVM shutdown 267 if (System.getProperty("catalina.home") != null) 268 { 269 saveGlobalProjectData(); 270 271 // Force the class loader to load some classes that are 272 // required by our JVM shutdown hook. 273 // TODO: Use ClassLoader.loadClass("whatever"); instead 274 ClassData.class.toString(); 275 CoverageData.class.toString(); 276 CoverageDataContainer.class.toString(); 277 FileLocker.class.toString(); 278 HasBeenInstrumented.class.toString(); 279 LineData.class.toString(); 280 PackageData.class.toString(); 281 SourceFileData.class.toString(); 282 } 283 284 // Add a hook to save the data when the JVM exits 285 saveTimer = new SaveTimer(); 286 Runtime.getRuntime().addShutdownHook(new Thread(saveTimer)); 287 288 // Possibly also save the coverage data every x seconds? 289 //Timer timer = new Timer(true); 290 //timer.schedule(saveTimer, 100); 291 } 292 293 public static void saveGlobalProjectData() 294 { 295 ProjectData projectDataToSave = null; 296 297 globalProjectDataLock.lock(); 298 try 299 { 300 projectDataToSave = globalProjectData; 301 302 /* 303 * The next statement is not necessary at the moment, because this method is only called 304 * either at the very beginning or at the very end of a test. If the code is changed 305 * to save more frequently, then this will become important. 306 */ 307 globalProjectData = new ProjectData(); 308 } 309 finally 310 { 311 globalProjectDataLock.unlock(); 312 } 313 314 /* 315 * Now sleep a bit in case there is a thread still holding a reference to the "old" 316 * globalProjectData (now referenced with projectDataToSave). 317 * We want it to finish its updates. I assume 1 second is plenty of time. 318 */ 319 try 320 { 321 Thread.sleep(1000); 322 } 323 catch (InterruptedException e) 324 { 325 } 326 327 // Get a file lock 328 File dataFile = CoverageDataFileHandler.getDefaultDataFile(); 329 330 /* 331 * A note about the next synchronized block: Cobertura uses static fields to 332 * hold the data. When there are multiple classloaders, each classloader 333 * will keep track of the line counts for the classes that it loads. 334 * 335 * The static initializers for the Cobertura classes are also called for 336 * each classloader. So, there is one shutdown hook for each classloader. 337 * So, when the JVM exits, each shutdown hook will try to write the 338 * data it has kept to the datafile. They will do this at the same 339 * time. Before Java 6, this seemed to work fine, but with Java 6, there 340 * seems to have been a change with how file locks are implemented. So, 341 * care has to be taken to make sure only one thread locks a file at a time. 342 * 343 * So, we will synchronize on the string that represents the path to the 344 * dataFile. Apparently, there will be only one of these in the JVM 345 * even if there are multiple classloaders. I assume that is because 346 * the String class is loaded by the JVM's root classloader. 347 */ 348 synchronized (dataFile.getPath().intern() ) { 349 FileLocker fileLocker = new FileLocker(dataFile); 350 351 try 352 { 353 // Read the old data, merge our current data into it, then 354 // write a new ser file. 355 if (fileLocker.lock()) 356 { 357 ProjectData datafileProjectData = loadCoverageDataFromDatafile(dataFile); 358 if (datafileProjectData == null) 359 { 360 datafileProjectData = projectDataToSave; 361 } 362 else 363 { 364 datafileProjectData.merge(projectDataToSave); 365 } 366 CoverageDataFileHandler.saveCoverageData(datafileProjectData, dataFile); 367 } 368 } 369 finally 370 { 371 // Release the file lock 372 fileLocker.release(); 373 } 374 } 375 } 376 377 private static ProjectData loadCoverageDataFromDatafile(File dataFile) 378 { 379 ProjectData projectData = null; 380 381 // Read projectData from the serialized file. 382 if (dataFile.isFile()) 383 { 384 projectData = CoverageDataFileHandler.loadCoverageData(dataFile); 385 } 386 387 if (projectData == null) 388 { 389 // We could not read from the serialized file, so use a new object. 390 System.out.println("Cobertura: Coverage data file " + dataFile.getAbsolutePath() 391 + " either does not exist or is not readable. Creating a new data file."); 392 } 393 394 return projectData; 395 } 396 397 }