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                    lock.lock();
086                    try
087                    {
088                            LineData lineData = getLineData(lineNumber);
089                            if (lineData == null)
090                            {
091                                    lineData = new LineData(lineNumber);
092                                    // Each key is a line number in this class, stored as an Integer object.
093                                    // Each value is information about the line, stored as a LineData object.
094                                    children.put(new Integer(lineNumber), lineData);
095                            }
096                            lineData.setMethodNameAndDescriptor(methodName, methodDescriptor);
097                  
098                            // methodName and methodDescriptor can be null when cobertura.ser with 
099                            // no line information was loaded (or was not loaded at all).
100                            if( methodName!=null && methodDescriptor!=null)
101                                    methodNamesAndDescriptors.add(methodName + methodDescriptor);
102                            return lineData;
103                    }
104                    finally
105                    {
106                            lock.unlock();
107                    }
108            }
109    
110            /**
111             * This is required because we implement Comparable.
112             */
113            public int compareTo(Object o)
114            {
115                    if (!o.getClass().equals(ClassData.class))
116                            return Integer.MAX_VALUE;
117                    return this.name.compareTo(((ClassData)o).name);
118            }
119    
120            public boolean containsInstrumentationInfo()
121            {
122                    lock.lock();
123                    try
124                    {
125                            return this.containsInstrumentationInfo;
126                    }
127                    finally
128                    {
129                            lock.unlock();
130                    }
131            }
132    
133            /**
134             * Returns true if the given object is an instance of the
135             * ClassData class, and it contains the same data as this
136             * class.
137             */
138            public boolean equals(Object obj)
139            {
140                    if (this == obj)
141                            return true;
142                    if ((obj == null) || !(obj.getClass().equals(this.getClass())))
143                            return false;
144    
145                    ClassData classData = (ClassData)obj;
146                    getBothLocks(classData);
147                    try
148                    {
149                            return super.equals(obj)
150                                    && this.branches.equals(classData.branches)
151                                    && this.methodNamesAndDescriptors
152                                            .equals(classData.methodNamesAndDescriptors)
153                                    && this.name.equals(classData.name)
154                                    && this.sourceFileName.equals(classData.sourceFileName);
155                    }
156                    finally
157                    {
158                            lock.unlock();
159                            classData.lock.unlock();
160                    }
161            }
162    
163            public String getBaseName()
164            {
165                    int lastDot = this.name.lastIndexOf('.');
166                    if (lastDot == -1)
167                    {
168                            return this.name;
169                    }
170                    return this.name.substring(lastDot + 1);
171            }
172    
173            /**
174             * @return The branch coverage rate for a particular method.
175             */
176            public double getBranchCoverageRate(String methodNameAndDescriptor)
177            {
178                    int total = 0;
179                    int covered = 0;
180    
181                    lock.lock();
182                    try
183                    {
184                            for (Iterator iter = branches.values().iterator(); iter.hasNext();) {
185                                    LineData next = (LineData) iter.next();
186                                    if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor()))
187                                    {
188                                            total += next.getNumberOfValidBranches();
189                                            covered += next.getNumberOfCoveredBranches();
190                                    }
191                            }
192                            if (total == 0) return 1.0;
193                            return (double) covered / total;
194                    }
195                    finally
196                    {
197                            lock.unlock();
198                    }
199            }
200    
201            public Collection getBranches() 
202            {
203                    lock.lock();
204                    try
205                    {
206                            return Collections.unmodifiableCollection(branches.keySet());
207                    }
208                    finally
209                    {
210                            lock.unlock();
211                    }
212            }
213    
214            /**
215             * @param lineNumber The source code line number.
216             * @return The coverage of the line
217             */
218            public LineData getLineCoverage(int lineNumber) 
219            {
220                    Integer lineObject = new Integer(lineNumber);
221                    lock.lock();
222                    try
223                    {
224                            if (!children.containsKey(lineObject)) 
225                            {
226                                    return null;
227                            }
228            
229                            return (LineData) children.get(lineObject);
230                    }
231                    finally
232                    {
233                            lock.unlock();
234                    }
235            }
236    
237            /**
238             * @return The line coverage rate for particular method
239             */
240            public double getLineCoverageRate(String methodNameAndDescriptor) 
241            {
242                    int total = 0;
243                    int hits = 0;
244    
245                    lock.lock();
246                    try
247                    {
248                            Iterator iter = children.values().iterator();
249                            while (iter.hasNext()) 
250                            {
251                                    LineData next = (LineData) iter.next();
252                                    if (methodNameAndDescriptor.equals(next.getMethodName() + next.getMethodDescriptor())) 
253                                    {
254                                            total++;
255                                            if (next.getHits() > 0) {
256                                                    hits++;
257                                            }
258                                    }
259                            }
260                            if (total == 0) return 1d;
261                            return (double) hits / total;
262                    }
263                    finally
264                    {
265                            lock.unlock();
266                    }
267            }
268    
269            private LineData getLineData(int lineNumber)
270            {
271                    lock.lock();
272                    try
273                    {
274                            return (LineData)children.get(Integer.valueOf(lineNumber));
275                    }
276                    finally
277                    {
278                            lock.unlock();
279                    }
280            }
281    
282            public SortedSet getLines()
283            {
284                    lock.lock();
285                    try
286                    {
287                            return new TreeSet(this.children.values());
288                    }
289                    finally
290                    {
291                            lock.unlock();
292                    }
293            }
294    
295            public Collection getLines(String methodNameAndDescriptor)
296            {
297                    Collection lines = new HashSet();
298                    lock.lock();
299                    try
300                    {
301                            Iterator iter = children.values().iterator();
302                            while (iter.hasNext())
303                            {
304                                    LineData next = (LineData)iter.next();
305                                    if (methodNameAndDescriptor.equals(next.getMethodName()
306                                                    + next.getMethodDescriptor()))
307                                    {
308                                            lines.add(next);
309                                    }
310                            }
311                            return lines;
312                    }
313                    finally
314                    {
315                            lock.unlock();
316                    }
317            }
318    
319            /**
320             * @return The method name and descriptor of each method found in the
321             *         class represented by this instrumentation.
322             */
323            public Set getMethodNamesAndDescriptors() 
324            {
325                    lock.lock();
326                    try
327                    {
328                            return methodNamesAndDescriptors;
329                    }
330                    finally
331                    {
332                            lock.unlock();
333                    }
334            }
335    
336            public String getName() 
337            {
338                    return name;
339            }
340    
341            /**
342             * @return The number of branches in this class.
343             */
344            public int getNumberOfValidBranches() 
345            {
346                    int number = 0;
347                    lock.lock();
348                    try
349                    {
350                            for (Iterator i = branches.values().iterator(); 
351                                    i.hasNext(); 
352                                    number += ((LineData) i.next()).getNumberOfValidBranches())
353                                    ;
354                            return number;
355                    }
356                    finally
357                    {
358                            lock.unlock();
359                    }
360            }
361    
362            /**
363             * @see net.sourceforge.cobertura.coveragedata.CoverageData#getNumberOfCoveredBranches()
364             */
365            public int getNumberOfCoveredBranches() 
366            {
367                    int number = 0;
368                    lock.lock();
369                    try
370                    {
371                            for (Iterator i = branches.values().iterator(); 
372                                    i.hasNext(); 
373                                    number += ((LineData) i.next()).getNumberOfCoveredBranches())
374                                    ;
375                            return number;
376                    }
377                    finally
378                    {
379                            lock.unlock();
380                    }
381            }
382    
383            public String getPackageName()
384            {
385                    int lastDot = this.name.lastIndexOf('.');
386                    if (lastDot == -1)
387                    {
388                            return "";
389                    }
390                    return this.name.substring(0, lastDot);
391            }
392    
393             /**
394             * Return the name of the file containing this class.  If this
395             * class' sourceFileName has not been set (for whatever reason)
396             * then this method will attempt to infer the name of the source
397             * file using the class name.
398             *
399             * @return The name of the source file, for example
400             *         net/sourceforge/cobertura/coveragedata/ClassData.java
401             */
402            public String getSourceFileName()
403            {
404                    String baseName;
405                    lock.lock();
406                    try
407                    {
408                            if (sourceFileName != null)
409                                    baseName = sourceFileName;
410                            else
411                            {
412                                    baseName = getBaseName();
413                                    int firstDollarSign = baseName.indexOf('$');
414                                    if (firstDollarSign == -1 || firstDollarSign == 0)
415                                            baseName += ".java";
416                                    else
417                                            baseName = baseName.substring(0, firstDollarSign)
418                                                    + ".java";
419                            }
420            
421                            String packageName = getPackageName();
422                            if (packageName.equals(""))
423                                    return baseName;
424                            return packageName.replace('.', '/') + '/' + baseName;
425                    }
426                    finally
427                    {
428                            lock.unlock();
429                    }
430            }
431    
432            public int hashCode()
433            {
434                    return this.name.hashCode();
435            }
436    
437            /**
438             * @return True if the line contains at least one condition jump (branch)
439             */
440            public boolean hasBranch(int lineNumber) 
441            {
442                    lock.lock();
443                    try
444                    {
445                            return branches.containsKey(Integer.valueOf(lineNumber));
446                    }
447                    finally
448                    {
449                            lock.unlock();
450                    }
451            }
452    
453            /**
454             * Determine if a given line number is a valid line of code.
455             *
456             * @return True if the line contains executable code.  False
457             *         if the line is empty, or a comment, etc.
458             */
459            public boolean isValidSourceLineNumber(int lineNumber) 
460            {
461                    lock.lock();
462                    try
463                    {
464                            return children.containsKey(Integer.valueOf(lineNumber));
465                    }
466                    finally
467                    {
468                            lock.unlock();
469                    }
470            }
471    
472            public void addLineJump(int lineNumber, int branchNumber) 
473            {
474                    lock.lock();
475                    try
476                    {
477                            LineData lineData = getLineData(lineNumber);
478                            if (lineData != null) 
479                            {
480                                    lineData.addJump(branchNumber);
481                                    this.branches.put(Integer.valueOf(lineNumber), lineData);
482                            }
483                    }
484                    finally
485                    {
486                            lock.unlock();
487                    }
488            }
489    
490            public void addLineSwitch(int lineNumber, int switchNumber, int[] keys) 
491            {
492                    lock.lock();
493                    try
494                    {
495                            LineData lineData = getLineData(lineNumber);
496                            if (lineData != null) 
497                            {
498                                    lineData.addSwitch(switchNumber, keys);
499                                    this.branches.put(Integer.valueOf(lineNumber), lineData);
500                            }
501                    }
502                    finally
503                    {
504                            lock.unlock();
505                    }
506            }
507    
508            public void addLineSwitch(int lineNumber, int switchNumber, int min, int max) 
509            {
510                    lock.lock();
511                    try
512                    {
513                            LineData lineData = getLineData(lineNumber);
514                            if (lineData != null) 
515                            {
516                                    lineData.addSwitch(switchNumber, min, max);
517                                    this.branches.put(Integer.valueOf(lineNumber), lineData);
518                            }
519                    }
520                    finally
521                    {
522                            lock.unlock();
523                    }
524            }
525    
526            /**
527             * Merge some existing instrumentation with this instrumentation.
528             *
529             * @param coverageData Some existing coverage data.
530             */
531            public void merge(CoverageData coverageData)
532            {
533                    ClassData classData = (ClassData)coverageData;
534    
535                    // If objects contain data for different classes then don't merge
536                    if (!this.getName().equals(classData.getName()))
537                            return;
538    
539                    getBothLocks(classData);
540                    try
541                    {
542                            super.merge(coverageData);
543            
544                            // We can't just call this.branches.putAll(classData.branches);
545                            // Why not?  If we did a putAll, then the LineData objects from
546                            // the coverageData class would overwrite the LineData objects
547                            // that are already in "this.branches"  And we don't need to
548                            // update the LineData objects that are already in this.branches
549                            // because they are shared between this.branches and this.children,
550                            // so the object hit counts will be moved when we called
551                            // super.merge() above.
552                            for (Iterator iter = classData.branches.keySet().iterator(); iter.hasNext();)
553                            {
554                                    Object key = iter.next();
555                                    if (!this.branches.containsKey(key))
556                                    {
557                                            this.branches.put(key, classData.branches.get(key));
558                                    }
559                            }
560            
561                            this.containsInstrumentationInfo |= classData.containsInstrumentationInfo;
562                            this.methodNamesAndDescriptors.addAll(classData
563                                            .getMethodNamesAndDescriptors());
564                            if (classData.sourceFileName != null)
565                                    this.sourceFileName = classData.sourceFileName;
566                    }
567                    finally
568                    {
569                            lock.unlock();
570                            classData.lock.unlock();
571                    }
572            }
573    
574            public void removeLine(int lineNumber)
575            {
576                    Integer lineObject = Integer.valueOf(lineNumber);
577                    lock.lock();
578                    try
579                    {
580                            children.remove(lineObject);
581                            branches.remove(lineObject);
582                    }
583                    finally
584                    {
585                            lock.unlock();
586                    }
587            }
588    
589            public void setContainsInstrumentationInfo()
590            {
591                    lock.lock();
592                    try
593                    {
594                            this.containsInstrumentationInfo = true;
595                    }
596                    finally
597                    {
598                            lock.unlock();
599                    }
600            }
601    
602            public void setSourceFileName(String sourceFileName)
603            {
604                    lock.lock();
605                    try
606                    {
607                            this.sourceFileName = sourceFileName;
608                    }
609                    finally
610                    {
611                            lock.unlock();
612                    }
613            }
614    
615            /**
616             * Increment the number of hits for a particular line of code.
617             *
618             * @param lineNumber the line of code to increment the number of hits.
619             */
620            public void touch(int lineNumber)
621            {
622                    lock.lock();
623                    try
624                    {
625                            LineData lineData = getLineData(lineNumber);
626                            if (lineData == null)
627                                    lineData = addLine(lineNumber, null, null);
628                            lineData.touch();
629                    }
630                    finally
631                    {
632                            lock.unlock();
633                    }
634            }
635    
636            /**
637             * Increments the number of hits for particular hit counter of particular branch on particular line number.
638             * 
639             * @param lineNumber The line of code where the branch is
640             * @param branchNumber  The branch on the line to change the hit counter
641             * @param branch The hit counter (true or false)
642             */
643            public void touchJump(int lineNumber, int branchNumber, boolean branch) {
644                    lock.lock();
645                    try
646                    {
647                            LineData lineData = getLineData(lineNumber);
648                            if (lineData == null)
649                                    lineData = addLine(lineNumber, null, null);
650                            lineData.touchJump(branchNumber, branch);
651                    }
652                    finally
653                    {
654                            lock.unlock();
655                    }
656            }
657    
658            /**
659             * Increments the number of hits for particular hit counter of particular switch branch on particular line number.
660             * 
661             * @param lineNumber The line of code where the branch is
662             * @param switchNumber  The switch on the line to change the hit counter
663             * @param branch The hit counter 
664             */
665            public void touchSwitch(int lineNumber, int switchNumber, int branch) {
666                    lock.lock();
667                    try
668                    {
669                            LineData lineData = getLineData(lineNumber);
670                            if (lineData == null)
671                                    lineData = addLine(lineNumber, null, null);
672                            lineData.touchSwitch(switchNumber, branch);
673                    }
674                    finally
675                    {
676                            lock.unlock();
677                    }
678            }
679    
680    }