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