![JAR search and dependency download from the Maven repository](/logo.png)
org.evosuite.regression.ObjectDistanceCalculator Maven / Gradle / Ivy
The newest version!
/**
* Copyright (C) 2010-2018 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 .
*/
package org.evosuite.regression;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 (http://se.ethz.ch/~meyer/publications/testing/object_distance.pdf).
*
* We implemented the following changes:
*
* - 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:
*
* - The difference is already taken into account in the type distance.
* - If one type adds a field but the other does not, so what? R/2?
*
* Therefore we decided to apply the value R as factor in the type difference to
* the non-shared fields.
* - 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.
* - There is no distance given for two characters. We defined that to be C =
* 10.
*
*/
public class ObjectDistanceCalculator {
private 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;
private final Map hashRecursionCntMap = new LinkedHashMap<>();
private final Map resultCache = new LinkedHashMap<>();
private int numDifferentVariables = 0;
public static double getObjectDistance(Object p, Object q) {
ObjectDistanceCalculator calculator = new ObjectDistanceCalculator();
return calculator.getObjectDistanceImpl(p, q) + normalize(calculator.numDifferentVariables);
}
private static Collection getAllFields(Class> commonAncestor) {
Collection result = new ArrayList<>();
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 normalizeTowardsZero(double x) {
return 1.0 / (x + 1.0);
}
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 getNonSharedFields(Class> commonAncestor, Object p) {
Collection result = new ArrayList<>();
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 double getElementaryDistance(Character p, Character q) {
if (p.equals(q)) {
return 0;
} else {
numDifferentVariables++;
}
return normalize(Math.abs(p - q));
}
private double getElementaryDistance(Number p, Number q) {
if (!p.equals(q)) {
numDifferentVariables++;
}
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;
if (p instanceof Long) {
distance = Math.abs(p.longValue() - q.longValue());
} else {
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);
}
/* Levenshtein distance */
private double getElementaryDistance(String p, String q) {
if (!p.equals(q)) {
numDifferentVariables++;
}
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 double getObjectDistanceImpl(Object p, Object q) {
if (p == q) {
return 0.0;
}
// one is null, the other isn't
if (p == null || q == null) {
// if only one of them is null
numDifferentVariables++;
return 0;
}
// type mismatch
boolean isNumberP = p instanceof Number;
boolean isNumberQ = q instanceof Number;
if (isNumberP != isNumberQ) {
return 1;
}
// if they're both numbers, check NaN / Infinity status
if (isNumberP && haveDifferentNaNOrInfinity(p, q)) {
return 1;
}
// if they're from different classes
if (!p.getClass().getName().equals(q.getClass().getName())) {
numDifferentVariables++;
return 0;
}
// What if one is a primitive and the other not?
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 && isStringObjectMap((Map) p) && isStringObjectMap((Map) q)) {
return normalize(getObjectMapDistance((Map) p, (Map) q));
}
/*
TODO: add support for maps of other types.
One possible approach is perhaps to turn the object into json and turning it back to a
recursive Map and apply the method above to it. (Jackson library is able
to do this pretty quickly).
*/
if (p instanceof Enum) {
// Levenshtein distance of enum name
return getElementaryDistance(((Enum) p).name(), ((Enum) q).name());
}
return getCompositeObjectDistance(p, q);
// throw new Error("Distance of unknown type!");
}
/**
* This following relatively-hacky way, checks whether the map keys are strings
*/
private boolean isStringObjectMap(Map p) {
if(p.isEmpty()){
return true;
}
return p.keySet().iterator().next().getClass().getName().equals(String.class.getName());
}
private boolean haveDifferentNaNOrInfinity(Object p, Object q) {
boolean isNanP = false;
boolean isNanQ = false;
boolean isInfiniteP = false;
boolean isInfiniteQ = false;
if (p instanceof Double) {
Double doubleP = (Double) p;
isNanP = Double.isNaN(doubleP);
isInfiniteP = Double.isInfinite(doubleP);
}
if (q instanceof Double) {
Double doubleQ = (Double) q;
isNanQ = Double.isNaN(doubleQ);
isInfiniteQ = Double.isInfinite(doubleQ);
}
if (p instanceof Float) {
Float floatP = (Float) p;
isNanP = Float.isNaN(floatP);
isInfiniteP = Float.isInfinite(floatP);
}
if (q instanceof Float) {
Float floatQ = (Float) q;
isNanQ = Float.isNaN(floatQ);
isInfiniteQ = Float.isInfinite(floatQ);
}
// One is NaN, other is Infinity
if (isNanP && isInfiniteQ || isNanQ && isInfiniteP) {
return true;
}
// one is Nan, the other isn't
if (isNanP != isNanQ) {
return true;
}
// one is infinite, the other isn't
if (isInfiniteP != isInfiniteQ) {
return true;
}
/*
both are infinite (previous condition ensures equality), and they don't match
(e.g., one is positive infinity, and the other is negative)
*/
if (isInfiniteP && !p.equals(q)) {
return true;
}
return false;
}
public double getObjectMapDistance(Map map1, Map map2) {
double distance = 0.0;
int missingFields = 0;
for (String fieldName : map1.keySet()) {
if (!map2.containsKey(fieldName)) {
missingFields++;
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 (Double.valueOf(tmpDistance).isNaN() || Double.valueOf(tmpDistance).isInfinite()) {
numDifferentVariables++;
tmpDistance = 0;
}
distance += tmpDistance;
}
// account for field differences
distance += getElementaryDistance(map1.size(), map2.size());
if (map1.size() == map2.size()) {
distance += normalize(missingFields);
}
return distance;
}
private boolean breakRecursion(Object p, Object q) {
Integer hashCode = getHasCode(p, q);
Integer recursionCount = hashRecursionCntMap.get(hashCode);
if (recursionCount == null) {
recursionCount = 0;
}
if (recursionCount >= MAX_RECURSION) {
return true;
}
recursionCount++;
hashRecursionCntMap.put(hashCode, recursionCount);
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 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();
}
public int getNumDifferentVariables() {
return numDifferentVariables;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy