001: /*
002: * Copyright 2006-2007, Unitils.org
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.unitils.reflectionassert;
017:
018: import org.unitils.core.UnitilsException;
019:
020: import static java.lang.Boolean.TRUE;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.Stack;
025:
026: /**
027: * Abstract superclass that defines a template for sub implementations that can compare objects of a certain kind.
028: * Different instances of different subtypes will be chained to obtain a reflection comparator chain. This chain
029: * will compare two objects with eachother through reflection. Depending on the composition of the chain, a number
030: * of 'leniency levels' are in operation.
031: * <p/>
032: * If the check indicates that both objects are not equal, the first (and only the first!) found difference is returned.
033: * The actual difference can then be retrieved by the fieldStack, leftValue and rightValue properties.
034: *
035: * @author Tim Ducheyne
036: * @author Filip Neven
037: */
038: abstract public class ReflectionComparator {
039:
040: /**
041: * Root of the comparator chain. Comparisons must start with calling the getDifference method of this object
042: */
043: protected ReflectionComparator rootComparator;
044:
045: /**
046: * Next element in the comparator chain.
047: */
048: protected ReflectionComparator chainedComparator;
049:
050: /**
051: * Constructs a new instance, with the given comparator as the next element in the chain. Makes sure that this
052: * instance is registered as root comparator of the given chained comparator. Setting the root comparator gets
053: * propagated to all elements in the chain. This way, all comparators share the same root at all times.
054: *
055: * @param chainedComparator The next comparator in the chain
056: */
057: public ReflectionComparator(ReflectionComparator chainedComparator) {
058: this .chainedComparator = chainedComparator;
059: setRootComparator(this );
060: }
061:
062: /**
063: * Sets the root comparator. This operation is propagated to all comparators in the chain. This way, all comparators
064: * share the same root at all times.
065: *
066: * @param rootComparator The root comparator, i.e. the first comparator in the chain
067: */
068: protected void setRootComparator(ReflectionComparator rootComparator) {
069: this .rootComparator = rootComparator;
070: if (chainedComparator != null) {
071: chainedComparator.setRootComparator(rootComparator);
072: }
073: }
074:
075: /**
076: * Indicates whether this ReflectionComparator is able to check whether their is a difference in the given left
077: * and right objects or not.
078: *
079: * @param left The left object
080: * @param right The right object
081: * @return true if this ReflectionComparator is able to check whether their is a difference in the given left
082: * and right objects, false otherwise
083: */
084: abstract public boolean canHandle(Object left, Object right);
085:
086: /**
087: * Checks whether there is a difference between the left and right objects. Whether there is a difference, depends
088: * on the concrete comparators in the chain.
089: *
090: * @param left the left instance
091: * @param right the right instance
092: * @return the difference, null if there is no difference
093: */
094: public Difference getDifference(Object left, Object right) {
095: return getDifference(left, right, new Stack<String>(),
096: new HashMap<TraversedInstancePair, Boolean>());
097: }
098:
099: /**
100: * If this ReflectionComparator is able to check whether their is a difference in the given left
101: * and right objects (i.e. {@link #canHandle(Object,Object)} returns true), the objects are compared.
102: *
103: * @param left The left instance
104: * @param right The right instance
105: * @param fieldStack Stack indicating the path from the root of the object structure to the object that is currently
106: * compared
107: * @param traversedInstancePairs Set with pairs of objects that have been compared with eachother. A pair of two
108: * @return The difference, null if there is no difference
109: */
110: protected Difference getDifference(Object left, Object right,
111: Stack<String> fieldStack,
112: Map<TraversedInstancePair, Boolean> traversedInstancePairs) {
113: if (isTraversedInstancePairEqual(left, right,
114: traversedInstancePairs)) {
115: return null;
116: }
117:
118: if (canHandle(left, right)) {
119: registerTraversedInstancePair(left, right, true,
120: traversedInstancePairs);
121: Difference difference = doGetDifference(left, right,
122: fieldStack, traversedInstancePairs);
123: registerTraversedInstancePair(left, right,
124: (difference == null), traversedInstancePairs);
125: return difference;
126: } else {
127: if (chainedComparator == null) {
128: throw new UnitilsException(
129: "No ReflectionComparator found for objects "
130: + left + " and" + right + " at "
131: + fieldStack.toString());
132: } else {
133: return chainedComparator.getDifference(left, right,
134: fieldStack, traversedInstancePairs);
135: }
136: }
137: }
138:
139: /**
140: * Abstract method that makes up the core of a reflection comparator. Implementations should return a concrete
141: * {@link Difference} object when left and right are different, or null otherwise. This method will only be called
142: * if {@link #canHandle(Object,Object)} returns true. An implementation doesn't have to take care of chaining or
143: * circular references.
144: *
145: * @param left The left instance
146: * @param right The right instance
147: * @param fieldStack Stack indicating the path from the root of the object structure to the object that is currently
148: * compared
149: * @param traversedInstancePairs Set with pairs of objects that have been compared with eachother. A pair of two
150: * @return The difference, null if there is no difference
151: */
152: abstract protected Difference doGetDifference(Object left,
153: Object right, Stack<String> fieldStack,
154: Map<TraversedInstancePair, Boolean> traversedInstancePairs);
155:
156: /**
157: * Checks whether there is no difference between the left and right objects. The meaning of no difference is
158: * determined by the set comparator modes. See class javadoc for more info.
159: *
160: * @param left the left instance
161: * @param right the right instance
162: * @return true if there is no difference, false otherwise
163: */
164: public boolean isEqual(Object left, Object right) {
165: Difference difference = rootComparator.getDifference(left,
166: right);
167: return difference == null;
168: }
169:
170: /**
171: * Registers the fact that the given left and right object have been compared, to make sure the same two objects
172: * will not be compared again (to avoid infinite loops in case of circular references)
173: *
174: * @param left The left instance
175: * @param right The right instance
176: * @param outcome The outcome of the comparison
177: * @param traversedInstancePairs Map with pairs of objects that have been compared with each other.
178: */
179: protected void registerTraversedInstancePair(Object left,
180: Object right, boolean outcome,
181: Map<TraversedInstancePair, Boolean> traversedInstancePairs) {
182: if (left == null || right == null) {
183: return;
184: }
185: traversedInstancePairs.put(new TraversedInstancePair(left,
186: right), outcome);
187: }
188:
189: /**
190: * Checks whether the given left and right object have already been compared, according to the given set of
191: * traversedInstancePairs. If so, this will return the outcome of the comparison. False will be returned
192: * if the pair was not yet compared or is being compared.
193: *
194: * @param left the left instance
195: * @param right the right instance
196: * @param traversedInstancePairs Map with pairs of objects that have been compared with each other.
197: * @return true if already compared and equal, false otherwise
198: */
199: protected boolean isTraversedInstancePairEqual(Object left,
200: Object right,
201: Map<TraversedInstancePair, Boolean> traversedInstancePairs) {
202: if (left == null || right == null) {
203: return false;
204: }
205: return traversedInstancePairs.get(new TraversedInstancePair(
206: left, right)) == TRUE;
207: }
208:
209: /**
210: * A class for holding the difference between two objects.
211: */
212: public static class Difference {
213:
214: /* A message describing the difference */
215: protected String message;
216:
217: /* When isEquals is false this will contain the stack of the fieldnames where the difference was found. <br>
218: * The inner most field will be the top of the stack, eg "primitiveFieldInB", "fieldBinA", "fieldA". */
219: protected Stack<String> fieldStack;
220:
221: /* When isEquals is false this will contain the left value of the field where the difference was found. */
222: protected Object leftValue;
223:
224: /* When isEquals is false, this will contain the right value of the field where the difference was found. */
225: protected Object rightValue;
226:
227: /**
228: * Creates a difference.
229: *
230: * @param message a message describing the difference
231: * @param leftValue the left instance
232: * @param rightValue the right instance
233: * @param fieldStack the current field names
234: */
235: public Difference(String message, Object leftValue,
236: Object rightValue, Stack<String> fieldStack) {
237: this .message = message;
238: this .leftValue = leftValue;
239: this .rightValue = rightValue;
240: this .fieldStack = fieldStack;
241: }
242:
243: /**
244: * Gets a string representation of the field stack.
245: * Eg primitiveFieldInB.fieldBinA.fieldA
246: * The top-level element is an empty string.
247: *
248: * @return the field names as sting
249: */
250: public String getFieldStackAsString() {
251: String result = "";
252: Iterator<?> iterator = fieldStack.iterator();
253: while (iterator.hasNext()) {
254: result += iterator.next();
255: if (iterator.hasNext()) {
256: result += ".";
257: }
258: }
259: return result;
260: }
261:
262: /**
263: * Gets the message indicating the kind of difference.
264: *
265: * @return the message
266: */
267: public String getMessage() {
268: return message;
269: }
270:
271: /**
272: * Gets the stack of the fieldnames where the difference was found.
273: * The inner most field will be the top of the stack, eg "primitiveFieldInB", "fieldBinA", "fieldA".
274: * The top-level element has an empty stack.
275: *
276: * @return the stack of field names, not null
277: */
278: public Stack<String> getFieldStack() {
279: return fieldStack;
280: }
281:
282: /**
283: * Gets the left value of the field where the difference was found.
284: *
285: * @return the value
286: */
287: public Object getLeftValue() {
288: return leftValue;
289: }
290:
291: /**
292: * Gets the right value of the field where the difference was found.
293: *
294: * @return the value
295: */
296: public Object getRightValue() {
297: return rightValue;
298: }
299: }
300:
301: /**
302: * Value object that represents a pair of objects that have been compared with eachother. Two instances of this
303: * class are equal when the leftObject and rightObject fields reference the same instances.
304: */
305: protected static class TraversedInstancePair {
306:
307: /* The left object */
308: private Object leftObject;
309:
310: /* The right object */
311: private Object rightObject;
312:
313: /**
314: * Constructs a new instance with the given left and right object
315: *
316: * @param leftObject the left instance
317: * @param rightObject the right instance
318: */
319: public TraversedInstancePair(Object leftObject,
320: Object rightObject) {
321: this .leftObject = leftObject;
322: this .rightObject = rightObject;
323: }
324:
325: /**
326: * @return The left instance
327: */
328: public Object getLeftObject() {
329: return leftObject;
330: }
331:
332: /**
333: * @return The right instance
334: */
335: public Object getRightObject() {
336: return rightObject;
337: }
338:
339: /**
340: * @param o Another object
341: * @return true when the other object is a TraversedInstancePair with the same left and right object instances.
342: */
343: @Override
344: public boolean equals(Object o) {
345: if (this == o)
346: return true;
347: if (o == null || getClass() != o.getClass())
348: return false;
349:
350: TraversedInstancePair that = (TraversedInstancePair) o;
351:
352: if (!(leftObject == that.leftObject))
353: return false;
354: return rightObject == that.rightObject;
355: }
356:
357: /**
358: * @return This object's hashcode
359: */
360: @Override
361: public int hashCode() {
362: int result;
363: result = leftObject.hashCode();
364: result = 31 * result + rightObject.hashCode();
365: return result;
366: }
367: }
368: }
|