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 * 010 * Cobertura is free software; you can redistribute it and/or modify 011 * it under the terms of the GNU General Public License as published 012 * by the Free Software Foundation; either version 2 of the License, 013 * or (at your option) any later version. 014 * 015 * Cobertura is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of 017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 * General Public License for more details. 019 * 020 * You should have received a copy of the GNU General Public License 021 * along with Cobertura; if not, write to the Free Software 022 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 023 * USA 024 */ 025 026 package net.sourceforge.cobertura.coveragedata; 027 028 import java.io.File; 029 import java.util.Collection; 030 import java.util.Collections; 031 import java.util.HashMap; 032 import java.util.Iterator; 033 import java.util.Map; 034 import java.util.SortedSet; 035 import java.util.TreeSet; 036 037 import net.sourceforge.cobertura.util.FileLocker; 038 039 public class ProjectData extends CoverageDataContainer implements HasBeenInstrumented 040 { 041 042 private static final long serialVersionUID = 6; 043 044 private static ProjectData globalProjectData = null; 045 046 private static SaveTimer saveTimer = null; 047 048 /** This collection is used for quicker access to the list of classes. */ 049 private Map classes = Collections.synchronizedMap(new HashMap()); 050 051 public void addClassData(ClassData classData) 052 { 053 String packageName = classData.getPackageName(); 054 PackageData packageData = (PackageData)children.get(packageName); 055 if (packageData == null) 056 { 057 packageData = new PackageData(packageName); 058 // Each key is a package name, stored as an String object. 059 // Each value is information about the package, stored as a PackageData object. 060 this.children.put(packageName, packageData); 061 } 062 packageData.addClassData(classData); 063 this.classes.put(classData.getName(), classData); 064 } 065 066 public ClassData getClassData(String name) 067 { 068 return (ClassData)this.classes.get(name); 069 } 070 071 /** 072 * This is called by instrumented bytecode. 073 */ 074 public synchronized ClassData getOrCreateClassData(String name) 075 { 076 ClassData classData = (ClassData)this.classes.get(name); 077 if (classData == null) 078 { 079 classData = new ClassData(name); 080 addClassData(classData); 081 } 082 return classData; 083 } 084 085 public Collection getClasses() 086 { 087 return this.classes.values(); 088 } 089 090 public int getNumberOfClasses() 091 { 092 return this.classes.size(); 093 } 094 095 public int getNumberOfSourceFiles() 096 { 097 return getSourceFiles().size(); 098 } 099 100 public SortedSet getPackages() 101 { 102 return new TreeSet(this.children.values()); 103 } 104 105 public Collection getSourceFiles() 106 { 107 SortedSet sourceFileDatas = new TreeSet(); 108 Iterator iter = this.children.values().iterator(); 109 while (iter.hasNext()) 110 { 111 PackageData packageData = (PackageData)iter.next(); 112 sourceFileDatas.addAll(packageData.getSourceFiles()); 113 } 114 return sourceFileDatas; 115 } 116 117 /** 118 * Get all subpackages of the given package. Includes also specified package if 119 * it exists. 120 * 121 * @param packageName The package name to find subpackages for. 122 * For example, "com.example" 123 * @return A collection containing PackageData objects. Each one 124 * has a name beginning with the given packageName. For 125 * example: "com.example.io", "com.example.io.internal" 126 */ 127 public SortedSet getSubPackages(String packageName) 128 { 129 SortedSet subPackages = new TreeSet(); 130 Iterator iter = this.children.values().iterator(); 131 while (iter.hasNext()) 132 { 133 PackageData packageData = (PackageData)iter.next(); 134 if (packageData.getName().startsWith(packageName)) 135 subPackages.add(packageData); 136 } 137 return subPackages; 138 } 139 140 public void merge(CoverageData coverageData) 141 { 142 super.merge(coverageData); 143 144 ProjectData projectData = (ProjectData)coverageData; 145 for (Iterator iter = projectData.classes.keySet().iterator(); iter.hasNext();) 146 { 147 Object key = iter.next(); 148 if (!this.classes.containsKey(key)) 149 { 150 this.classes.put(key, projectData.classes.get(key)); 151 } 152 } 153 } 154 155 /** 156 * Get a reference to a ProjectData object in order to increase the 157 * coverage count for a specific line. 158 * 159 * This method is only called by code that has been instrumented. It 160 * is not called by any of the Cobertura code or ant tasks. 161 */ 162 public static ProjectData getGlobalProjectData() 163 { 164 if (globalProjectData != null) 165 return globalProjectData; 166 167 globalProjectData = new ProjectData(); 168 initialize(); 169 return globalProjectData; 170 } 171 172 // TODO: Is it possible to do this as a static initializer? 173 private static void initialize() 174 { 175 // Hack for Tomcat - by saving project data right now we force loading 176 // of classes involved in this process (like ObjectOutputStream) 177 // so that it won't be necessary to load them on JVM shutdown 178 if (System.getProperty("catalina.home") != null) 179 { 180 saveGlobalProjectData(); 181 182 // Force the class loader to load some classes that are 183 // required by our JVM shutdown hook. 184 // TODO: Use ClassLoader.loadClass("whatever"); instead 185 ClassData.class.toString(); 186 CoverageData.class.toString(); 187 CoverageDataContainer.class.toString(); 188 FileLocker.class.toString(); 189 HasBeenInstrumented.class.toString(); 190 LineData.class.toString(); 191 PackageData.class.toString(); 192 SourceFileData.class.toString(); 193 } 194 195 // Add a hook to save the data when the JVM exits 196 saveTimer = new SaveTimer(); 197 Runtime.getRuntime().addShutdownHook(new Thread(saveTimer)); 198 199 // Possibly also save the coverage data every x seconds? 200 //Timer timer = new Timer(true); 201 //timer.schedule(saveTimer, 100); 202 } 203 204 public static void saveGlobalProjectData() 205 { 206 ProjectData projectDataToSave = globalProjectData; 207 208 /* 209 * The next statement is not necessary at the moment, because this method is only called 210 * either at the very beginning or at the very end of a test. If the code is changed 211 * to save more frequently, then this will become important. 212 */ 213 globalProjectData = new ProjectData(); 214 215 /* 216 * Now sleep a bit in case there is a thread still holding a reference to the "old" 217 * globalProjectData (now referenced with projectDataToSave). 218 * We want it to finish its updates. I assume 2 seconds is plenty of time. 219 */ 220 try 221 { 222 Thread.sleep(1000); 223 } 224 catch (InterruptedException e) 225 { 226 } 227 228 // Get a file lock 229 File dataFile = CoverageDataFileHandler.getDefaultDataFile(); 230 FileLocker fileLocker = new FileLocker(dataFile); 231 232 // Read the old data, merge our current data into it, then 233 // write a new ser file. 234 if (fileLocker.lock()) 235 { 236 ProjectData datafileProjectData = loadCoverageDataFromDatafile(dataFile); 237 if (datafileProjectData == null) 238 { 239 datafileProjectData = projectDataToSave; 240 } 241 else 242 { 243 datafileProjectData.merge(projectDataToSave); 244 } 245 CoverageDataFileHandler.saveCoverageData(datafileProjectData, dataFile); 246 } 247 248 // Release the file lock 249 fileLocker.release(); 250 } 251 252 private static ProjectData loadCoverageDataFromDatafile(File dataFile) 253 { 254 ProjectData projectData = null; 255 256 // Read projectData from the serialized file. 257 if (dataFile.isFile()) 258 { 259 projectData = CoverageDataFileHandler.loadCoverageData(dataFile); 260 } 261 262 if (projectData == null) 263 { 264 // We could not read from the serialized file, so use a new object. 265 System.out.println("Cobertura: Coverage data file " + dataFile.getAbsolutePath() 266 + " either does not exist or is not readable. Creating a new data file."); 267 } 268 269 return projectData; 270 } 271 272 }