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
025package net.sourceforge.cobertura.coveragedata;
026
027import java.io.IOException;
028import java.io.ObjectInputStream;
029import java.io.Serializable;
030import java.util.ArrayList;
031import java.util.List;
032import java.util.concurrent.locks.Lock;
033import java.util.concurrent.locks.ReentrantLock;
034
035import 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 */
045public 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(int new_hits)
363        {
364                lock.lock();
365                try
366                {
367                        this.hits+=new_hits;
368                }
369                finally
370                {
371                        lock.unlock();
372                }
373        }
374        
375        void touchJump(int jumpNumber, boolean branch,int hits) 
376        {
377                getJumpData(jumpNumber).touchBranch(branch,hits);
378        }
379        
380        void touchSwitch(int switchNumber, int branch,int hits) 
381        {
382                getSwitchData(switchNumber, null).touchBranch(branch,hits);
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}