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
024package net.sourceforge.cobertura.coveragedata;
025
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.Map;
032import java.util.Set;
033import java.util.SortedSet;
034import 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
051public 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}