/**
* Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite
* contributors
*
* This file is part of EvoSuite.
*
* EvoSuite is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* EvoSuite 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
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
*/
package org.evosuite.regression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Implementation of object distance following
* "Object distance and its application to adaptive random testing of object-oriented programs"
* by Ilinca Ciupa, Andreas Leitner, Manuel Oriol, Bertrand Meyer (<a
* href="http://se.ethz.ch/~meyer/publications/testing/object_distance.pdf"
* >http://se.ethz.ch/~meyer/publications/testing/object_distance.pdf</a>).
*
* We implemented the following changes:
* <ul>
* <li>In the paper if a reference field does not match (i.e., is different
* because the types are different) then R = 10 is used as difference. This does
* not make sense for several reasons:
* <ul>
* <li>The difference is already taken into account in the type distance.</li>
* <li>If one type adds a field but the other does not, so what? R/2?</li>
* </ul>
* Therefore we decided to apply the value R as factor in the type difference to
* the non-shared fields.</li>
* <li>In the paper a factor of 1/2 is applied to the recursive distance. We
* treat the recursive distance simply as any other field distance, thus it is
* normalized by the number of fields and not by a static factor.</li>
* <li>There is no distance given for two characters. We defined that to be C =
* 10.</li>
* </ul>
*
*/
public class ObjectDistanceCalculator {
protected static final Logger logger = LoggerFactory
.getLogger(ObjectDistanceCalculator.class);
private static final double B = 1;
private static final double R = 10;
private static final double V = 10;
private static final double C = 10;
private static final int MAX_RECURSION = 4;
public static int different_variables = 0;
private final Map<Integer, Integer> hashRecursionCntMap = new LinkedHashMap<Integer, Integer>();
private final Map<Integer, Double> resultCache = new LinkedHashMap<Integer, Double>();
public static double getObjectDistance(Object p, Object q) {
double distance = new ObjectDistanceCalculator().getObjectDistanceImpl(
p, q);
// assert distance >= 0.0 : "Result was " + distance;
// assert !Double.isNaN(distance);
// assert !Double.isInfinite(distance);
return distance;
}
private static Collection<Field> getAllFields(Class<?> commonAncestor) {
Collection<Field> result = new ArrayList<Field>();
Class<?> ancestor = commonAncestor;
while (!ancestor.equals(Object.class)) {
result.addAll(Arrays.asList(ancestor.getDeclaredFields()));
ancestor = ancestor.getSuperclass();
}
return result;
}
private static Class<?> getCommonAncestor(Object p, Object q) {
double pInheritCnt = getTypeDistance(Object.class, p);
double qInheritCnt = getTypeDistance(Object.class, q);
Class<?> pClass = p.getClass();
Class<?> qClass = q.getClass();
while (!pClass.equals(qClass)) {
if (pInheritCnt > qInheritCnt) {
pClass = pClass.getSuperclass();
pInheritCnt--;
} else {
qClass = qClass.getSuperclass();
qInheritCnt--;
}
}
return pClass;
}
private static double getElementaryDistance(Boolean p, Boolean q) {
if (p.equals(q)) {
return 0;
}
return B;
}
private static double normalize(double x) {
return x / (x + 1.0);
}
private static double normalize_inverse(double x) {
return 1.0 / (x + 1.0);
}
private static double getElementaryDistance(Character p, Character q) {
if (p.equals(q)) {
return 0;
} else {
different_variables++;
}
return normalize(Math.abs(p.charValue() - q.charValue()));
}
private static double getElementaryDistance(Number p, Number q) {
//if(p.equals(-1) && !q.equals(-1))
//System.out.println("Num1: " + p + " | Num2: " + q);
if (!p.equals(q))
different_variables++;
if ((p instanceof Double)
&& (((Double) p).isNaN() || ((Double) p).isInfinite())) {
if (p.equals(q))
return 0;
else
return 1;
}
if ((p instanceof Float)
&& (((Float) p).isNaN() || ((Float) p).isInfinite())) {
if (p.equals(q))
return 0;
else
return 1;
}
double distance = Math.abs(p.doubleValue() - q.doubleValue());
// If the epsilon is less than 0.01D (as is used for assertion generation)
// set distance to 0.
if (p instanceof Double) {
if (distance < 0.01)
distance = 0;
}
return normalize(distance);
}
private static double getElementaryDistance(String p, String q) {
// Levenshtein distance
if (!p.equals(q))
different_variables++;
int[][] distanceMatrix = new int[p.length() + 1][q.length() + 1];
for (int idx = 0; idx <= p.length(); idx++) {
distanceMatrix[idx][0] = idx;
}
for (int jdx = 1; jdx <= q.length(); jdx++) {
distanceMatrix[0][jdx] = jdx;
}
for (int idx = 1; idx <= p.length(); idx++) {
for (int jdx = 1; jdx <= q.length(); jdx++) {
int cost;
if (p.charAt(idx - 1) == q.charAt(jdx - 1)) {
cost = 0;
} else {
cost = 1;
}
distanceMatrix[idx][jdx] = Math.min(
distanceMatrix[idx - 1][jdx] + 1, // deletion
Math.min(distanceMatrix[idx][jdx - 1] + 1, // insertion
distanceMatrix[idx - 1][jdx - 1] + cost // substitution
));
if ((idx > 1) && (jdx > 1)
&& (p.charAt(idx - 1) == q.charAt(jdx - 2))
&& (p.charAt(idx - 2) == q.charAt(jdx - 1))) {
distanceMatrix[idx][jdx] = Math.min(
distanceMatrix[idx][jdx],
distanceMatrix[idx - 2][jdx - 2] + cost // transposition
);
}
}
}
return normalize(distanceMatrix[p.length()][q.length()]);
}
private static Object getFieldValue(Field field, Object p) {
try {
Class<?> fieldType = field.getType();
field.setAccessible(true);
if (fieldType.isPrimitive()) {
if (fieldType.equals(Boolean.TYPE)) {
return field.getBoolean(p);
}
if (fieldType.equals(Integer.TYPE)) {
return field.getInt(p);
}
if (fieldType.equals(Byte.TYPE)) {
return field.getByte(p);
}
if (fieldType.equals(Short.TYPE)) {
return field.getShort(p);
}
if (fieldType.equals(Long.TYPE)) {
return field.getLong(p);
}
if (fieldType.equals(Double.TYPE)) {
return field.getDouble(p);
}
if (fieldType.equals(Float.TYPE)) {
return field.getFloat(p);
}
if (fieldType.equals(Character.TYPE)) {
return field.getChar(p);
}
throw new UnsupportedOperationException("Primitive type "
+ fieldType + " not implemented!");
}
return field.get(p);
} catch (IllegalAccessException exc) {
throw new RuntimeException(exc);
}
}
private static Integer getHasCode(Object p, Object q) {
return ((p == null) ? 0 : p.hashCode())
+ ((q == null) ? 0 : q.hashCode());
}
private static Collection<Field> getNonSharedFields(
Class<?> commonAncestor, Object p) {
Collection<Field> result = new ArrayList<Field>();
Class<?> ancestor = p.getClass();
while (!ancestor.equals(commonAncestor)) {
result.addAll(Arrays.asList(ancestor.getDeclaredFields()));
ancestor = ancestor.getSuperclass();
}
return result;
}
private static double getTypeDistance(Class<?> commonAncestor, Object p) {
double result = 0.0;
Class<?> ancestor = p.getClass();
while (!ancestor.equals(commonAncestor)) {
ancestor = ancestor.getSuperclass();
result++;
}
return result;
}
private static double getTypeDistance(Class<?> commonAncestor, Object p,
Object q) {
double result = getTypeDistance(commonAncestor, p)
+ getTypeDistance(commonAncestor, q);
result += getNonSharedFields(commonAncestor, p).size() * R;
result += getNonSharedFields(commonAncestor, q).size() * R;
return result;
}
private static double getObjectDistanceImpl(Object p, Object q) {
if (p == q) {
return 0.0;
}
// both are null
if ((p == null) || (q == null)) {
return 0;
}
// type mismatch
if (((p instanceof Double) && (!(q instanceof Double)))
|| ((q instanceof Double) && (!(p instanceof Double))))
return 1;
/*if (((p instanceof Double) &&
((Double.isNaN((Double) p)) || Double.isInfinite((Double) p)))
|| ((q instanceof Double) && ((Double.isNaN((Double) q))
|| Double.isInfinite((Double) q)))) {*/
if ((p instanceof Double) && (q instanceof Double)) {
// One is NaN, other is Infinity
if (((Double.isNaN((Double) p)) && (Double.isInfinite((Double) q)))
|| ((Double.isNaN((Double) q)) && (Double
.isInfinite((Double) p))))
return 1;
if (((Double.isNaN((Double) p)) && (!Double.isNaN((Double) q)))
|| ((Double.isNaN((Double) q)) && (!Double.isNaN((Double) p))))
return 1;
if (((Double.isInfinite((Double) p)) && (!Double.isInfinite((Double) q)))
|| ((Double.isInfinite((Double) q)) && (!Double.isInfinite((Double) p))))
return 1;
if ((Double.isInfinite((Double) p)) && (Double.isInfinite((Double) q))) {
if (!((Double) p).equals(((Double) q)))
return 1;
}
/*if ((((p instanceof Double) && ((Double.isNaN((Double) p)) || Double
.isInfinite((Double) p))) && (!((q instanceof Double) && ((Double
.isNaN((Double) q)) || Double.isInfinite((Double) q)))))
|| (!(((p instanceof Double) && ((Double.isNaN((Double) p)) || Double
.isInfinite((Double) p)))) && (((q instanceof Double) && ((Double
.isNaN((Double) q)) || Double
.isInfinite((Double) q))))))
return 1;
else
return 0;
*/
}
if (((p instanceof Float) && (!(q instanceof Float)))
|| ((q instanceof Float) && (!(p instanceof Float))))
return 1;
if ((p instanceof Float) && (q instanceof Float)) {
// One is NaN, other is Infinity
if (((Float.isNaN((Float) p)) && (Float.isInfinite((Float) q)))
|| ((Float.isNaN((Float) q)) && (Float
.isInfinite((Float) p))))
return 1;
if (((Float.isNaN((Float) p)) && (!Float.isNaN((Float) q)))
|| ((Float.isNaN((Float) q)) && (!Float.isNaN((Float) p))))
return 1;
if (((Float.isInfinite((Float) p)) && (!Float.isInfinite((Float) q)))
|| ((Float.isInfinite((Float) q)) && (!Float.isInfinite((Float) p))))
return 1;
if ((Float.isInfinite((Float) p)) && (Float.isInfinite((Float) q))) {
if (!((Float) p).equals(((Float) q)))
return 1;
}
}
if (!p.getClass().equals(q.getClass()))
return 0;
// What if one is a primitive and the other not?
// if (!value.getClass().equals(value2.getClass())) ?
if (p instanceof Number) {
return getElementaryDistance((Number) p, (Number) q);
}
if (p instanceof Boolean) {
return getElementaryDistance((Boolean) p, (Boolean) q);
}
if (p instanceof String) {
return getElementaryDistance((String) p, (String) q);
}
if (p instanceof Character) {
return getElementaryDistance((Character) p, (Character) q);
}
if (p instanceof Map) {
return normalize(getObjectMapDistance((Map<String, Object>) p,
(Map<String, Object>) q));
}
// TODO: enums.
if (p instanceof Enum) {
return 0;
/*return getElementaryDistance(((Enum) p).ordinal(),
((Enum) q).ordinal());*/
}
throw new Error("Distance of unknown type!");
}
public static double getObjectMapDistance(Map<String, Object> map1,
Map<String, Object> map2) {
double distance = 0.0;
for (String fieldName : map1.keySet()) {
if (!map2.containsKey(fieldName))
continue;
Object value1 = map1.get(fieldName);
Object value2 = map2.get(fieldName);
double tmpDistance = 0;
try {
tmpDistance = getObjectDistanceImpl(value1, value2);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
/*if(tmpDistance !=0)
//if(fieldName.equals("fake_var_java_lang_Double"))
System.out.println("field: " + fieldName + ", d: " +
tmpDistance+" <");
*/
if (Double.valueOf(tmpDistance).isNaN()
|| Double.valueOf(tmpDistance).isInfinite()) {
different_variables++;
tmpDistance = 0;
}
distance += tmpDistance;
}
// System.out.println("final dis: " + distance+" +");
return distance;
}
private boolean breakRecursion(Object p, Object q) {
Integer hashCode = getHasCode(p, q);
Integer recursionCnt = hashRecursionCntMap.get(hashCode);
if (recursionCnt == null) {
recursionCnt = 0;
}
if (recursionCnt >= MAX_RECURSION) {
return true;
}
recursionCnt++;
hashRecursionCntMap.put(hashCode, recursionCnt);
return false;
}
private double getCompositeObjectDistance(Object p, Object q) {
Double cachedDistance = resultCache.get(getHasCode(p, q));
if (cachedDistance != null) {
return cachedDistance;
}
if (breakRecursion(p, q)) {
return 0.0;
}
Class<?> commonAncestor = getCommonAncestor(p, q);
double distance = getTypeDistance(commonAncestor, p, q);
distance += getFieldDistance(commonAncestor, p, q);
resultCache.put(getHasCode(p, q), distance);
return distance;
}
private double getFieldDistance(Class<?> commonAncestor, Object p, Object q) {
Collection<Field> fields = getAllFields(commonAncestor);
double sum = 0;
for (Field field : fields) {
sum += getObjectDistanceImpl(getFieldValue(field, p), getFieldValue(field, q));
}
if (sum == 0.0) {
return sum;
}
return sum / fields.size();
}
}