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
040: HasBeenInstrumented {
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: String packageName = classData.getPackageName();
053: PackageData packageData = (PackageData) children
054: .get(packageName);
055: if (packageData == null) {
056: packageData = new PackageData(packageName);
057: // Each key is a package name, stored as an String object.
058: // Each value is information about the package, stored as a PackageData object.
059: this .children.put(packageName, packageData);
060: }
061: packageData.addClassData(classData);
062: this .classes.put(classData.getName(), classData);
063: }
064:
065: public ClassData getClassData(String name) {
066: return (ClassData) this .classes.get(name);
067: }
068:
069: /**
070: * This is called by instrumented bytecode.
071: */
072: public synchronized ClassData getOrCreateClassData(String name) {
073: ClassData classData = (ClassData) this .classes.get(name);
074: if (classData == null) {
075: classData = new ClassData(name);
076: addClassData(classData);
077: }
078: return classData;
079: }
080:
081: public Collection getClasses() {
082: return this .classes.values();
083: }
084:
085: public int getNumberOfClasses() {
086: return this .classes.size();
087: }
088:
089: public int getNumberOfSourceFiles() {
090: return getSourceFiles().size();
091: }
092:
093: public SortedSet getPackages() {
094: return new TreeSet(this .children.values());
095: }
096:
097: public Collection getSourceFiles() {
098: SortedSet sourceFileDatas = new TreeSet();
099: Iterator iter = this .children.values().iterator();
100: while (iter.hasNext()) {
101: PackageData packageData = (PackageData) iter.next();
102: sourceFileDatas.addAll(packageData.getSourceFiles());
103: }
104: return sourceFileDatas;
105: }
106:
107: /**
108: * Get all subpackages of the given package. Includes also specified package if
109: * it exists.
110: *
111: * @param packageName The package name to find subpackages for.
112: * For example, "com.example"
113: * @return A collection containing PackageData objects. Each one
114: * has a name beginning with the given packageName. For
115: * example: "com.example.io", "com.example.io.internal"
116: */
117: public SortedSet getSubPackages(String packageName) {
118: SortedSet subPackages = new TreeSet();
119: Iterator iter = this .children.values().iterator();
120: while (iter.hasNext()) {
121: PackageData packageData = (PackageData) iter.next();
122: if (packageData.getName().startsWith(packageName))
123: subPackages.add(packageData);
124: }
125: return subPackages;
126: }
127:
128: public void merge(CoverageData coverageData) {
129: super .merge(coverageData);
130:
131: ProjectData projectData = (ProjectData) coverageData;
132: for (Iterator iter = projectData.classes.keySet().iterator(); iter
133: .hasNext();) {
134: Object key = iter.next();
135: if (!this .classes.containsKey(key)) {
136: this .classes.put(key, projectData.classes.get(key));
137: }
138: }
139: }
140:
141: /**
142: * Get a reference to a ProjectData object in order to increase the
143: * coverage count for a specific line.
144: *
145: * This method is only called by code that has been instrumented. It
146: * is not called by any of the Cobertura code or ant tasks.
147: */
148: public static ProjectData getGlobalProjectData() {
149: if (globalProjectData != null)
150: return globalProjectData;
151:
152: globalProjectData = new ProjectData();
153: initialize();
154: return globalProjectData;
155: }
156:
157: // TODO: Is it possible to do this as a static initializer?
158: private static void initialize() {
159: // Hack for Tomcat - by saving project data right now we force loading
160: // of classes involved in this process (like ObjectOutputStream)
161: // so that it won't be necessary to load them on JVM shutdown
162: if (System.getProperty("catalina.home") != null) {
163: saveGlobalProjectData();
164:
165: // Force the class loader to load some classes that are
166: // required by our JVM shutdown hook.
167: // TODO: Use ClassLoader.loadClass("whatever"); instead
168: ClassData.class.toString();
169: CoverageData.class.toString();
170: CoverageDataContainer.class.toString();
171: FileLocker.class.toString();
172: HasBeenInstrumented.class.toString();
173: LineData.class.toString();
174: PackageData.class.toString();
175: SourceFileData.class.toString();
176: }
177:
178: // Add a hook to save the data when the JVM exits
179: saveTimer = new SaveTimer();
180: Runtime.getRuntime().addShutdownHook(new Thread(saveTimer));
181:
182: // Possibly also save the coverage data every x seconds?
183: //Timer timer = new Timer(true);
184: //timer.schedule(saveTimer, 100);
185: }
186:
187: public static void saveGlobalProjectData() {
188: ProjectData projectDataToSave = globalProjectData;
189:
190: /*
191: * The next statement is not necessary at the moment, because this method is only called
192: * either at the very beginning or at the very end of a test. If the code is changed
193: * to save more frequently, then this will become important.
194: */
195: globalProjectData = new ProjectData();
196:
197: /*
198: * Now sleep a bit in case there is a thread still holding a reference to the "old"
199: * globalProjectData (now referenced with projectDataToSave).
200: * We want it to finish its updates. I assume 2 seconds is plenty of time.
201: */
202: try {
203: Thread.sleep(1000);
204: } catch (InterruptedException e) {
205: }
206:
207: // Get a file lock
208: File dataFile = CoverageDataFileHandler.getDefaultDataFile();
209: FileLocker fileLocker = new FileLocker(dataFile);
210:
211: // Read the old data, merge our current data into it, then
212: // write a new ser file.
213: if (fileLocker.lock()) {
214: ProjectData datafileProjectData = loadCoverageDataFromDatafile(dataFile);
215: if (datafileProjectData == null) {
216: datafileProjectData = projectDataToSave;
217: } else {
218: datafileProjectData.merge(projectDataToSave);
219: }
220: CoverageDataFileHandler.saveCoverageData(
221: datafileProjectData, dataFile);
222: }
223:
224: // Release the file lock
225: fileLocker.release();
226: }
227:
228: private static ProjectData loadCoverageDataFromDatafile(
229: File dataFile) {
230: ProjectData projectData = null;
231:
232: // Read projectData from the serialized file.
233: if (dataFile.isFile()) {
234: projectData = CoverageDataFileHandler
235: .loadCoverageData(dataFile);
236: }
237:
238: if (projectData == null) {
239: // We could not read from the serialized file, so use a new object.
240: System.out
241: .println("Cobertura: Coverage data file "
242: + dataFile.getAbsolutePath()
243: + " either does not exist or is not readable. Creating a new data file.");
244: }
245:
246: return projectData;
247: }
248:
249: }
|