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    }