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