/*
* Copyright 2008, Unitils.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.unitils.reflectionassert;
import org.unitils.core.UnitilsException;
import org.unitils.reflectionassert.comparator.Comparator;
import org.unitils.reflectionassert.difference.Difference;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
/**
* 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.
*
* @author Tim Ducheyne
* @author Filip Neven
*/
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 left the left instance
* @param right the right instance
* @return true if there is no difference, false otherwise
*/
public boolean isEqual(Object left, Object right) {
Difference difference = getDifference(left, right, true);
return difference == null;
}
/**
* Checks whether there is a difference between the left and right objects.
*
* @param left the left instance
* @param right the right instance
* @return the difference, null if there is no difference
*/
public Difference getDifference(Object left, Object right) {
return getDifference(left, right, 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 left the left instance
* @param right 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 left, Object right, boolean onlyFirstDifference) {
// check whether difference is available in cache
Map<Object, Difference> cachedResult = getCachedDifference(left, onlyFirstDifference);
if (cachedResult != null) {
if (cachedResult.containsKey(right)) {
// found difference in cache, return cached value
return cachedResult.get(right);
}
} else {
cachedResult = new IdentityHashMap<Object, Difference>();
saveResultInCache(left, cachedResult, onlyFirstDifference);
}
cachedResult.put(right, null);
// perform actual comparison by iterating over the comparators
boolean compared = false;
Difference result = null;
for (Comparator comparator : comparators) {
if (comparator.canCompare(left, right)) {
result = comparator.compare(left, right, onlyFirstDifference, this);
compared = true;
break;
}
}
// check whether a suitable comparator was found
if (!compared) {
throw new UnitilsException("Could not determine differences. No comparator found that is able to compare the values. Left: " + left + ", right " + right);
}
// register outcome in cache
cachedResult.put(right, 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);
}
}
}