package org.test4j.hamcrest.matcher.property.reflection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import org.test4j.hamcrest.matcher.property.comparator.Comparator;
import org.test4j.hamcrest.matcher.property.difference.Difference;
import org.test4j.module.Test4JException;
/**
* A comparator for comparing two values by reflection.
* <p/>
* The actual comparison of the values is implemented as a comparator chain. The
* chain is passed as an argument during the construction. Each of the
* comparators in the chain is able to compare some types of values. E.g. a
* CollectionComparator is able to compare collections, simple values such as
* integers are compared by the SimpleCasesComparator... A number of 'leniency
* levels' can also be added to the chain: e.g. the
* LenientOrderCollectionComparator ignores the actual ordering of 2
* collections.
* <p/>
* The preferred way of creating new instances is by using the
* ReflectionComparatorFactory. This factory will make sure that a correct
* comparator chain is assembled.
* <p/>
* A readable report differences can be created using the DifferenceReport.
*
*/
public class ReflectionComparator {
/**
* The comparator chain.
*/
protected List<Comparator> comparators;
/**
* A cache of results, so that comparisons are only performed once and
* infinite loops because of cycles are avoided A different cache is used
* dependent on whether only the first difference is required or whether we
* need all differences, since the resulting {@link Difference} objects
* differ.
*/
protected Map<Object, Map<Object, Difference>> firstDifferenceCachedResults = new IdentityHashMap<Object, Map<Object, Difference>>();
protected Map<Object, Map<Object, Difference>> allDifferencesCachedResults = new IdentityHashMap<Object, Map<Object, Difference>>();
/**
* Creates a comparator that will use the given chain.
*
* @param comparators
* The comparator chain, not null
*/
public ReflectionComparator(List<Comparator> comparators) {
this.comparators = comparators;
}
/**
* Checks whether there is no difference between the left and right objects.
*
* @param expectedValue
* the left instance
* @param actualValue
* the right instance
* @return true if there is no difference, false otherwise
*/
public boolean isEqual(Object expectedValue, Object actualValue) {
Difference difference = getDifference(expectedValue, actualValue, true);
return difference == null;
}
/**
* Checks whether there is a difference between the left and right objects.
*
* @param expectedValue
* the left instance
* @param actualValue
* the right instance
* @return the difference, null if there is no difference
*/
public Difference getDifference(Object expectedValue, Object actualValue) {
return getDifference(expectedValue, actualValue, false);
}
/**
* Checks whether there are differences between the left and right objects.
* This will return the root difference of the whole difference tree
* containing all the differences between the objects.
*
* @param expectedValue
* the left instance
* @param actualValue
* the right instance
* @param onlyFirstDifference
* True if the comparison should stop at the first differnece
* @return the root difference, null if there is no difference
*/
public Difference getDifference(Object expectedValue, Object actualValue, boolean onlyFirstDifference) {
// check whether difference is available in cache
Map<Object, Difference> cachedResult = getCachedDifference(expectedValue, onlyFirstDifference);
if (cachedResult != null) {
if (cachedResult.containsKey(actualValue)) {
// found difference in cache, return cached value
return cachedResult.get(actualValue);
}
} else {
cachedResult = new IdentityHashMap<Object, Difference>();
saveResultInCache(expectedValue, cachedResult, onlyFirstDifference);
}
cachedResult.put(actualValue, null);
// perform actual comparison by iterating over the comparators
boolean compared = false;
Difference result = null;
for (Comparator comparator : comparators) {
boolean canCompare = comparator.canCompare(expectedValue, actualValue);
if (canCompare) {
result = comparator.compare(expectedValue, actualValue, onlyFirstDifference, this);
compared = true;
break;
}
}
// check whether a suitable comparator was found
if (!compared) {
throw new Test4JException(
"Could not determine differences. No comparator found that is able to compare the values. Left: "
+ expectedValue + ", right " + actualValue);
}
// register outcome in cache
cachedResult.put(actualValue, result);
return result;
}
protected void saveResultInCache(Object left, Map<Object, Difference> cachedResult, boolean onlyFirstDifference) {
if (onlyFirstDifference) {
firstDifferenceCachedResults.put(left, cachedResult);
} else {
allDifferencesCachedResults.put(left, cachedResult);
}
}
protected Map<Object, Difference> getCachedDifference(Object left, boolean onlyFirstDifference) {
if (onlyFirstDifference) {
return firstDifferenceCachedResults.get(left);
} else {
return allDifferencesCachedResults.get(left);
}
}
}