/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.utility.diff; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.persistence.tools.workbench.utility.ClassTools; import org.eclipse.persistence.tools.workbench.utility.CollectionTools; import org.eclipse.persistence.tools.workbench.utility.string.StringTools; /** * Given a pair of objects to compare, use Java reflection to compare the * objects' field values. Allow for field-specific differentiators. A field can * be specified a "reference" field, meaning that only the "keys" of the fields' * values will be compared (via Differentiator#keyDiff(Object, Object)); * as opposed to a "composite" field, where all the values of the field's fields * are compared normally. By default, all fields are considered "composite" * fields. * * By default the "key" differentiator is another "reflective" differentiator * that compares the values of the "key" fields. This "key" differentiator * can be replaced if the objects' keys are not directly represented by * one or more fields. * * Unless overridden, "container" fields get type-specific differentiators. * Any fields whose declared types are compatible with List, Collection, * Map get the appropriate differentiators. The same applies to any * fields whose declared type is an array. * * @see DiffEngine * TODO add support for multi-dimensional arrays and collections */ public class ReflectiveDifferentiator implements Differentiator { /** the class of objects the differentiator will compare */ private final Class javaClass; /** * the default field differentiator, used to compare the * fields with no field-specific differentiator specified; * typically this is either an EqualityDifferentiator or a * DiffEngine.RecordingDifferentiator */ private final Differentiator defaultFieldDifferentiator; /** the field differentiators, keyed by field */ private final Map fieldDifferentiators; /** * the key differentiator; by default this compares "key" fields */ private KeyDifferentiator keyDifferentiator; /** * by default this is false; but some "value" objects implement #equals(Object) * "incorrectly", so we must use a reflective differentiator to compare them and * set this flag to true */ private boolean comparesValueObjects; /** * specialized differentiators for "container" fields; * these are lazy-initialized so they are only instantiated when * actually needed */ private OrderedContainerDifferentiator listDifferentiator; private OrderedContainerDifferentiator arrayDifferentiator; private ContainerDifferentiator collectionDifferentiator; private ContainerDifferentiator unorderedArrayDifferentiator; private ContainerDifferentiator mapDifferentiator; // ********** constructors/initialization ********** public ReflectiveDifferentiator(Class javaClass) { this(javaClass, EqualityDifferentiator.instance()); } public ReflectiveDifferentiator(Class javaClass, Differentiator defaultFieldDifferentiator) { super(); if (javaClass.isInterface()) { throw new IllegalArgumentException("interfaces cannot be compared reflectively: " + javaClass.getName()); } this.javaClass = javaClass; if (defaultFieldDifferentiator == null) { throw new NullPointerException(); } this.defaultFieldDifferentiator = defaultFieldDifferentiator; // TODO add 'compareStaticFields' setting to constructor parms? this.fieldDifferentiators = this.buildDefaultFieldDifferentiators(false); this.keyDifferentiator = new DefaultKeyDifferentiator(); this.comparesValueObjects = false; } private Map buildDefaultFieldDifferentiators(boolean compareStaticFields) { Map differentiators = new HashMap(); Field[] declaredFields = this.javaClass.getDeclaredFields(); for (int i = declaredFields.length; i-- > 0; ) { Field field = declaredFields[i]; field.setAccessible(true); if (Modifier.isStatic(field.getModifiers()) && ! compareStaticFields) { continue; // skip static fields unless the flag is true } differentiators.put(field, this.defaultFieldDifferentiator(field)); } return differentiators; } /** * return the default differentiator for the specified field */ private Differentiator defaultFieldDifferentiator(Field field) { Class fieldType = field.getType(); if (List.class.isAssignableFrom(fieldType)) { return this.getListDifferentiator(); } if (Collection.class.isAssignableFrom(fieldType)) { return this.getCollectionDifferentiator(); } if (Map.class.isAssignableFrom(fieldType)) { return this.getMapDifferentiator(); } if (fieldType.isArray()) { return this.getArrayDifferentiator(); } return this.defaultFieldDifferentiator; } /** * return the default differentiator for the specified field */ private Differentiator defaultFieldDifferentiator(String fieldName) { return this.defaultFieldDifferentiator(this.field(fieldName)); } // ********** Differentiator implementation ********** /** * @see Differentiator#diff(Object, Object) */ public Diff diff(Object object1, Object object2) { return this.diff(object1, object2, this.fieldDifferentiators, DifferentiatorAdapter.NORMAL); } /** * @see Differentiator#keyDiff(Object, Object) */ public Diff keyDiff(Object object1, Object object2) { return this.keyDifferentiator.keyDiff(object1, object2); } Diff diff(Object object1, Object object2, Map differentiators, DifferentiatorAdapter adapter) { if (object1 == object2) { return new NullDiff(object1, object2, this); } if (this.diffIsFatal(object1, object2)) { return new SimpleDiff(object1, object2, this.fatalDescriptionTitle(), this); } ReflectiveFieldDiff[] diffs = new ReflectiveFieldDiff[differentiators.size()]; int i = 0; for (Iterator stream = differentiators.entrySet().iterator(); stream.hasNext(); ) { Map.Entry entry = (Map.Entry) stream.next(); Field field = (Field) entry.getKey(); Differentiator fieldDifferentiator = (Differentiator) entry.getValue(); Object fieldValue1 = this.fieldValue(field, object1); Object fieldValue2 = this.fieldValue(field, object2); Diff fieldDiff = adapter.diff(fieldDifferentiator, fieldValue1, fieldValue2); diffs[i++] = new ReflectiveFieldDiff(field, fieldDiff, this); } // put the field diffs in alphabetical order return new ReflectiveDiff(this.javaClass, object1, object2, (ReflectiveFieldDiff[]) CollectionTools.sort(diffs), this); } /** * @see Differentiator#comparesValueObjects() */ public boolean comparesValueObjects() { return this.comparesValueObjects; } // ********** default container field differentiators ********** private OrderedContainerDifferentiator getListDifferentiator() { if (this.listDifferentiator == null) { this.listDifferentiator = OrderedContainerDifferentiator.forLists(this.defaultFieldDifferentiator); } return this.listDifferentiator; } private OrderedContainerDifferentiator getArrayDifferentiator() { if (this.arrayDifferentiator == null) { this.arrayDifferentiator = OrderedContainerDifferentiator.forArrays(this.defaultFieldDifferentiator); } return this.arrayDifferentiator; } private ContainerDifferentiator getCollectionDifferentiator() { if (this.collectionDifferentiator == null) { this.collectionDifferentiator = ContainerDifferentiator.forCollections(this.defaultFieldDifferentiator); } return this.collectionDifferentiator; } private ContainerDifferentiator getUnorderedArrayDifferentiator() { if (this.unorderedArrayDifferentiator == null) { this.unorderedArrayDifferentiator = ContainerDifferentiator.forArrays(this.defaultFieldDifferentiator); } return this.unorderedArrayDifferentiator; } private ContainerDifferentiator getMapDifferentiator() { if (this.mapDifferentiator == null) { this.mapDifferentiator = ContainerDifferentiator.forMaps(this.defaultFieldDifferentiator, this.defaultFieldDifferentiator); } return this.mapDifferentiator; } // ********** queries ********** public Class getJavaClass() { return this.javaClass; } public Differentiator getDefaultFieldDifferentiator() { return this.defaultFieldDifferentiator; } public KeyDifferentiator getKeyDifferentiator() { return this.keyDifferentiator; } private boolean diffIsFatal(Object object1, Object object2) { if (object1 == null) { return true; } if (object2 == null) { return true; } return (object1.getClass() != object2.getClass()); } protected String fatalDescriptionTitle() { return "Objects cannot be compared via reflection (" + this.javaClass.getName() + ")"; } /** * @see Object#toString() */ public String toString() { return StringTools.buildToStringFor(this, ClassTools.shortNameFor(this.javaClass)); } // ********** behavior ********** /** * allow clients to replace the default key differentiator */ public void setKeyDifferentiator(KeyDifferentiator keyDifferentiator) { this.keyDifferentiator = keyDifferentiator; } /** * allow clients to configure the differentiator for comparing "value" objects */ public void setComparesValueObjects(boolean comparesValueObjects) { this.comparesValueObjects = comparesValueObjects; } public Differentiator setFieldDifferentiator(String fieldName, Differentiator differentiator) { return this.setFieldDifferentiator(this.field(fieldName), differentiator); } private Differentiator setFieldDifferentiator(Field field, Differentiator differentiator) { if (differentiator == null) { throw new NullPointerException(); } Object prev = this.fieldDifferentiators.put(field, differentiator); if (prev != this.defaultFieldDifferentiator(field)) { throw new IllegalArgumentException("duplicate field differentiator: " + field.getName()); } return differentiator; } public Differentiator replaceFieldDifferentiator(String fieldName, Differentiator differentiator) { return this.replaceFieldDifferentiator(this.field(fieldName), differentiator); } private Differentiator replaceFieldDifferentiator(Field field, Differentiator differentiator) { if (differentiator == null) { throw new NullPointerException(); } Object prev = this.fieldDifferentiators.put(field, differentiator); if (prev == this.defaultFieldDifferentiator(field)) { throw new IllegalArgumentException("field differentiator not present: " + field.getName()); } return differentiator; } public Differentiator getFieldDifferentiator(String fieldName) { return (Differentiator) this.fieldDifferentiators.get(this.field(fieldName)); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public Differentiator setKeyFieldDifferentiator(String fieldName, Differentiator differentiator) { return this.setKeyFieldDifferentiator(this.field(fieldName), differentiator); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ private Differentiator setKeyFieldDifferentiator(Field field, Differentiator differentiator) { if (this.keyDifferentiator instanceof DefaultKeyDifferentiator) { return ((DefaultKeyDifferentiator) this.keyDifferentiator).setKeyFieldDifferentiator(field, differentiator); } throw new IllegalStateException("the default key differentiator is not being used"); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public Differentiator replaceKeyFieldDifferentiator(String fieldName, Differentiator differentiator) { return this.replaceKeyFieldDifferentiator(this.field(fieldName), differentiator); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ private Differentiator replaceKeyFieldDifferentiator(Field field, Differentiator differentiator) { if (this.keyDifferentiator instanceof DefaultKeyDifferentiator) { return ((DefaultKeyDifferentiator) this.keyDifferentiator).replaceKeyFieldDifferentiator(field, differentiator); } throw new IllegalStateException("the default key differentiator is not being used"); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public Differentiator getKeyFieldDifferentiator(String fieldName) { if (this.keyDifferentiator instanceof DefaultKeyDifferentiator) { return ((DefaultKeyDifferentiator) this.keyDifferentiator).getKeyFieldDifferentiator(this.field(fieldName)); } throw new IllegalStateException("the default key differentiator is not being used"); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public Differentiator removeKeyFieldDifferentiator(String fieldName) { if (this.keyDifferentiator instanceof DefaultKeyDifferentiator) { return ((DefaultKeyDifferentiator) this.keyDifferentiator).removeKeyFieldDifferentiator(this.field(fieldName)); } throw new IllegalStateException("the default key differentiator is not being used"); } /** * "checked" exceptions suck */ private Object fieldValue(Field field, Object object) { try { return field.get(object); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } } /** * "checked" exceptions suck */ private Field field(String fieldName) { Field field = null; try { field = this.javaClass.getDeclaredField(fieldName); } catch (NoSuchFieldException ex) { throw new IllegalArgumentException(fieldName); } field.setAccessible(true); return field; } // ********** ignored fields ********** public NullDifferentiator ignoreFieldNamed(String fieldName) { return (NullDifferentiator) this.setFieldDifferentiator(fieldName, NullDifferentiator.instance()); } public NullDifferentiator ignoreFieldsNamed(String fieldName) { return this.ignoreFieldNamed(fieldName); } public void ignoreFieldsNamed(String fieldName1, String fieldName2) { this.ignoreFieldsNamed(new String[] {fieldName1, fieldName2}); } public void ignoreFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.ignoreFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void ignoreFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.ignoreFieldNamed(fieldNames[i]); } } // ********** key fields ********** /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public Differentiator addKeyFieldNamed(String fieldName) { return this.setKeyFieldDifferentiator(fieldName, this.defaultFieldDifferentiator(fieldName)); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public Differentiator addKeyFieldsNamed(String fieldName) { return this.addKeyFieldNamed(fieldName); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public void addKeyFieldsNamed(String fieldName1, String fieldName2) { this.addKeyFieldsNamed(new String[] {fieldName1, fieldName2}); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public void addKeyFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addKeyFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } /** * convenience method, when using the default key differentiator; * throw an IllegalStateException if the default key differentiator is not being used */ public void addKeyFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addKeyFieldNamed(fieldNames[i]); } } // ********** reference (as opposed to composite) fields ********** private ReferenceDifferentiator addReferenceFieldNamed(String fieldName, Differentiator differentiator) { return (ReferenceDifferentiator) this.setFieldDifferentiator(fieldName, new ReferenceDifferentiator(differentiator)); } public Differentiator addReferenceFieldNamed(String fieldName) { return this.addReferenceFieldNamed(fieldName, this.defaultFieldDifferentiator(fieldName)); } public Differentiator addReferenceFieldsNamed(String fieldName) { return this.addReferenceFieldNamed(fieldName); } public void addReferenceFieldsNamed(String fieldName1, String fieldName2) { this.addReferenceFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addReferenceFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addReferenceFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addReferenceFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addReferenceFieldNamed(fieldNames[i]); } } // ********** (composite) list fields ********** public OrderedContainerDifferentiator addListFieldNamed(String fieldName) { return (OrderedContainerDifferentiator) this.setFieldDifferentiator(fieldName, this.getListDifferentiator()); } public OrderedContainerDifferentiator addListFieldsNamed(String fieldName) { return this.addListFieldNamed(fieldName); } public void addListFieldsNamed(String fieldName1, String fieldName2) { this.addListFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addListFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addListFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addListFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addListFieldNamed(fieldNames[i]); } } // ********** reference (as opposed to composite) list fields ********** public ReferenceDifferentiator addReferenceListFieldNamed(String fieldName) { return this.addReferenceFieldNamed(fieldName, this.getListDifferentiator()); } public ReferenceDifferentiator addReferenceListFieldsNamed(String fieldName) { return this.addReferenceListFieldNamed(fieldName); } public void addReferenceListFieldsNamed(String fieldName1, String fieldName2) { this.addReferenceListFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addReferenceListFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addReferenceListFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addReferenceListFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addReferenceListFieldNamed(fieldNames[i]); } } // ********** (composite) array fields ********** public OrderedContainerDifferentiator addArrayFieldNamed(String fieldName) { return (OrderedContainerDifferentiator) this.setFieldDifferentiator(fieldName, this.getArrayDifferentiator()); } public OrderedContainerDifferentiator addArrayFieldsNamed(String fieldName) { return this.addArrayFieldNamed(fieldName); } public void addArrayFieldsNamed(String fieldName1, String fieldName2) { this.addArrayFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addArrayFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addArrayFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addArrayFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addArrayFieldNamed(fieldNames[i]); } } // ********** reference (as opposed to composite) array fields ********** public ReferenceDifferentiator addReferenceArrayFieldNamed(String fieldName) { return this.addReferenceFieldNamed(fieldName, this.getArrayDifferentiator()); } public ReferenceDifferentiator addReferenceArrayFieldsNamed(String fieldName) { return this.addReferenceArrayFieldNamed(fieldName); } public void addReferenceArrayFieldsNamed(String fieldName1, String fieldName2) { this.addReferenceArrayFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addReferenceArrayFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addReferenceArrayFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addReferenceArrayFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addReferenceArrayFieldNamed(fieldNames[i]); } } // ********** (composite) collection fields ********** public ContainerDifferentiator addCollectionFieldNamed(String fieldName) { return (ContainerDifferentiator) this.setFieldDifferentiator(fieldName, this.getCollectionDifferentiator()); } public ContainerDifferentiator addCollectionFieldsNamed(String fieldName) { return this.addCollectionFieldNamed(fieldName); } public void addCollectionFieldsNamed(String fieldName1, String fieldName2) { this.addCollectionFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addCollectionFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addCollectionFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addCollectionFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addCollectionFieldNamed(fieldNames[i]); } } // ********** reference (as opposed to composite) collection fields ********** public ReferenceDifferentiator addReferenceCollectionFieldNamed(String fieldName) { return this.addReferenceFieldNamed(fieldName, this.getCollectionDifferentiator()); } public ReferenceDifferentiator addReferenceCollectionFieldsNamed(String fieldName) { return this.addReferenceCollectionFieldNamed(fieldName); } public void addReferenceCollectionFieldsNamed(String fieldName1, String fieldName2) { this.addReferenceCollectionFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addReferenceCollectionFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addReferenceCollectionFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addReferenceCollectionFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addReferenceCollectionFieldNamed(fieldNames[i]); } } // ********** (composite) unordered array fields ********** public ContainerDifferentiator addUnorderedArrayFieldNamed(String fieldName) { return (ContainerDifferentiator) this.setFieldDifferentiator(fieldName, this.getUnorderedArrayDifferentiator()); } public ContainerDifferentiator addUnorderedArrayFieldsNamed(String fieldName) { return this.addUnorderedArrayFieldNamed(fieldName); } public void addUnorderedArrayFieldsNamed(String fieldName1, String fieldName2) { this.addUnorderedArrayFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addUnorderedArrayFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addUnorderedArrayFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addUnorderedArrayFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addUnorderedArrayFieldNamed(fieldNames[i]); } } // ********** reference (as opposed to composite) unordered array fields ********** public ReferenceDifferentiator addReferenceUnorderedArrayFieldNamed(String fieldName) { return this.addReferenceFieldNamed(fieldName, this.getUnorderedArrayDifferentiator()); } public ReferenceDifferentiator addReferenceUnorderedArrayFieldsNamed(String fieldName) { return this.addReferenceUnorderedArrayFieldNamed(fieldName); } public void addReferenceUnorderedArrayFieldsNamed(String fieldName1, String fieldName2) { this.addReferenceUnorderedArrayFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addReferenceUnorderedArrayFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addReferenceUnorderedArrayFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addReferenceUnorderedArrayFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addReferenceUnorderedArrayFieldNamed(fieldNames[i]); } } // ********** (composite) map fields ********** public ContainerDifferentiator addMapFieldNamed(String fieldName) { return (ContainerDifferentiator) this.setFieldDifferentiator(fieldName, this.getMapDifferentiator()); } public ContainerDifferentiator addMapFieldsNamed(String fieldName) { return this.addMapFieldNamed(fieldName); } public void addMapFieldsNamed(String fieldName1, String fieldName2) { this.addMapFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addMapFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addMapFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addMapFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addMapFieldNamed(fieldNames[i]); } } // ********** reference (as opposed to composite) map fields ********** public ReferenceDifferentiator addReferenceMapFieldNamed(String fieldName) { return this.addReferenceFieldNamed(fieldName, this.getMapDifferentiator()); } public ReferenceDifferentiator addReferenceMapFieldsNamed(String fieldName) { return this.addReferenceMapFieldNamed(fieldName); } public void addReferenceMapFieldsNamed(String fieldName1, String fieldName2) { this.addReferenceMapFieldsNamed(new String[] {fieldName1, fieldName2}); } public void addReferenceMapFieldsNamed(String fieldName1, String fieldName2, String fieldName3) { this.addReferenceMapFieldsNamed(new String[] {fieldName1, fieldName2, fieldName3}); } public void addReferenceMapFieldsNamed(String[] fieldNames) { for (int i = fieldNames.length; i-- > 0; ) { this.addReferenceMapFieldNamed(fieldNames[i]); } } // ********** pluggable interface for the "key" differentiator ********** /** * This defines the interface required of a "key" differentiator. The "key" * differentiator is used by the reflective differentiator when it is * executing a "key" diff. * See the default implementation below. */ public interface KeyDifferentiator { Diff keyDiff(Object object1, Object object2); } /** * The default "key" differentiator uses a set of user-specified * "key field" differentiators to compare a subset of the objects' * fields. Typically it will use the parent reflective differentiator's * default field differentiator. * * @see ReflectiveDifferentiator#addKeyFieldNamed(String) and * related methods */ private class DefaultKeyDifferentiator implements KeyDifferentiator { /** the key field differentiators, keyed by field; if this is empty, there is no "primary key" */ private Map keyFieldDifferentiators; DefaultKeyDifferentiator() { super(); this.keyFieldDifferentiators = new HashMap(); } public Diff keyDiff(Object object1, Object object2) { // borrow some of ReflectiveDifferentiator's code return ReflectiveDifferentiator.this.diff(object1, object2, this.keyFieldDifferentiators, DifferentiatorAdapter.KEY); } Differentiator setKeyFieldDifferentiator(Field field, Differentiator differentiator) { Object prev = this.replaceKeyFieldDifferentiator(field, differentiator); if (prev != null) { throw new IllegalArgumentException("duplicate key field differentiator: " + field.getName()); } return differentiator; } Differentiator replaceKeyFieldDifferentiator(Field field, Differentiator differentiator) { if (differentiator == null) { throw new NullPointerException(); } return (Differentiator) this.keyFieldDifferentiators.put(field, differentiator); } Differentiator getKeyFieldDifferentiator(Field field) { return (Differentiator) this.keyFieldDifferentiators.get(field); } Differentiator removeKeyFieldDifferentiator(Field field) { return (Differentiator) this.keyFieldDifferentiators.remove(field); } } /** * This key differentiator will reflectively invoke the specified method * on both of the objects and diff the return values with an equality * differentiator. Subclasses can override #diffKeys(Object, Object) * to use a different differentiator. */ public static class SimpleMethodKeyDifferentiator implements KeyDifferentiator { protected String methodName; protected static final Object NULL_KEY = new Object(); public SimpleMethodKeyDifferentiator(String methodName) { super(); this.methodName = methodName; } public Diff keyDiff(Object object1, Object object2) { return this.diffKeys(this.key(object1), this.key(object2)); } protected Diff diffKeys(Object key1, Object key2) { return EqualityDifferentiator.instance().diff(key1, key2); } /** * If the object is null, we return a unique key that can be used * to match up null objects. */ protected Object key(Object object) { return (object == null) ? NULL_KEY : ClassTools.invokeMethod(object, this.methodName); } protected Object nullKey() { return NULL_KEY; } public String toString() { return StringTools.buildToStringFor(this, this.methodName); } } }