/* * Copyright 2006 the original author or authors. * * 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. * * Modified by Clam <clamisgood@gmail.com> * * This file is part of LateralGM. * LateralGM is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package org.lateralgm.compare; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.Stack; /** * Abstract superclass that defines a template for sub implementations that can compare objects of a * certain kind. Different instances of different subtypes will be chained to obtain a reflection * comparator chain. This chain will compare two objects with eachother through reflection. * Depending on the composition of the chain, a number of 'leniency levels' are in operation. <p/> * If the check indicates that both objects are not equal, the first (and only the first!) found * difference is returned. The actual difference can then be retrieved by the fieldStack, leftValue * and rightValue properties. * * @author Tim Ducheyne * @author Filip Neven */ public abstract class ReflectionComparator { /** * Root of the comparator chain. Comparisons must start with calling the getDifference method of * this object */ protected ReflectionComparator rootComparator; /** * Next element in the comparator chain. */ protected ReflectionComparator chainedComparator; /** * Constructs a new instance, with the given comparator as the next element in the chain. Makes * sure that this instance is registered as root comparator of the given chained comparator. * Setting the root comparator gets propagated to all elements in the chain. This way, all * comparators share the same root at all times. * * @param chainedComparator The next comparator in the chain */ public ReflectionComparator(ReflectionComparator chainedComparator) { this.chainedComparator = chainedComparator; setRootComparator(this); } /** * Sets the root comparator. This operation is propagated to all comparators in the chain. This * way, all comparators share the same root at all times. * * @param rootComparator The root comparator, i.e. the first comparator in the chain */ protected void setRootComparator(ReflectionComparator rootComparator) { this.rootComparator = rootComparator; if (chainedComparator != null) { chainedComparator.setRootComparator(rootComparator); } } /** * Indicates whether this ReflectionComparator is able to check whether their is a difference in * the given left and right objects or not. * * @param left The left object * @param right The right object * @return true if this ReflectionComparator is able to check whether their is a difference in the * given left and right objects, false otherwise */ public abstract boolean canHandle(Object left, Object right); /** * Checks whether there is a difference between the left and right objects. Whether there is a * difference, depends on the concrete comparators in the chain. * * @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,new Stack<String>(),new HashSet<TraversedInstancePair>()); } /** * If this ReflectionComparator is able to check whether their is a difference in the given left * and right objects (i.e. {@link #canHandle(Object, Object)} returns true), the objects are * compared. * * @param left The left instance * @param right The right instance * @param fieldStack Stack indicating the path from the root of the object structure to the object * that is currently compared * @param traversedInstancePairs Set with pairs of objects that have been compared with eachother. * A pair of two */ protected Difference getDifference(Object left, Object right, Stack<String> fieldStack, Set<TraversedInstancePair> traversedInstancePairs) { if (isAlreadyTraversedInstancePair(left,right,traversedInstancePairs)) { return null; } if (canHandle(left,right)) { registerTraversedInstancePair(left,right,traversedInstancePairs); return doGetDifference(left,right,fieldStack,traversedInstancePairs); } if (chainedComparator == null) { throw new RuntimeException("No ReflectionComparator found for objects " + left + " and" + right + " at " + fieldStack.toString()); } return chainedComparator.getDifference(left,right,fieldStack,traversedInstancePairs); } /** * Abstract method that makes up the core of a reflection comparator. Implementations should * return a concrete {@link Difference} object when left and right are different, or null * otherwise. This method will only be called if {@link #canHandle(Object, Object)} returns true. * An implementation doesn't have to take care of chaining or circular references. * * @param left The left instance * @param right The right instance * @param fieldStack Stack indicating the path from the root of the object structure to the object * that is currently compared * @param traversedInstancePairs Set with pairs of objects that have been compared with eachother. * A pair of two */ protected abstract Difference doGetDifference(Object left, Object right, Stack<String> fieldStack, Set<TraversedInstancePair> traversedInstancePairs); /** * Checks whether there is no difference between the left and right objects. The meaning of no * difference is determined by the set comparator modes. See class javadoc for more info. * * @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 = rootComparator.getDifference(left,right); return difference == null; } /** * Registers the fact that the given left and right object have been compared, to make sure the * same two objects will not be compared again (to avoid infinite loops in case of circular * references) * * @param left the left instance * @param right the right instance * @param traversedInstancePairs Set with pairs of objects that have been compared with eachother. * A pair of two same objects will not be compared again, in order to avoid infinite * loops */ private void registerTraversedInstancePair(Object left, Object right, Set<TraversedInstancePair> traversedInstancePairs) { if (left != null && right != null) { traversedInstancePairs.add(new TraversedInstancePair(left,right)); } } /** * Checks whether the given left and right object have already been compared, according to the * given set of traversedInstancePairs. * * @param left the left instance * @param right the right instance * @param traversedInstancePairs Set with pairs of objects that have been compared with eachother. * A pair of two same objects will not be compared again, in order to avoid infinite * loops * @return true if the given left and right object have already been compared */ protected static boolean isAlreadyTraversedInstancePair(Object left, Object right, Set<TraversedInstancePair> traversedInstancePairs) { if (left == null || right == null) { return false; } return traversedInstancePairs.contains(new TraversedInstancePair(left,right)); } /** * A class for holding the difference between two objects. */ public static class Difference { /* A message describing the difference */ private String message; /* * When isEquals is false this will contain the stack of the fieldnames where the difference was * found. <br> The inner most field will be the top of the stack, eg "primitiveFieldInB", * "fieldBinA", "fieldA". */ private Stack<String> fieldStack; /* * When isEquals is false this will contain the left value of the field where the difference was * found. */ private Object leftValue; /* * When isEquals is false, this will contain the right value of the field where the difference * was found. */ private Object rightValue; /** * Creates a difference. * * @param message a message describing the difference * @param leftValue the left instance * @param rightValue the right instance * @param fieldStack the current field names */ protected Difference(String message, Object leftValue, Object rightValue, Stack<String> fieldStack) { this.message = message; this.leftValue = leftValue; this.rightValue = rightValue; this.fieldStack = fieldStack; } /** * Gets a string representation of the field stack. Eg primitiveFieldInB.fieldBinA.fieldA The * top-level element is an empty string. * * @return the field names as sting */ public String getFieldStackAsString() { String result = ""; Iterator<String> iterator = fieldStack.iterator(); while (iterator.hasNext()) { result += iterator.next(); if (iterator.hasNext()) { result += "."; } } return result; } /** * Gets the message indicating the kind of difference. * * @return the message */ public String getMessage() { return message; } /** * Gets the stack of the fieldnames where the difference was found. The inner most field will be * the top of the stack, eg "primitiveFieldInB", "fieldBinA", "fieldA". The top-level element * has an empty stack. * * @return the stack of field names, not null */ public Stack<String> getFieldStack() { return fieldStack; } /** * Gets the left value of the field where the difference was found. * * @return the value */ public Object getLeftValue() { return leftValue; } /** * Gets the right value of the field where the difference was found. * * @return the value */ public Object getRightValue() { return rightValue; } } /** * Value object that represents a pair of objects that have been compared with eachother. Two * instances of this class are equal when the leftObject and rightObject fields reference the same * instances. */ protected static class TraversedInstancePair { /** * The left object */ private Object leftObject; /** * The right object */ private Object rightObject; /** * Constructs a new instance with the given left and right object * * @param leftObject the left instance * @param rightObject the right instance */ public TraversedInstancePair(Object leftObject, Object rightObject) { this.leftObject = leftObject; this.rightObject = rightObject; } /** * @return The left instance */ public Object getLeftObject() { return leftObject; } /** * @return The right instance */ public Object getRightObject() { return rightObject; } /** * @param o Another object * @return true when the other object is a TraversedInstancePair with the same left and right * object instances. */ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TraversedInstancePair that = (TraversedInstancePair) o; if (!(leftObject == that.leftObject)) return false; if (!(rightObject == that.rightObject)) return false; return true; } /** * @return This object's hashcode */ public int hashCode() { int result; result = leftObject.hashCode(); result = 31 * result + rightObject.hashCode(); return result; } } private ObjectComparator findObjComparator() { if (this instanceof ObjectComparator) return (ObjectComparator) this; if (chainedComparator != null) return chainedComparator.findObjComparator(); return null; } public void addExclusions(Class<?> clazz, String...fieldNames) { ObjectComparator o = findObjComparator(); if (o != null) o.addExclusions(clazz,fieldNames); } public boolean areEqual(Object left, Object right) { Difference diff = getDifference(left,right); //For debugging purposes only if (diff != null) { System.out.println(diff.getMessage()); System.out.println(diff.getFieldStackAsString()); } return diff == null; } }