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 }