/*
* Cobertura - http://cobertura.sourceforge.net/
*
* Copyright (C) 2006 Jiri Mares
*
* Cobertura is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* Cobertura is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cobertura; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package net.sourceforge.cobertura.coveragedata;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* <p>
* This class implements HasBeenInstrumented so that when cobertura instruments
* itself, it will omit this class. It does this to avoid an infinite recursion
* problem because instrumented classes make use of this class.
* </p>
*/
public class SwitchData implements BranchCoverageData, Comparable, Serializable,
HasBeenInstrumented {
private static final long serialVersionUID = 9;
private transient Lock lock;
private int switchNumber;
private long defaultHits;
private long[] hits;
private int[] keys;
public SwitchData(int switchNumber, int[] keys) {
super();
this.switchNumber = switchNumber;
defaultHits = 0;
hits = new long[keys.length];
Arrays.fill(hits, 0);
this.keys = new int[keys.length];
System.arraycopy(keys, 0, this.keys, 0, keys.length);
initLock();
}
public SwitchData(int switchNumber, int min, int max) {
super();
this.switchNumber = switchNumber;
defaultHits = 0;
hits = new long[max - min + 1];
Arrays.fill(hits, 0);
this.keys = new int[max - min + 1];
for (int i = 0; min <= max; keys[i++] = min++) ;
initLock();
}
public SwitchData(int switchNumber) {
this(switchNumber, new int[0]);
}
private void initLock() {
lock = new ReentrantLock();
}
public int compareTo(Object o) {
if (!o.getClass().equals(SwitchData.class))
return Integer.MAX_VALUE;
return this.switchNumber - ((SwitchData) o).switchNumber;
}
void touchBranch(int branch, int new_hits) {
lock.lock();
try {
if (branch == -1)
defaultHits++;
else {
if (hits.length <= branch) {
long[] old = hits;
hits = new long[branch + 1];
System.arraycopy(old, 0, hits, 0, old.length);
Arrays.fill(hits, old.length, hits.length - 1, 0);
}
hits[branch] += new_hits;
}
} finally {
lock.unlock();
}
}
public int getSwitchNumber() {
return this.switchNumber;
}
public long getHits(int branch) {
lock.lock();
try {
if (hits.length > branch)
return hits[branch];
return -1;
} finally {
lock.unlock();
}
}
public long getDefaultHits() {
lock.lock();
try {
return defaultHits;
} finally {
lock.unlock();
}
}
public double getBranchCoverageRate() {
lock.lock();
try {
int branches = hits.length + 1;
int hit = (defaultHits > 0) ? 1 : 0;
for (int i = hits.length - 1; i >= 0; hit += ((hits[i--] > 0) ? 1 : 0)) ;
return ((double) hit) / branches;
} finally {
lock.unlock();
}
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if ((obj == null) || !(obj.getClass().equals(this.getClass())))
return false;
SwitchData switchData = (SwitchData) obj;
getBothLocks(switchData);
try {
return (this.defaultHits == switchData.defaultHits)
&& (Arrays.equals(this.hits, switchData.hits))
&& (this.switchNumber == switchData.switchNumber);
} finally {
lock.unlock();
switchData.lock.unlock();
}
}
public int hashCode() {
return this.switchNumber;
}
public int getNumberOfCoveredBranches() {
lock.lock();
try {
int ret = (defaultHits > 0) ? 1 : 0;
for (int i = hits.length - 1; i >= 0; i--) {
if (hits[i] > 0) ret++;
}
return ret;
} finally {
lock.unlock();
}
}
public int getNumberOfValidBranches() {
lock.lock();
try {
return hits.length + 1;
} finally {
lock.unlock();
}
}
public void merge(BranchCoverageData coverageData) {
SwitchData switchData = (SwitchData) coverageData;
getBothLocks(switchData);
try {
defaultHits += switchData.defaultHits;
for (int i = Math.min(hits.length, switchData.hits.length) - 1; i >= 0; i--)
hits[i] += switchData.hits[i];
if (switchData.hits.length > hits.length) {
long[] old = hits;
hits = new long[switchData.hits.length];
System.arraycopy(old, 0, hits, 0, old.length);
System.arraycopy(switchData.hits, old.length, hits, old.length, hits.length - old.length);
}
if ((this.keys.length == 0) && (switchData.keys.length > 0))
this.keys = switchData.keys;
} finally {
lock.unlock();
switchData.lock.unlock();
}
}
private void getBothLocks(SwitchData other) { /*
* To prevent deadlock, we need to get both locks or none at all.
*
* When this method returns, the thread will have both locks.
* Make sure you unlock them!
*/
boolean myLock = false;
boolean otherLock = false;
while ((!myLock) || (!otherLock)) {
try {
myLock = lock.tryLock();
otherLock = other.lock.tryLock();
} finally {
if ((!myLock) || (!otherLock)) {
//could not obtain both locks - so unlock the one we got.
if (myLock) {
lock.unlock();
}
if (otherLock) {
other.lock.unlock();
}
//do a yield so the other threads will get to work.
Thread.yield();
}
}
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
initLock();
}
}