package ca.uwaterloo.ece.qhanam.jrsrepair;
import java.util.HashMap;
import java.util.HashSet;
import java.util.NavigableMap;
import java.util.Random;
import java.util.Stack;
import java.util.TreeMap;
import java.util.Set;
import org.eclipse.jdt.core.dom.*;
/**
* Stores a list of statements and their weights. Randomly selects statements (with probabilities
* determined by their weights).
*
* Seed statements and faulty statements inserted here should have already been filtered according
* to statement coverage. Seed statements should be in the execution path of one or more test cases,
* while faulty statements should be in the execution path of one or more faulty test cases.
*
* TODO: Add functionality to support filtering seed statements by variable scope.
*
* @author qhanam
*
*/
public class Statements {
private NavigableMap<Double, SourceStatement> statements;
private double totalWeight; // We track the total weight so that we can randomly select a statement with weighting.
private HashMap<String, HashSet<String>> scope;
private Random random;
public Statements(HashMap<String, HashSet<String>> scope, long randomSeed) {
this.statements = new TreeMap<Double, SourceStatement>();
this.totalWeight = 0;
this.scope = scope;
this.random = new Random(randomSeed);
}
/**
* Add a statement to our statement maps. The statements are inserted according to their weight.
* @param s The statement to insert.
* @param weight The weight used to calculate the probability of this statement being selected.
* @param faulty Indicates the statement should be stored in the faulty statement list as well.
*/
public void addStatement(SourceStatement s, double weight){
this.totalWeight += weight;
this.statements.put(this.totalWeight, s);
}
/**
* Randomly selects and returns a faulty statement (to be mutated).
* @return The faulty statement to be mutated.
*/
public SourceStatement getRandomStatement(){
SourceStatement statement = null;
do{
/* Compute a random spot. */
double random = this.random.nextDouble() * this.totalWeight;
/* Find the statement at that random spot. */
statement = this.statements.ceilingEntry(random).getValue();
} while(statement.inUse);
/* This statement is now in use. */
statement.inUse = true;
return statement;
}
/**
* Randomly selects and returns a seed statement. Checks scope against
* a destination (faulty) statement.
* @return The faulty statement to be mutated.
*/
public SourceStatement getRandomStatement(SourceStatement destinationScope){
SourceStatement statement = null;
boolean inScope = false;
int ctr = 0;
do{
/* Compute a random spot. */
double random = this.random.nextDouble() * this.totalWeight;
/* Find the statement at that random spot. */
statement = this.statements.ceilingEntry(random).getValue();
/* Check that the statement variables are in scope. */
inScope = this.inScope(statement, destinationScope);
/* Just in case, let's have an escape plan. */
ctr++;
if(ctr > 500 && !statement.inUse) {
System.err.println("Could not find an unused statement in scope in " + ctr + " iterations.");
break;
}
} while(statement.inUse || !inScope);
/* This statement is now in use. */
statement.inUse = true;
return statement;
}
/**
* Check that all the variables used in the statement are in the scope.
* @param statement
* @return
*/
private boolean inScope(SourceStatement statement, SourceStatement destinationScope){
HashSet<String> methodScope = this.scope.get(destinationScope.sourceFile + "." + this.getMethodName(destinationScope.statement));
VarASTVisitor vav = new VarASTVisitor();
statement.statement.accept(vav);
/* If there are no variables used, the statement is in scope. */
if(vav.variableNames.size() == 0) return true;
/* TODO: Why does this happen? */
if(methodScope == null) return false;
/* If there is at least one SimpleName matching a variable in scope,
* the statement might be in scope so we return true. */
for(String variableName : vav.variableNames){
if(methodScope.contains(variableName)) return true;
}
/* The statement isn't in scope. */
return false;
}
private String getMethodName(ASTNode node){
while(!(node instanceof MethodDeclaration)){
if(node.equals(node.getRoot())) return "";
node = node.getParent();
}
MethodDeclaration md = (MethodDeclaration) node;
return md.getName().toString();
}
/**
* Returns a string containing the statements in this set and their weights.
*/
@Override
public String toString(){
String s = "";
Set<NavigableMap.Entry<Double, SourceStatement>> entrySet = statements.entrySet();
for(NavigableMap.Entry<Double, SourceStatement> entry : entrySet){
s += (Math.round(entry.getKey()*10.0)/10.0) + " : " + entry.getValue() + "\n";
}
return s;
}
/**
* Checks if the statement list is empty.
* @return True if there are zero elements in the statement map.
*/
public boolean isEmpty(){
if(this.statements.size() == 0) return true;
return false;
}
/**
* Collects potential variable usages in a statement.
* @author qhanam
*/
private class VarASTVisitor extends ASTVisitor{
public Stack<String> variableNames;
public VarASTVisitor(){
this.variableNames = new Stack<String>();
}
/**
* Get the names of potential variables used in this statement.
*/
public boolean visit(SimpleName var) {
this.variableNames.add(var.toString());
return false;
}
}
}