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, 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}