001/*
002 * Cobertura - http://cobertura.sourceforge.net/
003 *
004 * Copyright (C) 2006 Jiri Mares
005 *
006 * Cobertura is free software; you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published
008 * by the Free Software Foundation; either version 2 of the License,
009 * or (at your option) any later version.
010 *
011 * Cobertura is distributed in the hope that it will be useful, but
012 * WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with Cobertura; if not, write to the Free Software
018 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019 * USA
020 */
021
022package net.sourceforge.cobertura.coveragedata;
023
024import java.io.IOException;
025import java.io.ObjectInputStream;
026import java.io.Serializable;
027import java.util.Arrays;
028import java.util.concurrent.locks.Lock;
029import java.util.concurrent.locks.ReentrantLock;
030
031/**
032 * <p>
033 * This class implements HasBeenInstrumented so that when cobertura instruments
034 * itself, it will omit this class. It does this to avoid an infinite recursion
035 * problem because instrumented classes make use of this class.
036 * </p>
037 */
038public class SwitchData implements BranchCoverageData, Comparable, Serializable,
039                HasBeenInstrumented
040{
041        private static final long serialVersionUID = 9;
042
043        private transient Lock lock;
044
045        private int switchNumber;
046        
047        private long defaultHits;
048
049        private long[] hits;
050        
051        private int[] keys;
052
053        public SwitchData(int switchNumber, int[] keys)
054        {
055                super();
056                this.switchNumber = switchNumber;
057                defaultHits = 0;
058                hits = new long[keys.length];
059                Arrays.fill(hits, 0);
060                this.keys = new int[keys.length];
061                System.arraycopy(keys, 0, this.keys, 0, keys.length);
062                initLock();
063        }
064
065        public SwitchData(int switchNumber, int min, int max)
066        {
067                super();
068                this.switchNumber = switchNumber;
069                defaultHits = 0;
070                hits = new long[max - min + 1];
071                Arrays.fill(hits, 0);
072                this.keys = new int[max - min + 1];
073                for (int i = 0; min <= max; keys[i++] = min++);
074                initLock();
075        }
076
077        public SwitchData(int switchNumber)
078        {
079                this(switchNumber, new int[0]);
080        }
081        
082        private void initLock()
083        {
084                 lock = new ReentrantLock();
085        }
086
087        public int compareTo(Object o)
088        {
089                if (!o.getClass().equals(SwitchData.class))
090                        return Integer.MAX_VALUE;
091                return this.switchNumber - ((SwitchData) o).switchNumber;
092        }
093        
094        void touchBranch(int branch) 
095        {
096                lock.lock();
097                try
098                {
099                        if (branch == -1)
100                                defaultHits++;
101                        else 
102                        {
103                                if (hits.length <= branch)
104                                {
105                                        long[] old = hits;
106                                        hits = new long[branch + 1];
107                                        System.arraycopy(old, 0, hits, 0, old.length);
108                                        Arrays.fill(hits, old.length, hits.length - 1, 0);
109                                }
110                                hits[branch]++;
111                        }
112                }
113                finally
114                {
115                        lock.unlock();
116                }
117        }
118        
119        public int getSwitchNumber()
120        {
121                return this.switchNumber;
122        }
123
124        public long getHits(int branch)
125        {
126                lock.lock();
127                try
128                {
129                        if (hits.length > branch)
130                                return hits[branch];
131                        return -1;
132                }
133                finally
134                {
135                        lock.unlock();
136                }
137        }
138
139        public long getDefaultHits()
140        {
141                lock.lock();
142                try
143                {
144                        return defaultHits;
145                }
146                finally
147                {
148                        lock.unlock();
149                }
150        }
151
152        public double getBranchCoverageRate()
153        {
154                lock.lock();
155                try
156                {
157                        int branches = hits.length + 1;
158                        int hit = (defaultHits > 0) ? 1 : 0;
159                        for (int i = hits.length - 1; i >= 0; hit += ((hits[i--] > 0) ? 1 : 0));
160                        return ((double) hit) / branches;
161                }
162                finally
163                {
164                        lock.unlock();
165                }
166        }
167
168        public boolean equals(Object obj)
169        {
170                if (this == obj)
171                        return true;
172                if ((obj == null) || !(obj.getClass().equals(this.getClass())))
173                        return false;
174
175                SwitchData switchData = (SwitchData) obj;
176                getBothLocks(switchData);
177                try
178                {
179                        return (this.defaultHits == switchData.defaultHits)
180                                        && (Arrays.equals(this.hits, switchData.hits))
181                                        && (this.switchNumber == switchData.switchNumber);
182                }
183                finally
184                {
185                        lock.unlock();
186                        switchData.lock.unlock();
187                }
188        }
189
190        public int hashCode()
191        {
192                return this.switchNumber;
193        }
194
195        public int getNumberOfCoveredBranches()
196        {
197                lock.lock();
198                try
199                {
200                        int ret = (defaultHits > 0) ? 1 : 0;
201                        for (int i = hits.length -1; i >= 0;i--) 
202                        {
203                                if (hits[i] > 0) ret++;
204                        }
205                        return ret;
206                }
207                finally
208                {
209                        lock.unlock();
210                }
211        }
212
213        public int getNumberOfValidBranches()
214        {
215                lock.lock();
216                try
217                {
218                        return hits.length + 1;
219                }
220                finally
221                {
222                        lock.unlock();
223                }
224        }
225
226        public void merge(BranchCoverageData coverageData)
227        {
228                SwitchData switchData = (SwitchData) coverageData;
229                getBothLocks(switchData);
230                try
231                {
232                        defaultHits += switchData.defaultHits;
233                        for (int i = Math.min(hits.length, switchData.hits.length) - 1; i >= 0; i--)
234                                hits[i] += switchData.hits[i];
235                        if (switchData.hits.length > hits.length)
236                        {
237                                long[] old = hits;
238                                hits = new long[switchData.hits.length];
239                                System.arraycopy(old, 0, hits, 0, old.length);
240                                System.arraycopy(switchData.hits, old.length, hits, old.length, hits.length - old.length);
241                        }
242                        if ((this.keys.length == 0) && (switchData.keys.length > 0))
243                                this.keys = switchData.keys;
244                }
245                finally
246                {
247                        lock.unlock();
248                        switchData.lock.unlock();
249                }
250
251        }
252
253        private void getBothLocks(SwitchData other) {
254                /*
255                 * To prevent deadlock, we need to get both locks or none at all.
256                 * 
257                 * When this method returns, the thread will have both locks.
258                 * Make sure you unlock them!
259                 */
260                boolean myLock = false;
261                boolean otherLock = false;
262                while ((!myLock) || (!otherLock))
263                {
264                        try
265                        {
266                                myLock = lock.tryLock();
267                                otherLock = other.lock.tryLock();
268                        }
269                        finally
270                        {
271                                if ((!myLock) || (!otherLock))
272                                {
273                                        //could not obtain both locks - so unlock the one we got.
274                                        if (myLock)
275                                        {
276                                                lock.unlock();
277                                        }
278                                        if (otherLock)
279                                        {
280                                                other.lock.unlock();
281                                        }
282                                        //do a yield so the other threads will get to work.
283                                        Thread.yield();
284                                }
285                        }
286                }
287        }
288
289        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
290        {
291                in.defaultReadObject();
292                initLock();
293        }
294}