package fr.inria.diversify.transformation.query;
import fr.inria.diversify.coverage.ICoverageReport;
import fr.inria.diversify.coverage.NullCoverageReport;
import fr.inria.diversify.diversification.InputProgram;
import fr.inria.diversify.transformation.Transformation;
import fr.inria.diversify.transformation.TransformationJsonParser;
import fr.inria.diversify.transformation.TransformationParserException;
import fr.inria.diversify.transformation.ast.ASTTransformation;
import fr.inria.diversify.util.Log;
import java.io.*;
import java.util.*;
/**
* Search for points of known sosies
* <p/>
* Created by marcel on 6/06/14.
*/
public class KnownSosieQuery extends TransformationQuery {
List<Transformation> transformations;
/**
* A class containing data and logic to help increase the speed of the search process of a previously found
* transformation
*/
protected class TransformationFoundRecord {
//Transformation that incremented by this transformation.
//A parent transformation of {1, 2, 3} is for example {1, 2}, {1, 3} or {2, 3}
//Formally: Y is parent of X if Y subset of X
private TransformationFoundRecord parent = null;
//Transformation found just before this one
//Used to have a linked list of previously built transformations.
private TransformationFoundRecord previous = null;
//Index of the transformations taken from the pool of transformations that conforms this multisosie
ArrayList<Integer> transformation;
private int incrementalSeries;
//Known status of this transformation
int status = Transformation.SOSIE;
int myHashCode = 0;
public TransformationFoundRecord(TransformationFoundRecord parent, TransformationFoundRecord previous, int status) {
setParentAndPrevious(parent, previous);
this.status = status;
}
public TransformationFoundRecord(Integer[] indexes, TransformationFoundRecord parent, TransformationFoundRecord previous) {
transformation = new ArrayList<>(Arrays.asList(indexes));
setParentAndPrevious(parent, previous);
}
public void setParentAndPrevious(TransformationFoundRecord parent, TransformationFoundRecord previous) {
this.setPrevious(previous);
this.setParent(parent);
}
@Override
public boolean equals(Object p) {
ArrayList<Integer> t = ((TransformationFoundRecord) p).transformation;
if (t.size() != transformation.size()) return false;
for (Integer ti : t) {
if (!transformation.contains(ti)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
if (myHashCode == 0) {
if (transformation == null) return 0;
for (Integer i : transformation) {
myHashCode += i * 17;
}
myHashCode %= 5009;
}
return myHashCode;
}
public void setIncrementalSeries(int incrementalSeries) {
if ( getParent() != null && getParent().getIncrementalSeries() != incrementalSeries ) {
throw new RuntimeException("the series number mismatch!!");
}
this.incrementalSeries = incrementalSeries;
}
/**
* Series number of the transformation found. A redundant data to help improve robustness.
*/
public int getIncrementalSeries() {
return incrementalSeries;
}
public TransformationFoundRecord getParent() {
return parent;
}
public void setParent(TransformationFoundRecord parent) {
this.parent = parent;
if ( this.parent != null ) {
this.incrementalSeries = this.parent.incrementalSeries;
}
}
public TransformationFoundRecord getPrevious() {
return previous;
}
public void setPrevious(TransformationFoundRecord previous) {
this.previous = previous;
}
}
/**
* Status of the last transformation found. Querys may use this information to optimize further exploration of
* the search space
*/
private int lastTransformationStatus = Transformation.SOSIE;
/**
* Multipoint transformations may be (parent/child) related.
*
* The incremental series number helps to identify parents and childs obtained in different runs
*/
protected int lastIncrementalSeries = 0;
protected HashMap<Integer, HashSet<TransformationFoundRecord>> transformationFounds;
public boolean isCleanSeriesOnly() {
return cleanSeriesOnly;
}
public void setCleanSeriesOnly(boolean cleanSeriesOnly) {
this.cleanSeriesOnly = cleanSeriesOnly;
}
/**
* An small helper class to order sosies by their coverage
*/
private class SosieWithCoverage {
private List<Integer> coverage;
private Transformation transformation;
public SosieWithCoverage(Transformation t) {
this.transformation = t;
coverage = new ArrayList<>();
}
@Override
public String toString() {
return "Coverage size: " + coverage.size() + " " + this.transformation.toString();
}
}
/**
* Status of the last transformation found. Querys may use this information to optimize further exploration of
* the search space
*/
public int getLastTransformationStatus() {
return lastTransformationStatus;
}
/**
* Multipoint transformations may be (parent/child) related.
*
* The incremental series number helps to identify parents and childs obtained in different runs
*/
public int getLastIncrementalSeries() {
return lastIncrementalSeries;
}
/*
public void setLastIncrementalSeries(int lastIncrementalSeries) {
this.lastIncrementalSeries = lastIncrementalSeries;
}
*/
private boolean cleanSeriesOnly = true;
/**
* Sosies found from the transformation pool passed as paramethers.
*/
protected Transformation getSosies(int index) {
return sosies.get(index).transformation;
}
private ArrayList<SosieWithCoverage> sosies;
//Indicates if the multi-sosies are locate incrementing a the previously found smaller multi-sosie
private boolean incrementalSosiefication = true;
//Last multisosie found
protected TransformationFoundRecord prevRecord = null;
//Last size of transformation elements we where ask to executeQuery for.
protected int lastTransfSizeNOfElems = 0;
//Increment of the last series number
private boolean seriesIncrement = true;
public void setLastTransformationStatus(int lastTransformationStatus) {
this.lastTransformationStatus = lastTransformationStatus;
if (prevRecord != null) {
prevRecord.status = lastTransformationStatus;
}
}
public KnownSosieQuery(InputProgram inputProgram, ArrayList<Transformation> transf) {
super(inputProgram);
transformationFounds = new HashMap<>();
extractSosies(transf);
}
public KnownSosieQuery(InputProgram inputProgram) throws TransformationParserException {
super(inputProgram);
transformationFounds = new HashMap<>();
TransformationJsonParser parser = new TransformationJsonParser(false, getInputProgram());
File f = new File(getInputProgram().getPreviousTransformationsPath());
Collection<Transformation> ts;
if (f.isDirectory()) {
ts = parser.parseDir(f.getAbsolutePath());
} else {
ts = parser.parseFile(f);
}
extractSosies(ts);
}
/**
* Extracts the sosies from a transformation list. This method also extract the coverage report and sorts
* the sosies by their coverage
*
* @param transf
*/
private void extractSosies(Collection<Transformation> transf) {
ICoverageReport coverageReport = getInputProgram().getCoverageReport();
boolean coveragePresent = coverageReport != null && !(coverageReport instanceof NullCoverageReport);
sosies = new ArrayList<>();
for (Transformation t : transf) {
if (t.isSosie()) {
SosieWithCoverage c = new SosieWithCoverage(t);
if (coveragePresent) {
//Distribution of this transformation transplant point
//each client creates a jacoco file, each one is assigned an index
c.coverage = coverageReport.getCoverageDistribution(((ASTTransformation) t).getTransplantationPoint());
Collections.sort(c.coverage);
}
//Don't add sosies without coverage in case such coverage exists
if (!coveragePresent || c.coverage.size() > 0) {
sosies.add(c);
}
}
}
//Order the sosies from less covered to more covered. This way we increases the chances that an uniformly
//distributed selection covers most of the clients
if (coveragePresent) {
Collections.sort(sosies, new Comparator<SosieWithCoverage>() {
@Override
public int compare(SosieWithCoverage o1, SosieWithCoverage o2) {
int sizeDiff = o1.coverage.size() - o2.coverage.size();
if (sizeDiff == 0) {
int i = 0;
while (i < o1.coverage.size() && o1.coverage.get(i) - o1.coverage.get(i) == 0) {
i++;
}
return i >= o1.coverage.size() ? sizeDiff : o1.coverage.get(i) - o1.coverage.get(i);
}
return sizeDiff;
}
});
}
}
/**
* Performs the search for transformations
*
* @throws SeveralTriesUnsuccessful when several unsuccessful attempts have been made to get the transformations
*/
public void executeQuery() {
int max = 100;
Exception[] causes = new Exception[max];
int trials = 0;
boolean failed = true;
while (trials < max && failed)
try {
//The amount of transformations are given in the executeQuery by the InputProgram
transformations = query(getInputProgram().getTransformationPerRun());
failed = false;
} catch (QueryException qe) {
if ( qe.getReason().equals(QueryException.Reasons.UNABLE_TO_FIND_SOSIE_PARENT) ) {
//We cannot recover from this one. No use to try
causes[0] = qe;
trials = max + 1;
}
} catch (Exception e) {
Log.warn("Unable to executeQuery: " + e.getMessage());
causes[trials] = e;
failed = true;
trials++;
}
if (trials >= max) {
throw new SeveralTriesUnsuccessful(causes);
}
}
/**
* Tells if a similar transformation has been already found
*
* @param tf transformation found
* @param size Size of transformation asked by the users.
* This size may vary from the actual size of the transformation
* @return true if already found
*/
protected boolean alreadyFound(int size, TransformationFoundRecord tf) {
if (!transformationFounds.containsKey(size)) {
HashSet<TransformationFoundRecord> h = new HashSet<>();
h.add(tf);
transformationFounds.put(size, h);
return false;
} else if (transformationFounds.get(size).contains(tf)) {
return true;
} else {
transformationFounds.get(size).add(tf);
return false;
}
}
/**
* Returns the list of found transformations a collection of transformations
*
* @return
* @throws Exception
*/
public Collection<Transformation> getMultiTransformations() {
return transformations;
}
@Override
public Transformation query() throws QueryException {
return null;
}
/**
* Estimate the max number of multisosie transformations
*/
private long maxNumberOfTransformations(int nb) {
long z = sosies.size();
long max = z;
for (int i = 0; i < nb - 1; i++) {
z--;
long preMax = max * z;
//Avoid overflow
if (preMax < 0) return Long.MAX_VALUE;
max = preMax;
}
z = nb;
for (int i = nb; i > 1; i--) {
nb--;
z = z * nb;
}
return max / z;
}
/**
* Finds an sosie transformation to increment (inherit from)
*
* @param nb Current transformation size
* @return An integer array with the index of the single-transformations forming the multi-transformation
* @throws QueryException
*/
private Integer[] initIncrementalTransformation(int nb) throws QueryException {
Integer[] indexes = new Integer[nb];
//Create the linked list data structure to allow incremental multisosies
if (incrementalSosiefication && prevRecord != null) {
ArrayList<Integer> tf = null;
do {
if (lastTransfSizeNOfElems != nb) {
seriesIncrement = false;
//This means that we have changed the transformation size and therefore we must use
//the previously found multisosie as the parent of the current transformation
if (isCleanSeriesOnly() && prevRecord.status != Transformation.SOSIE) {
//The last transformation was not a sosie. Create an empty slot and continue search
prevRecord = new TransformationFoundRecord(
prevRecord, null, prevRecord.status);
//Since we create and empty slot we are not longer on the edge
lastTransfSizeNOfElems = nb;
} else {
tf = prevRecord.transformation;
}
} else if (prevRecord.getParent() != null) {
if (prevRecord.getParent().getPrevious() == null) {
//Special case when we reach the end of the previous sosie list
// and we are still searching for new sosies
throw new QueryException(QueryException.Reasons.UNABLE_TO_FIND_SOSIE_PARENT);
}
//On the other hand we may continue creating multisosies incrementing an existing one
int s = prevRecord.getParent().getPrevious().status;
if (isCleanSeriesOnly() && s != Transformation.SOSIE) {
//The last transformation was not a sosie. Create an empty slot and continue search
prevRecord = new TransformationFoundRecord(
prevRecord.getParent().getPrevious(),
prevRecord, s);
} else {
tf = prevRecord.getParent().getPrevious().transformation;
}
}
} while (!(tf != null ||
prevRecord.getParent() == null ||
prevRecord.getParent().getPrevious() == null));
//We found none... go boom
if (tf == null &&
prevRecord.getParent() != null &&
prevRecord.getParent().getPrevious() == null) {
throw new QueryException(QueryException.Reasons.UNABLE_TO_FIND_SOSIE_PARENT);
}
//Copy the parent transformations and index in the pool of transformations
if (tf != null) {
for (int i = 0; i < tf.size(); i++) {
transformations.add(getSosies(tf.get(i)));
indexes[i] = tf.get(i);
}
}
}
return indexes;
}
/**
* Completes the incremental transformation process
*
* @param nb Current transformation size
* @param indexes Indexes of the transformations found
* @param tf Transformations found
* @param f
*/
private void completeIncrementalTransformation(int nb, Integer[] indexes,
ArrayList<Transformation> tf, TransformationFoundRecord f) {
//Linking list mechanism to know the parent of a multisosie
if (prevRecord == null) {
prevRecord = f;
} else if (lastTransfSizeNOfElems != nb) {
prevRecord = new TransformationFoundRecord(indexes, prevRecord, null);
} else if (prevRecord.getParent() == null) {
prevRecord = new TransformationFoundRecord(indexes, null, prevRecord);
if (seriesIncrement) {
lastIncrementalSeries += 1;
}
prevRecord.setIncrementalSeries(lastIncrementalSeries);
} else {
prevRecord = new TransformationFoundRecord(indexes, prevRecord.getParent().getPrevious(), prevRecord);
}
transformations = tf;
//prevRecord.setIncrementalSeries(lastIncrementalSeries);
}
@Override
public List<Transformation> query(int nb) throws QueryException {
//Update the incremental series number
if (prevRecord == null) lastIncrementalSeries = 0;
transformations = new ArrayList();
//Integer[] indexes = new Integer[nb];
Integer[] indexes = initIncrementalTransformation(nb);
Random r = new Random();
//Don't create a sosie bigger than the sosie pool. Duh!
if (nb > sosies.size()) nb = sosies.size();
long maxTransfNumbers = maxNumberOfTransformations(nb);
int transAttempts = 0;
boolean found = true;
//Try several times searching for a transformation we haven't found before.
while (found && transAttempts < maxTransfNumbers) {
int attempts = 0;
ArrayList<Transformation> tf = new ArrayList<>(transformations);
int i = tf.size();
Arrays.fill(indexes, tf.size(), indexes.length, -1);
//Build the transformation
while (tf.size() < nb && attempts < sosies.size()) {
int index = r.nextInt(sosies.size());
Transformation t = getSosies(index);
if (canBeMerged(t)) {
indexes[i] = index;
i++;
tf.add(t);
}
attempts++;
}
TransformationFoundRecord f = new TransformationFoundRecord(indexes, null, null);
//See if the transformation was already found
found = alreadyFound(nb, f);
if (!found) {
completeIncrementalTransformation(nb, indexes, tf, f);
//Log the transformation found in the run
try {
StringBuilder sb = new StringBuilder();
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("multisosiesfound.txt", true)));
if (transformationFounds.get(nb).size() > 1) {
sb.append(" Status: ").append(getLastTransformationStatus());
}
sb.append("\n Serie:: ").append(prevRecord.getIncrementalSeries()).append(". Transf::");
for (int k = 0; k < indexes.length && indexes[k] > -1; k++) {
sb.append(indexes[k]).append(", ");
}
out.println(sb.toString());
out.close();
} catch (IOException e) {
//Nothing to do here
}
}
transAttempts++;
}
if (transAttempts >= maxTransfNumbers) {
throw new MaxNumberOfAttemptsReach(maxTransfNumbers, transAttempts);
}
lastIncrementalSeries = prevRecord.getIncrementalSeries();
lastTransfSizeNOfElems = nb;
return transformations;
}
/**
* Indicates if the transformation can be merged with the current ones
*
* @param t
* @return
*/
protected boolean canBeMerged(Transformation t) {
//Avoid sosies already in the transformation
boolean result = !transformations.contains(t);
//Get the class name of the transformation
ASTTransformation ast = (ASTTransformation) t;
String classFileT = ast.getTransplantationPoint().getCompilationUnit().getFile().getName();
//Avoid sosies in the same class
for (int i = 0; i < transformations.size() && result; i++) {
ASTTransformation a = (ASTTransformation) transformations.get(i);
String classFileA = a.getTransplantationPoint().getCompilationUnit().getFile().getName();
result = result && !classFileA.equals(classFileT);
}
return result;
}
/**
* Uses sosies from previous runs
*/
public boolean getIncrementalSosiefication() {
return incrementalSosiefication;
}
public void setIncrementalSosiefication(boolean incrementalSosiefication) {
this.incrementalSosiefication = incrementalSosiefication;
}
/**
* Returns the sosies found from the pool of transformations
*
* @return
*/
public ArrayList<Transformation> getSosies() {
ArrayList<Transformation> result = new ArrayList<>();
for (SosieWithCoverage s : sosies) {
result.add(s.transformation);
}
return result;
}
}