001    /*
002     * Cobertura - http://cobertura.sourceforge.net/
003     *
004     * Copyright (C) 2003 jcoverage ltd.
005     * Copyright (C) 2005 Mark Doliner
006     * Copyright (C) 2006 Jiri Mares
007     *
008     * Cobertura is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License as published
010     * by the Free Software Foundation; either version 2 of the License,
011     * or (at your option) any later version.
012     *
013     * Cobertura is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016     * General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with Cobertura; if not, write to the Free Software
020     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
021     * USA
022     */
023    
024    package net.sourceforge.cobertura.coveragedata;
025    
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.HashSet;
030    import java.util.Iterator;
031    import java.util.Map;
032    import java.util.Set;
033    import java.util.SortedSet;
034    import java.util.TreeSet;
035    
036    /**
037     * <p>
038     * ProjectData information is typically serialized to a file. An
039     * instance of this class records coverage information for a single
040     * class that has been instrumented.
041     * </p>
042     *
043     * <p>
044     * This class implements HasBeenInstrumented so that when cobertura
045     * instruments itself, it will omit this class.  It does this to
046     * avoid an infinite recursion problem because instrumented classes
047     * make use of this class.
048     * </p>
049     */
050    
051    public class ClassData extends CoverageDataContainer
052            implements Comparable, HasBeenInstrumented 
053    {
054    
055            private static final long serialVersionUID = 5;
056    
057            /**
058             * Each key is a line number in this class, stored as an Integer object.
059             * Each value is information about the line, stored as a LineData object.
060             */
061            private Map branches = new HashMap();
062    
063            private boolean containsInstrumentationInfo = false;
064    
065            private Set methodNamesAndDescriptors = new HashSet();
066    
067            private String name = null;
068    
069            private String sourceFileName = null;
070    
071            /**
072             * @param name In the format "net.sourceforge.cobertura.coveragedata.ClassData"
073             */
074            public ClassData(String name)
075            {
076                    if (name == null)
077                            throw new IllegalArgumentException(
078                                    "Class name must be specified.");
079                    this.name = name;
080            }
081    
082            public LineData addLine(int lineNumber, String methodName,
083                            String methodDescriptor)
084            {
085                    LineData lineData = getLineData(lineNumber);
086                    if (lineData == null)
087                    {
088                            lineData = new LineData(lineNumber);
089                            // Each key is a line number in this class, stored as an Integer object.
090                            // Each value is information about the line, stored as a LineData object.
091                            children.put(new Integer(lineNumber), lineData);
092                    }
093                    lineData.setMethodNameAndDescriptor(methodName, methodDescriptor);
094          
095                    // methodName and methodDescriptor can be null when cobertura.ser with 
096                    // no line information was loaded (or was not loaded at all).
097                    if( methodName!=null && methodDescriptor!=null)
098                            methodNamesAndDescriptors.add(methodName + methodDescriptor);
099                    return lineData;
100            }
101    
102            /**
103             * This is required because we implement Comparable.
104             */
105            public int compareTo(Object o)
106            {
107                    if (!o.getClass().equals(ClassData.class))
108                            return Integer.MAX_VALUE;
109                    return this.name.compareTo(((ClassData)o).name);
110            }
111    
112            public boolean containsInstrumentationInfo()
113            {
114                    return this.containsInstrumentationInfo;
115            }
116    
117            /**
118             * Returns true if the given object is an instance of the
119             * ClassData class, and it contains the same data as this
120             * class.
121             */
122            public boolean equals(Object obj)
123            {
124                    if (this == obj)
125                            return true;
126                    if ((obj == null) || !(obj.getClass().equals(this.getClass())))
127                            return false;
128    
129                    ClassData classData = (ClassData)obj;
130                    return super.equals(obj)
131                            && this.branches.equals(classData.branches)
132                            && this.methodNamesAndDescriptors
133                                    .equals(classData.methodNamesAndDescriptors)
134                            && this.name.equals(classData.name)
135                            && this.sourceFileName.equals(classData.sourceFileName);
136            }
137    
138            public String getBaseName()
139            {
140                    int lastDot = this.name.lastIndexOf('.');
141                    if (lastDot == -1)
142                    {
143                            return this.name;
144                    }
145                    return this.name.substring(lastDot + 1);
146            }
147    
148            /**
149             * @return The branch coverage rate for a particular method.
150             */
151            public double getBranchCoverageRate(String methodNameAndDescriptor)
152            {
153                    int total = 0;
154                    int covered = 0;
155    
156                    for (Iterator iter = branches.values().iterator(); iter.hasNext();) {
157                            LineData next = (LineData) iter.next();
158                            if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor()))
159                            {
160                                    total += next.getNumberOfValidBranches();
161                                    covered += next.getNumberOfCoveredBranches();
162                            }
163                    }
164                    if (total == 0) return 1.0;
165                    return (double) covered / total;
166            }
167    
168            public Collection getBranches() 
169            {
170                    return Collections.unmodifiableCollection(branches.keySet());
171            }
172    
173            /**
174             * @param lineNumber The source code line number.
175             * @return The coverage of the line
176             */
177            public LineData getLineCoverage(int lineNumber) 
178            {
179                    Integer lineObject = new Integer(lineNumber);
180                    if (!children.containsKey(lineObject)) 
181                    {
182                            return null;
183                    }
184    
185                    return (LineData) children.get(lineObject);
186            }
187    
188            /**
189             * @return The line coverage rate for particular method
190             */
191            public double getLineCoverageRate(String methodNameAndDescriptor) 
192            {
193                    int total = 0;
194                    int hits = 0;
195    
196                    Iterator iter = children.values().iterator();
197                    while (iter.hasNext()) 
198                    {
199                            LineData next = (LineData) iter.next();
200                            if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor())) 
201                            {
202                                    total++;
203                                    if (next.getHits() > 0) {
204                                            hits++;
205                                    }
206                            }
207                    }
208                    if (total == 0) return 1d;
209                    return (double) hits / total;
210            }
211    
212            private LineData getLineData(int lineNumber)
213            {
214                    return (LineData)children.get(new Integer(lineNumber));
215            }
216    
217            public SortedSet getLines()
218            {
219                    return new TreeSet(this.children.values());
220            }
221    
222            public Collection getLines(String methodNameAndDescriptor)
223            {
224                    Collection lines = new HashSet();
225                    Iterator iter = children.values().iterator();
226                    while (iter.hasNext())
227                    {
228                            LineData next = (LineData)iter.next();
229                            if (methodNameAndDescriptor.equals(next.getMethodName()
230                                            + next.getMethodDescriptor()))
231                            {
232                                    lines.add(next);
233                            }
234                    }
235                    return lines;
236            }
237    
238            /**
239             * @return The method name and descriptor of each method found in the
240             *         class represented by this instrumentation.
241             */
242            public Set getMethodNamesAndDescriptors() 
243            {
244                    return methodNamesAndDescriptors;
245            }
246    
247            public String getName() 
248            {
249                    return name;
250            }
251    
252            /**
253             * @return The number of branches in this class.
254             */
255            public int getNumberOfValidBranches() 
256            {
257                    int number = 0;
258                    for (Iterator i = branches.values().iterator(); 
259                            i.hasNext(); 
260                            number += ((LineData) i.next()).getNumberOfValidBranches())
261                            ;
262                    return number;
263            }
264    
265            /**
266             * @see net.sourceforge.cobertura.coveragedata.CoverageData#getNumberOfCoveredBranches()
267             */
268            public int getNumberOfCoveredBranches() 
269            {
270                    int number = 0;
271                    for (Iterator i = branches.values().iterator(); 
272                            i.hasNext(); 
273                            number += ((LineData) i.next()).getNumberOfCoveredBranches())
274                            ;
275                    return number;
276            }
277    
278            public String getPackageName()
279            {
280                    int lastDot = this.name.lastIndexOf('.');
281                    if (lastDot == -1)
282                    {
283                            return "";
284                    }
285                    return this.name.substring(0, lastDot);
286            }
287    
288             /**
289             * Return the name of the file containing this class.  If this
290             * class' sourceFileName has not been set (for whatever reason)
291             * then this method will attempt to infer the name of the source
292             * file using the class name.
293             *
294             * @return The name of the source file, for example
295             *         net/sourceforge/cobertura/coveragedata/ClassData.java
296             */
297            public String getSourceFileName()
298            {
299                    String baseName;
300                    if (sourceFileName != null)
301                            baseName = sourceFileName;
302                    else
303                    {
304                            baseName = getBaseName();
305                            int firstDollarSign = baseName.indexOf('$');
306                            if (firstDollarSign == -1 || firstDollarSign == 0)
307                                    baseName += ".java";
308                            else
309                                    baseName = baseName.substring(0, firstDollarSign)
310                                            + ".java";
311                    }
312    
313                    String packageName = getPackageName();
314                    if (packageName.equals(""))
315                            return baseName;
316                    return packageName.replace('.', '/') + '/' + baseName;
317            }
318    
319            public int hashCode()
320            {
321                    return this.name.hashCode();
322            }
323    
324            /**
325             * @return True if the line contains at least one condition jump (branch)
326             */
327            public boolean hasBranch(int lineNumber) 
328            {
329                    return branches.containsKey(new Integer(lineNumber));
330            }
331    
332            /**
333             * Determine if a given line number is a valid line of code.
334             *
335             * @return True if the line contains executable code.  False
336             *         if the line is empty, or a comment, etc.
337             */
338            public boolean isValidSourceLineNumber(int lineNumber) 
339            {
340                    return children.containsKey(new Integer(lineNumber));
341            }
342    
343            public void addLineJump(int lineNumber, int branchNumber) 
344            {
345                    LineData lineData = getLineData(lineNumber);
346                    if (lineData != null) 
347                    {
348                            lineData.addJump(branchNumber);
349                            this.branches.put(new Integer(lineNumber), lineData);
350                    }
351            }
352    
353            public void addLineSwitch(int lineNumber, int switchNumber, int[] keys) 
354            {
355                    LineData lineData = getLineData(lineNumber);
356                    if (lineData != null) 
357                    {
358                            lineData.addSwitch(switchNumber, keys);
359                            this.branches.put(new Integer(lineNumber), lineData);
360                    }
361            }
362    
363            public void addLineSwitch(int lineNumber, int switchNumber, int min, int max) 
364            {
365                    LineData lineData = getLineData(lineNumber);
366                    if (lineData != null) 
367                    {
368                            lineData.addSwitch(switchNumber, min, max);
369                            this.branches.put(new Integer(lineNumber), lineData);
370                    }
371            }
372    
373            /**
374             * Merge some existing instrumentation with this instrumentation.
375             *
376             * @param coverageData Some existing coverage data.
377             */
378            public void merge(CoverageData coverageData)
379            {
380                    ClassData classData = (ClassData)coverageData;
381    
382                    // If objects contain data for different classes then don't merge
383                    if (!this.getName().equals(classData.getName()))
384                            return;
385    
386                    super.merge(coverageData);
387    
388                    // We can't just call this.branches.putAll(classData.branches);
389                    // Why not?  If we did a putAll, then the LineData objects from
390                    // the coverageData class would overwrite the LineData objects
391                    // that are already in "this.branches"  And we don't need to
392                    // update the LineData objects that are already in this.branches
393                    // because they are shared between this.branches and this.children,
394                    // so the object hit counts will be moved when we called
395                    // super.merge() above.
396                    for (Iterator iter = classData.branches.keySet().iterator(); iter.hasNext();)
397                    {
398                            Object key = iter.next();
399                            if (!this.branches.containsKey(key))
400                            {
401                                    this.branches.put(key, classData.branches.get(key));
402                            }
403                    }
404    
405                    this.containsInstrumentationInfo |= classData.containsInstrumentationInfo;
406                    this.methodNamesAndDescriptors.addAll(classData
407                                    .getMethodNamesAndDescriptors());
408                    if (classData.sourceFileName != null)
409                            this.sourceFileName = classData.sourceFileName;
410                    }
411    
412            public void removeLine(int lineNumber)
413            {
414                    Integer lineObject = new Integer(lineNumber);
415                    children.remove(lineObject);
416                    branches.remove(lineObject);
417            }
418    
419            public void setContainsInstrumentationInfo()
420            {
421                    this.containsInstrumentationInfo = true;
422            }
423    
424            public void setSourceFileName(String sourceFileName)
425            {
426                    this.sourceFileName = sourceFileName;
427            }
428    
429            /**
430             * Increment the number of hits for a particular line of code.
431             *
432             * @param lineNumber the line of code to increment the number of hits.
433             */
434            public void touch(int lineNumber)
435            {
436                    LineData lineData = getLineData(lineNumber);
437                    if (lineData == null)
438                            lineData = addLine(lineNumber, null, null);
439                    lineData.touch();
440            }
441    
442            /**
443             * Increments the number of hits for particular hit counter of particular branch on particular line number.
444             * 
445             * @param lineNumber The line of code where the branch is
446             * @param branchNumber  The branch on the line to change the hit counter
447             * @param branch The hit counter (true or false)
448             */
449            public void touchJump(int lineNumber, int branchNumber, boolean branch) {
450                    LineData lineData = getLineData(lineNumber);
451                    if (lineData == null)
452                            lineData = addLine(lineNumber, null, null);
453                    lineData.touchJump(branchNumber, branch);
454            }
455    
456            /**
457             * Increments the number of hits for particular hit counter of particular switch branch on particular line number.
458             * 
459             * @param lineNumber The line of code where the branch is
460             * @param switchNumber  The switch on the line to change the hit counter
461             * @param branch The hit counter 
462             */
463            public void touchSwitch(int lineNumber, int switchNumber, int branch) {
464                    LineData lineData = getLineData(lineNumber);
465                    if (lineData == null)
466                            lineData = addLine(lineNumber, null, null);
467                    lineData.touchSwitch(switchNumber, branch);
468            }
469    
470    }