001    /*
002     * Cobertura - http://cobertura.sourceforge.net/
003     *
004     * Copyright (C) 2003 jcoverage ltd.
005     * Copyright (C) 2005 Mark Doliner
006     * Copyright (C) 2005 Mark Sinke
007     * Copyright (C) 2006 Jiri Mares
008     *
009     * Cobertura is free software; you can redistribute it and/or modify
010     * it under the terms of the GNU General Public License as published
011     * by the Free Software Foundation; either version 2 of the License,
012     * or (at your option) any later version.
013     *
014     * Cobertura is distributed in the hope that it will be useful, but
015     * WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * General Public License for more details.
018     *
019     * You should have received a copy of the GNU General Public License
020     * along with Cobertura; if not, write to the Free Software
021     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
022     * USA
023     */
024    
025    package net.sourceforge.cobertura.coveragedata;
026    
027    import java.io.IOException;
028    import java.io.ObjectInputStream;
029    import java.io.Serializable;
030    import java.util.ArrayList;
031    import java.util.List;
032    import java.util.concurrent.locks.Lock;
033    import java.util.concurrent.locks.ReentrantLock;
034    
035    import net.sourceforge.cobertura.util.StringUtil;
036    
037    /**
038     * <p>
039     * This class implements HasBeenInstrumented so that when cobertura
040     * instruments itself, it will omit this class.  It does this to
041     * avoid an infinite recursion problem because instrumented classes
042     * make use of this class.
043     * </p>
044     */
045    public class LineData
046                    implements Comparable, CoverageData, HasBeenInstrumented, Serializable
047    {
048            private static final long serialVersionUID = 4;
049    
050            private transient Lock lock;
051    
052            private long hits;
053            private List jumps;
054            private List switches;
055            private final int lineNumber;
056            private String methodDescriptor;
057            private String methodName;
058    
059            LineData(int lineNumber)
060            {
061                    this(lineNumber, null, null);
062            }
063    
064            LineData(int lineNumber, String methodName, String methodDescriptor)
065            {
066                    this.hits = 0;
067                    this.jumps = null;
068                    this.lineNumber = lineNumber;
069                    this.methodName = methodName;
070                    this.methodDescriptor = methodDescriptor;
071                    initLock();
072            }
073            
074            private void initLock()
075            {
076                     lock = new ReentrantLock();
077            }
078    
079            /**
080             * This is required because we implement Comparable.
081             */
082            public int compareTo(Object o)
083            {
084                    if (!o.getClass().equals(LineData.class))
085                            return Integer.MAX_VALUE;
086                    return this.lineNumber - ((LineData)o).lineNumber;
087            }
088    
089            public boolean equals(Object obj)
090            {
091                    if (this == obj)
092                            return true;
093                    if ((obj == null) || !(obj.getClass().equals(this.getClass())))
094                            return false;
095    
096                    LineData lineData = (LineData)obj;
097                    getBothLocks(lineData);
098                    try
099                    {
100                            return (this.hits == lineData.hits)
101                                            && ((this.jumps == lineData.jumps) || ((this.jumps != null) && (this.jumps.equals(lineData.jumps))))
102                                            && ((this.switches == lineData.switches) || ((this.switches != null) && (this.switches.equals(lineData.switches))))
103                                            && (this.lineNumber == lineData.lineNumber)
104                                            && (this.methodDescriptor.equals(lineData.methodDescriptor))
105                                            && (this.methodName.equals(lineData.methodName));
106                    }
107                    finally
108                    {
109                            lock.unlock();
110                            lineData.lock.unlock();
111                    }
112            }
113    
114            public double getBranchCoverageRate()
115            {
116                    if (getNumberOfValidBranches() == 0)
117                            return 1d;
118                    lock.lock();
119                    try
120                    {
121                            return ((double) getNumberOfCoveredBranches()) / getNumberOfValidBranches();
122                    }
123                    finally
124                    {
125                            lock.unlock();
126                    }
127            }
128    
129            public String getConditionCoverage()
130            {
131                    StringBuffer ret = new StringBuffer();
132                    if (getNumberOfValidBranches() == 0)
133                    {
134                            ret.append(StringUtil.getPercentValue(1.0));
135                    }
136                    else
137                    {
138                            lock.lock();
139                            try
140                            {
141                                    ret.append(StringUtil.getPercentValue(getBranchCoverageRate()));
142                                    ret.append(" (").append(getNumberOfCoveredBranches()).append("/").append(getNumberOfValidBranches()).append(")");
143                            }
144                            finally
145                            {
146                                    lock.unlock();
147                            }
148                    }
149                    return ret.toString();
150            }
151            
152            public long getHits()
153            {
154                    lock.lock();
155                    try
156                    {
157                            return hits;
158                    }
159                    finally
160                    {
161                            lock.unlock();
162                    }
163            }
164    
165            public boolean isCovered()
166            {
167                    lock.lock();
168                    try
169                    {
170                            return (getHits() > 0) && ((getNumberOfValidBranches() == 0) || ((1.0 - getBranchCoverageRate()) < 0.0001));
171                    }
172                    finally
173                    {
174                            lock.unlock();
175                    }
176            }
177            
178            public double getLineCoverageRate()
179            {
180                    return (getHits() > 0) ? 1 : 0;
181            }
182    
183            public int getLineNumber()
184            {
185                    return lineNumber;
186            }
187    
188            public String getMethodDescriptor()
189            {
190                    lock.lock();
191                    try
192                    {
193                            return methodDescriptor;
194                    }
195                    finally
196                    {
197                            lock.unlock();
198                    }
199            }
200    
201            public String getMethodName()
202            {
203                    lock.lock();
204                    try
205                    {
206                            return methodName;
207                    }
208                    finally
209                    {
210                            lock.unlock();
211                    }
212            }
213    
214            /**
215             * @see net.sourceforge.cobertura.coveragedata.CoverageData#getNumberOfCoveredBranches()
216             */
217            /*public int getNumberOfCoveredBranches()
218            {
219                    if (this.branches == null) 
220                            return 0;
221                    int covered = 0;
222                    for (Iterator i = this.branches.iterator(); i.hasNext(); covered += ((BranchData) i.next()).getNumberOfCoveredBranches());
223                    return covered;
224            }*/
225    
226            public int getNumberOfCoveredLines()
227            {
228                    return (getHits() > 0) ? 1 : 0;
229            }
230    
231            public int getNumberOfValidBranches()
232            {
233                    int ret = 0;
234                    lock.lock();
235                    try
236                    {
237                            if (jumps != null)
238                                    for (int i = jumps.size() - 1; i >= 0; i--)
239                                            ret += ((JumpData) jumps.get(i)).getNumberOfValidBranches();
240                            if (switches != null)
241                                    for (int i = switches.size() - 1; i >= 0; i--)
242                                            ret += ((SwitchData) switches.get(i)).getNumberOfValidBranches();
243                            return ret;
244                    }
245                    finally
246                    {
247                            lock.unlock();
248                    }
249            }
250            
251            public int getNumberOfCoveredBranches()
252            {
253                    int ret = 0;
254                    lock.lock();
255                    try
256                    {
257                            if (jumps != null)
258                                    for (int i = jumps.size() - 1; i >= 0; i--)
259                                            ret += ((JumpData) jumps.get(i)).getNumberOfCoveredBranches();
260                            if (switches != null)
261                                    for (int i = switches.size() - 1; i >= 0; i--)
262                                            ret += ((SwitchData) switches.get(i)).getNumberOfCoveredBranches();
263                            return ret;
264                    }
265                    finally
266                    {
267                            lock.unlock();
268                    }
269            }
270    
271            public int getNumberOfValidLines()
272            {
273                    return 1;
274            }
275    
276            public int hashCode()
277            {
278                    return this.lineNumber;
279            }
280    
281            public boolean hasBranch()
282            {
283                    lock.lock();
284                    try
285                    {
286                            return (jumps != null) || (switches != null);
287                    }
288                    finally
289                    {
290                            lock.unlock();
291                    }
292            }
293    
294            public void merge(CoverageData coverageData)
295            {
296                    LineData lineData = (LineData)coverageData;
297                    getBothLocks(lineData);
298                    try
299                    {
300                            this.hits += lineData.hits;
301                            if (lineData.jumps != null)
302                                    if (this.jumps == null) 
303                                            this.jumps = lineData.jumps;
304                                    else
305                                    {
306                                            for (int i = Math.min(this.jumps.size(), lineData.jumps.size()) - 1; i >= 0; i--)
307                                                    ((JumpData) this.jumps.get(i)).merge((JumpData) lineData.jumps.get(i));
308                                            for (int i = Math.min(this.jumps.size(), lineData.jumps.size()); i < lineData.jumps.size(); i++) 
309                                                    this.jumps.add(lineData.jumps.get(i));
310                                    }
311                            if (lineData.switches != null)
312                                    if (this.switches == null) 
313                                            this.switches = lineData.switches;
314                                    else
315                                    {
316                                            for (int i = Math.min(this.switches.size(), lineData.switches.size()) - 1; i >= 0; i--)
317                                                    ((SwitchData) this.switches.get(i)).merge((SwitchData) lineData.switches.get(i));
318                                            for (int i = Math.min(this.switches.size(), lineData.switches.size()); i < lineData.switches.size(); i++) 
319                                                    this.switches.add(lineData.switches.get(i));
320                                    }
321                            if (lineData.methodName != null)
322                                    this.methodName = lineData.methodName;
323                            if (lineData.methodDescriptor != null)
324                                    this.methodDescriptor = lineData.methodDescriptor;
325                    }
326                    finally
327                    {
328                            lock.unlock();
329                            lineData.lock.unlock();
330                    }
331            }
332    
333            void addJump(int jumpNumber)
334            {
335                    getJumpData(jumpNumber);
336            }
337    
338            void addSwitch(int switchNumber, int[] keys)
339            {
340                    getSwitchData(switchNumber, new SwitchData(switchNumber, keys));
341            }
342    
343            void addSwitch(int switchNumber, int min, int max)
344            {
345                    getSwitchData(switchNumber, new SwitchData(switchNumber, min, max));
346            }
347    
348            void setMethodNameAndDescriptor(String name, String descriptor)
349            {
350                    lock.lock();
351                    try
352                    {
353                            this.methodName = name;
354                            this.methodDescriptor = descriptor;
355                    }
356                    finally
357                    {
358                            lock.unlock();
359                    }
360            }
361    
362            void touch()
363            {
364                    lock.lock();
365                    try
366                    {
367                            this.hits++;
368                    }
369                    finally
370                    {
371                            lock.unlock();
372                    }
373            }
374            
375            void touchJump(int jumpNumber, boolean branch) 
376            {
377                    getJumpData(jumpNumber).touchBranch(branch);
378            }
379            
380            void touchSwitch(int switchNumber, int branch) 
381            {
382                    getSwitchData(switchNumber, null).touchBranch(branch);
383            }
384            
385            public int getConditionSize() {
386                    lock.lock();
387                    try
388                    {
389                            return ((jumps == null) ? 0 : jumps.size()) + ((switches == null) ? 0 :switches.size());
390                    }
391                    finally
392                    {
393                            lock.unlock();
394                    }
395            }
396            
397            public Object getConditionData(int index)
398            {
399                    Object branchData = null;
400                    lock.lock();
401                    try
402                    {
403                            int jumpsSize = (jumps == null) ? 0 : jumps.size();
404                            int switchesSize = (switches == null) ? 0 :switches.size();
405                            if (index < jumpsSize) 
406                            {
407                                    branchData = jumps.get(index);
408                            }
409                            else if (index < jumpsSize + switchesSize)
410                            {
411                                    branchData = switches.get(index - jumpsSize);
412                            }
413                            return branchData;
414                    }
415                    finally
416                    {
417                            lock.unlock();
418                    }
419            }
420            
421            public String getConditionCoverage(int index) {
422                    Object branchData = getConditionData(index);
423                    if (branchData == null)
424                    {
425                            return StringUtil.getPercentValue(1.0);
426                    } 
427                    else if (branchData instanceof JumpData) 
428                    {
429                            JumpData jumpData = (JumpData) branchData;
430                            return StringUtil.getPercentValue(jumpData.getBranchCoverageRate());
431                    }
432                    else
433                    {
434                            SwitchData switchData = (SwitchData) branchData;
435                            return StringUtil.getPercentValue(switchData.getBranchCoverageRate());
436    
437                    }
438            }
439            
440            JumpData getJumpData(int jumpNumber) 
441            {
442                    lock.lock();
443                    try
444                    {
445                            if (jumps == null) 
446                            {
447                                    jumps = new ArrayList();
448                            }
449                            if (jumps.size() <= jumpNumber) 
450                            {
451                                    for (int i = jumps.size(); i <= jumpNumber; jumps.add(new JumpData(i++)));
452                            }
453                            return (JumpData) jumps.get(jumpNumber);
454                    }
455                    finally
456                    {
457                            lock.unlock();
458                    }
459            }
460            
461            SwitchData getSwitchData(int switchNumber, SwitchData data) 
462            {
463                    lock.lock();
464                    try
465                    {
466                            if (switches == null) 
467                            {
468                                    switches = new ArrayList();
469                            }
470                            if (switches.size() < switchNumber) 
471                            {
472                                    for (int i = switches.size(); i < switchNumber; switches.add(new SwitchData(i++)));
473                            }
474                            if (switches.size() == switchNumber) 
475                                    if (data != null)
476                                            switches.add(data);
477                                    else
478                                            switches.add(new SwitchData(switchNumber));
479                            return (SwitchData) switches.get(switchNumber);
480                    }
481                    finally
482                    {
483                            lock.unlock();
484                    }
485            }
486    
487            private void getBothLocks(LineData other) {
488                    /*
489                     * To prevent deadlock, we need to get both locks or none at all.
490                     * 
491                     * When this method returns, the thread will have both locks.
492                     * Make sure you unlock them!
493                     */
494                    boolean myLock = false;
495                    boolean otherLock = false;
496                    while ((!myLock) || (!otherLock))
497                    {
498                            try
499                            {
500                                    myLock = lock.tryLock();
501                                    otherLock = other.lock.tryLock();
502                            }
503                            finally
504                            {
505                                    if ((!myLock) || (!otherLock))
506                                    {
507                                            //could not obtain both locks - so unlock the one we got.
508                                            if (myLock)
509                                            {
510                                                    lock.unlock();
511                                            }
512                                            if (otherLock)
513                                            {
514                                                    other.lock.unlock();
515                                            }
516                                            //do a yield so the other threads will get to work.
517                                            Thread.yield();
518                                    }
519                            }
520                    }
521            }
522    
523            private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
524            {
525                    in.defaultReadObject();
526                    initLock();
527            }
528    }