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    }