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