diff --git a/src/main/java/de/danielbechler/diff/BeanDiffer.java b/src/main/java/de/danielbechler/diff/BeanDiffer.java index 430e7289..8977b390 100644 --- a/src/main/java/de/danielbechler/diff/BeanDiffer.java +++ b/src/main/java/de/danielbechler/diff/BeanDiffer.java @@ -49,6 +49,17 @@ public final Node compare(final Node parentNode, final Instances instances) { beanNode.setState(Node.State.IGNORED); } + else if (nodeInspector.hasEqualsOnlyValueProviderMethod(beanNode)){ + String method = nodeInspector.getEqualsOnlyValueProviderMethod(beanNode); + if (instances.areMethodResultsEqual(method)) + { + beanNode.setState(Node.State.UNTOUCHED); + } + else + { + beanNode.setState(Node.State.CHANGED); + } + } else if (instances.areNull() || instances.areSame()) { beanNode.setState(Node.State.UNTOUCHED); diff --git a/src/main/java/de/danielbechler/diff/CollectionDiffer.java b/src/main/java/de/danielbechler/diff/CollectionDiffer.java index 18a8f325..c8033907 100644 --- a/src/main/java/de/danielbechler/diff/CollectionDiffer.java +++ b/src/main/java/de/danielbechler/diff/CollectionDiffer.java @@ -61,6 +61,17 @@ else if (nodeInspector.isEqualsOnly(collectionNode)) collectionNode.setState(Node.State.CHANGED); } } + else if (nodeInspector.hasEqualsOnlyValueProviderMethod(collectionNode)){ + String method = nodeInspector.getEqualsOnlyValueProviderMethod(collectionNode); + if (collectionInstances.areMethodResultsEqual(method)) + { + collectionNode.setState(Node.State.UNTOUCHED); + } + else + { + collectionNode.setState(Node.State.CHANGED); + } + } else if (collectionInstances.hasBeenAdded()) { compareItems(collectionNode, collectionInstances, collectionInstances.getWorking(Collection.class)); diff --git a/src/main/java/de/danielbechler/diff/Configuration.java b/src/main/java/de/danielbechler/diff/Configuration.java index 5396a2ea..1a2966a0 100644 --- a/src/main/java/de/danielbechler/diff/Configuration.java +++ b/src/main/java/de/danielbechler/diff/Configuration.java @@ -77,8 +77,10 @@ public enum PrimitiveDefaultValueMode private final Collection includedProperties = new HashSet(10); private final Collection excludedProperties = new HashSet(10); private final Collection equalsOnlyProperties = new LinkedHashSet(10); + private final Collection equalsOnlyValueProviderMethods = new LinkedHashSet(10); private final Collection> compareToOnlyTypes = new LinkedHashSet>(10); private final Collection> equalsOnlyTypes = new LinkedHashSet>(10); + private final Collection equalsOnlyValueProviderTypes = new LinkedHashSet(10); private boolean returnUnchangedNodes = false; private boolean returnIgnoredNodes = false; private boolean returnCircularNodes = true; @@ -124,7 +126,7 @@ public Configuration withoutProperty(final PropertyPath propertyPath) this.excludedProperties.add(propertyPath); return this; } - + public Configuration withCompareToOnlyType(final Class type) { this.compareToOnlyTypes.add(type); @@ -143,12 +145,22 @@ public Configuration withEqualsOnlyProperty(final PropertyPath propertyPath) return this; } + public Configuration withEqualsOnlyValueProviderMethod(final PropertyPath propertyPath, final String methodName) { + this.equalsOnlyValueProviderMethods.add(new PropertyPathAndMethod(propertyPath, methodName)); + return this; + } + + public Configuration withEqualsOnlyValueProviderMethod(PropertyPathAndMethod propertyPathEqualsMethod) { + this.equalsOnlyValueProviderMethods.add(propertyPathEqualsMethod); + return this; + } + public Configuration withIgnoredNodes() { this.returnIgnoredNodes = true; return this; } - + public Configuration withoutIgnoredNodes() { this.returnIgnoredNodes = false; @@ -309,6 +321,57 @@ public boolean isEqualsOnly(final Node node) } return false; } + + public boolean hasEqualsOnlyValueProviderMethod(Node node){ + return getEqualsOnlyValueProviderMethod(node) != null; + } + + public String getEqualsOnlyValueProviderMethod(Node node){ + final Class propertyType = node.getType(); + if (propertyType != null) + { + ObjectDiffEqualsOnlyValueProvidedType annotation = propertyType.getAnnotation(ObjectDiffEqualsOnlyValueProvidedType.class); + if (annotation != null) + { + return annotation.method(); + } + + ClassAndMethod applicable = findEqualsOnlyValueProviderMethodForClass(propertyType); + if (applicable != null) + { + return applicable.getMethod(); + } + } + if (node.hasEqualsOnlyValueProviderMethod()) + { + return node.getEqualsOnlyValueProviderMethod(); + } + PropertyPathAndMethod applicable = findEqualsOnlyValueProviderMethodForPath(node.getPropertyPath()); + if (applicable != null) + { + return applicable.getMethod(); + } + return null; + } + + private ClassAndMethod findEqualsOnlyValueProviderMethodForClass(Class clazz){ + for(ClassAndMethod propertyPathEqualsOnValueProviderType: equalsOnlyValueProviderTypes){ + if(clazz.equals(propertyPathEqualsOnValueProviderType.getClazz())){ + return propertyPathEqualsOnValueProviderType; + } + } + return null; + + } + + private PropertyPathAndMethod findEqualsOnlyValueProviderMethodForPath(PropertyPath propertyPath){ + for(PropertyPathAndMethod propertyPathEqualsOnValueProviderMethod: equalsOnlyValueProviderMethods){ + if(propertyPath.equals(propertyPathEqualsOnValueProviderMethod.getPropertyPath())){ + return propertyPathEqualsOnValueProviderMethod; + } + } + return null; + } public boolean isReturnable(final Node node) { @@ -347,4 +410,5 @@ else if (node.isRemoved()) } return true; } + } diff --git a/src/main/java/de/danielbechler/diff/Instances.java b/src/main/java/de/danielbechler/diff/Instances.java index ee255b05..eb09f21d 100644 --- a/src/main/java/de/danielbechler/diff/Instances.java +++ b/src/main/java/de/danielbechler/diff/Instances.java @@ -172,6 +172,19 @@ public boolean areEqual() { return isEqual(base, working); } + + public boolean areMethodResultsEqual(String method) { + try { + Object baseMethodResult = base.getClass().getMethod(method).invoke(base); + Object workingMethodResult = working.getClass().getMethod(method).invoke(working); + if(baseMethodResult == null){ + return workingMethodResult == null; + } + return baseMethodResult.equals(workingMethodResult); + } catch (Exception e) { + throw new RuntimeException(e); + } + } public boolean areEqualByComparison() { diff --git a/src/main/java/de/danielbechler/diff/MapDiffer.java b/src/main/java/de/danielbechler/diff/MapDiffer.java index 29398727..b33b4987 100644 --- a/src/main/java/de/danielbechler/diff/MapDiffer.java +++ b/src/main/java/de/danielbechler/diff/MapDiffer.java @@ -61,6 +61,17 @@ else if (nodeInspector.isEqualsOnly(mapNode)) mapNode.setState(Node.State.CHANGED); } } + else if (nodeInspector.hasEqualsOnlyValueProviderMethod(mapNode)){ + String method = nodeInspector.getEqualsOnlyValueProviderMethod(mapNode); + if (instances.areMethodResultsEqual(method)) + { + mapNode.setState(Node.State.UNTOUCHED); + } + else + { + mapNode.setState(Node.State.CHANGED); + } + } else if (instances.hasBeenAdded()) { compareEntries(mapNode, instances, instances.getWorking(Map.class).keySet()); diff --git a/src/main/java/de/danielbechler/diff/NodeInspector.java b/src/main/java/de/danielbechler/diff/NodeInspector.java index 9744eb4f..1ae5fa25 100644 --- a/src/main/java/de/danielbechler/diff/NodeInspector.java +++ b/src/main/java/de/danielbechler/diff/NodeInspector.java @@ -30,6 +30,10 @@ interface NodeInspector boolean isCompareToOnly(Node node); boolean isEqualsOnly(Node node); + + boolean hasEqualsOnlyValueProviderMethod(Node node); + + String getEqualsOnlyValueProviderMethod(Node node); boolean isReturnable(Node node); diff --git a/src/main/java/de/danielbechler/diff/accessor/AbstractAccessor.java b/src/main/java/de/danielbechler/diff/accessor/AbstractAccessor.java index 7262c2bb..a278a0cc 100644 --- a/src/main/java/de/danielbechler/diff/accessor/AbstractAccessor.java +++ b/src/main/java/de/danielbechler/diff/accessor/AbstractAccessor.java @@ -24,6 +24,7 @@ public abstract class AbstractAccessor implements Accessor private Set categories = new TreeSet(); private boolean equalsOnly; private boolean ignored; + private String equalsOnlyValueProviderMethod; public final Set getCategories() { @@ -54,5 +55,17 @@ public void setIgnored(final boolean ignored) { this.ignored = ignored; } + + public boolean hasEqualsOnlyValueProviderMethod(){ + return this.equalsOnlyValueProviderMethod != null && !this.equalsOnlyValueProviderMethod.equals(""); + } + + public void setEqualsOnlyValueProviderMethod(String equalsOnlyValueProviderMethod) { + this.equalsOnlyValueProviderMethod = equalsOnlyValueProviderMethod; + } + + public String getEqualsOnlyValueProviderMethod(){ + return equalsOnlyValueProviderMethod; + } } diff --git a/src/main/java/de/danielbechler/diff/accessor/PropertyDescriptor.java b/src/main/java/de/danielbechler/diff/accessor/PropertyDescriptor.java index 50cc094d..3e7038df 100644 --- a/src/main/java/de/danielbechler/diff/accessor/PropertyDescriptor.java +++ b/src/main/java/de/danielbechler/diff/accessor/PropertyDescriptor.java @@ -30,4 +30,8 @@ public interface PropertyDescriptor boolean isIgnored(); boolean isEqualsOnly(); + + boolean hasEqualsOnlyValueProviderMethod(); + + String getEqualsOnlyValueProviderMethod(); } diff --git a/src/main/java/de/danielbechler/diff/annotation/ObjectDiffEqualsOnlyValueProvidedType.java b/src/main/java/de/danielbechler/diff/annotation/ObjectDiffEqualsOnlyValueProvidedType.java new file mode 100644 index 00000000..75755a89 --- /dev/null +++ b/src/main/java/de/danielbechler/diff/annotation/ObjectDiffEqualsOnlyValueProvidedType.java @@ -0,0 +1,15 @@ +package de.danielbechler.diff.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@ObjectDiffAnnotation +public @interface ObjectDiffEqualsOnlyValueProvidedType { + public String method(); +} diff --git a/src/main/java/de/danielbechler/diff/annotation/ObjectDiffProperty.java b/src/main/java/de/danielbechler/diff/annotation/ObjectDiffProperty.java index d592a19b..80c4b833 100644 --- a/src/main/java/de/danielbechler/diff/annotation/ObjectDiffProperty.java +++ b/src/main/java/de/danielbechler/diff/annotation/ObjectDiffProperty.java @@ -51,4 +51,6 @@ * @return The categories for this property. */ public String[] categories() default {}; + + public String equalsOnlyValueProviderMethod() default ""; } diff --git a/src/main/java/de/danielbechler/diff/example/EqualsOnlyValueProviderMethodExample.java b/src/main/java/de/danielbechler/diff/example/EqualsOnlyValueProviderMethodExample.java new file mode 100644 index 00000000..723db0e6 --- /dev/null +++ b/src/main/java/de/danielbechler/diff/example/EqualsOnlyValueProviderMethodExample.java @@ -0,0 +1,72 @@ +package de.danielbechler.diff.example; + +import de.danielbechler.diff.Configuration; +import de.danielbechler.diff.ObjectDifferFactory; +import de.danielbechler.diff.node.Node; +import de.danielbechler.diff.path.PropertyPath; +import de.danielbechler.diff.visitor.PrintingVisitor; + +class EqualsOnlyValueProviderMethodExample { + private EqualsOnlyValueProviderMethodExample() + { + } + + public static void main(final String[] args) + { + PropertyClass prop = new PropertyClass("1", "2"); + final EncompassingClass base = new EncompassingClass(prop); + PropertyClass prop2 = new PropertyClass("1", "3"); + final EncompassingClass working = new EncompassingClass(prop2); + + final Configuration configuration = new Configuration(); + + // (Option 1) Causes the ObjectDiffer to compare using the method "getProp1" on the 'prop' property of the root object + configuration.withEqualsOnlyValueProviderMethod(PropertyPath.buildWith("prop"), "getProp1"); + + final Node node = ObjectDifferFactory.getInstance(configuration).compare(working, base); + + node.visit(new PrintingVisitor(working, base)); + + // Output with ignore: + // Property at path '/' has not changed + // Output without ignore: + // Property at path '/prop/prop2' has changed from [ 2 ] to [ 3 ] + } + + public static class EncompassingClass + { + private final PropertyClass prop; + + public EncompassingClass(final PropertyClass prop) + { + this.prop = prop; + } + + /* (Option 2) This annotation causes the ObjectDiffer to use getProp1 method to compare */ + //@ObjectDiffProperty(equalsOnlyValueProviderMethod = "getProp1") + public PropertyClass getProp() { + return prop; + } + } + + /* (Option 3) This annotation causes the ObjectDiffer to use getProp1 method to compare */ + //@ObjectDiffEqualsOnlyValueProvidedType(method="getProp1") + public static class PropertyClass + { + private String prop1; + private String prop2; + + public PropertyClass(String prop1, String prop2) + { + this.prop1 = prop1; + this.prop2 = prop2; + } + public String getProp1() { + return prop1; + } + public String getProp2() { + return prop2; + } + } + +} diff --git a/src/main/java/de/danielbechler/diff/introspect/StandardIntrospector.java b/src/main/java/de/danielbechler/diff/introspect/StandardIntrospector.java index 2e958cc7..25dec318 100644 --- a/src/main/java/de/danielbechler/diff/introspect/StandardIntrospector.java +++ b/src/main/java/de/danielbechler/diff/introspect/StandardIntrospector.java @@ -99,6 +99,7 @@ private static void handleObjectDiffPropertyAnnotation(final Method readMethod, propertyAccessor.setEqualsOnly(annotation.equalsOnly()); propertyAccessor.setIgnored(annotation.ignore()); propertyAccessor.setCategories(Collections.setOf(annotation.categories())); + propertyAccessor.setEqualsOnlyValueProviderMethod(annotation.equalsOnlyValueProviderMethod()); } } diff --git a/src/main/java/de/danielbechler/diff/node/DefaultNode.java b/src/main/java/de/danielbechler/diff/node/DefaultNode.java index 024a3f41..06d98615 100644 --- a/src/main/java/de/danielbechler/diff/node/DefaultNode.java +++ b/src/main/java/de/danielbechler/diff/node/DefaultNode.java @@ -317,6 +317,14 @@ public final boolean isIgnored() { return state == State.IGNORED || accessor.isIgnored(); } + + public boolean hasEqualsOnlyValueProviderMethod() { + return accessor.hasEqualsOnlyValueProviderMethod(); + } + + public String getEqualsOnlyValueProviderMethod() { + return accessor.getEqualsOnlyValueProviderMethod(); + } public final Set getCategories() { diff --git a/src/main/java/de/danielbechler/diff/node/Node.java b/src/main/java/de/danielbechler/diff/node/Node.java index 90d0c6e1..7330a6eb 100644 --- a/src/main/java/de/danielbechler/diff/node/Node.java +++ b/src/main/java/de/danielbechler/diff/node/Node.java @@ -201,4 +201,8 @@ public enum State T getPropertyAnnotation(Class annotationClass); + boolean hasEqualsOnlyValueProviderMethod(); + + String getEqualsOnlyValueProviderMethod(); + } diff --git a/src/main/java/de/danielbechler/diff/path/ClassAndMethod.java b/src/main/java/de/danielbechler/diff/path/ClassAndMethod.java new file mode 100644 index 00000000..7573f725 --- /dev/null +++ b/src/main/java/de/danielbechler/diff/path/ClassAndMethod.java @@ -0,0 +1,16 @@ +package de.danielbechler.diff.path; + +public class ClassAndMethod { + private Class clazz; + private String method; + public ClassAndMethod(Class clazz, String method){ + this.clazz = clazz; + this.method = method; + } + public Class getClazz() { + return clazz; + } + public String getMethod() { + return method; + } +} diff --git a/src/main/java/de/danielbechler/diff/path/PropertyPathAndMethod.java b/src/main/java/de/danielbechler/diff/path/PropertyPathAndMethod.java new file mode 100644 index 00000000..894693b8 --- /dev/null +++ b/src/main/java/de/danielbechler/diff/path/PropertyPathAndMethod.java @@ -0,0 +1,20 @@ +package de.danielbechler.diff.path; + +public class PropertyPathAndMethod { + private PropertyPath propertyPath; + private String method; + + public PropertyPathAndMethod(){} + public PropertyPathAndMethod(PropertyPath propertyPath, String method){ + this.propertyPath = propertyPath; + this.method = method; + } + + public PropertyPath getPropertyPath() { + return propertyPath; + } + + public String getMethod() { + return method; + } +} diff --git a/src/test/java/de/danielbechler/diff/BeanDifferShould.java b/src/test/java/de/danielbechler/diff/BeanDifferShould.java index 9aaeacdf..5719e987 100644 --- a/src/test/java/de/danielbechler/diff/BeanDifferShould.java +++ b/src/test/java/de/danielbechler/diff/BeanDifferShould.java @@ -120,6 +120,34 @@ public void compare_bean_via_equals() assertThat(node).self().isUntouched(); } + + @Test + public void detect_no_change_when_comparing_using_with_equals_only_value_provider_method_and_result_is_same() + { + final ObjectWithNestedObject working = new ObjectWithNestedObject("foo"); + working.setObject(new ObjectWithNestedObject("childid")); + final ObjectWithNestedObject base = new ObjectWithNestedObject("foo"); + base.setObject(new ObjectWithNestedObject("differentchildid")); + configuration.withEqualsOnlyValueProviderMethod(PropertyPath.buildRootPath(), "getId"); + + final Node node = differ.compare(Node.ROOT, Instances.of(working, base)); + + assertThat(node).self().isUntouched(); + } + + @Test + public void detect_change_when_comparing_using_equals_only_value_provider_method_and_result_is_different() + { + final ObjectWithNestedObject working = new ObjectWithNestedObject("foo"); + working.setObject(new ObjectWithNestedObject("childid")); + final ObjectWithNestedObject base = new ObjectWithNestedObject("bar"); + base.setObject(new ObjectWithNestedObject("differentchildid")); + configuration.withEqualsOnlyValueProviderMethod(PropertyPath.buildRootPath(), "getId"); + + final Node node = differ.compare(Node.ROOT, Instances.of(working, base)); + + assertThat(node).self().hasChanges(); + } @Test public void compare_bean_via_introspection_and_delegate_comparison_of_properties() diff --git a/src/test/java/de/danielbechler/diff/CollectionDifferShould.java b/src/test/java/de/danielbechler/diff/CollectionDifferShould.java index 5c67395e..b55bf50e 100644 --- a/src/test/java/de/danielbechler/diff/CollectionDifferShould.java +++ b/src/test/java/de/danielbechler/diff/CollectionDifferShould.java @@ -24,6 +24,7 @@ import java.util.*; +import static de.danielbechler.diff.node.Node.State.CHANGED; import static java.util.Arrays.*; import static java.util.Collections.*; import static org.mockito.Mockito.*; @@ -118,6 +119,26 @@ public void compare_only_via_equals_if_equals_only_is_enabled() compare(); verify(collectionNode).setState(Node.State.UNTOUCHED); } + + @Test + public void detect_no_change_when_comparing_using_with_equals_only_value_provider_method_and_result_is_same() + { + when(nodeInspector.hasEqualsOnlyValueProviderMethod(collectionNode)).thenReturn(true); + when(nodeInspector.getEqualsOnlyValueProviderMethod(collectionNode)).thenReturn("somemethod"); + when(instances.areMethodResultsEqual("somemethod")).thenReturn(true); + compare(); + verify(collectionNode).setState(Node.State.UNTOUCHED); + } + + @Test + public void detect_change_when_comparing_using_with_equals_only_value_provider_method_and_result_is_different() + { + when(nodeInspector.hasEqualsOnlyValueProviderMethod(collectionNode)).thenReturn(true); + when(nodeInspector.getEqualsOnlyValueProviderMethod(collectionNode)).thenReturn("somemethod"); + when(instances.areMethodResultsEqual("somemethod")).thenReturn(false); + compare(); + verify(collectionNode).setState(CHANGED); + } @Test public void detect_changes_if_equals_only_is_enabled() diff --git a/src/test/java/de/danielbechler/diff/InstancesTest.java b/src/test/java/de/danielbechler/diff/InstancesTest.java index ba685d8e..c1fcb749 100644 --- a/src/test/java/de/danielbechler/diff/InstancesTest.java +++ b/src/test/java/de/danielbechler/diff/InstancesTest.java @@ -17,6 +17,8 @@ package de.danielbechler.diff; import de.danielbechler.diff.accessor.*; +import de.danielbechler.diff.mock.ObjectWithString; + import org.testng.annotations.*; import java.lang.reflect.*; @@ -120,6 +122,51 @@ public void testIsPrimitiveTypeReturnsFalseForComplexType() assertThat(new Instances(RootAccessor.getInstance(), "1", "2", null).isPrimitiveType()).isFalse(); } + @Test + public void testMethodResultEqualNotEqual() throws Exception + { + final Method readMethod = getClass().getDeclaredMethod("getTestValue"); + final PropertyAccessor accessor = new PropertyAccessor("testValue", readMethod, null); + + ObjectWithString working = new ObjectWithString("string1"); + ObjectWithString base = new ObjectWithString("string2"); + + final Instances instances = new Instances(accessor, working, base, null); + assertThat(instances.areMethodResultsEqual("getValue")).isFalse(); + } + + @Test + public void testMethodResultEqualIsEqual() throws Exception + { + final Method readMethod = getClass().getDeclaredMethod("getTestValue"); + final PropertyAccessor accessor = new PropertyAccessor("testValue", readMethod, null); + + ObjectWithString working = new ObjectWithString("string"); + ObjectWithString base = new ObjectWithString("string"); + + final Instances instances = new Instances(accessor, working, base, null); + assertThat(instances.areMethodResultsEqual("getValue")).isTrue(); + } + + @Test + public void testMethodResultEqualInvalidMethod() throws Exception + { + final Method readMethod = getClass().getDeclaredMethod("getTestValue"); + final PropertyAccessor accessor = new PropertyAccessor("testValue", readMethod, null); + + ObjectWithString working = new ObjectWithString("string"); + ObjectWithString base = new ObjectWithString("string"); + + final Instances instances = new Instances(accessor, working, base, null); + try { + instances.areMethodResultsEqual("invalid"); + fail("no exception thrown"); + } + catch(RuntimeException e){ + assertThat(e.getCause() instanceof NoSuchMethodException).isTrue(); + } + } + @SuppressWarnings({"MethodMayBeStatic", "UnusedDeclaration"}) public long getTestValue() { diff --git a/src/test/java/de/danielbechler/diff/MapDifferShould.java b/src/test/java/de/danielbechler/diff/MapDifferShould.java index 92848556..3de11f08 100644 --- a/src/test/java/de/danielbechler/diff/MapDifferShould.java +++ b/src/test/java/de/danielbechler/diff/MapDifferShould.java @@ -90,6 +90,30 @@ public void detect_no_change_when_comparing_using_equals() verify(node).setState(UNTOUCHED); } + @Test + public void detect_no_change_when_comparing_using_equals_only_provider_method_and_result_is_same() + { + when(nodeInspector.hasEqualsOnlyValueProviderMethod(internalNode)).thenReturn(true); + when(nodeInspector.getEqualsOnlyValueProviderMethod(internalNode)).thenReturn("somemethod"); + when(instances.areMethodResultsEqual("somemethod")).thenReturn(true); + + node = compare(working, base); + + verify(node).setState(UNTOUCHED); + } + + @Test + public void detect_change_when_comparing_using_with_equals_only_provider_method_and_result_is_different() + { + when(nodeInspector.hasEqualsOnlyValueProviderMethod(internalNode)).thenReturn(true); + when(nodeInspector.getEqualsOnlyValueProviderMethod(internalNode)).thenReturn("somemethod"); + when(instances.areMethodResultsEqual("somemethod")).thenReturn(false); + + node = compare(working, base); + + verify(node).setState(CHANGED); + } + @Test public void detect_addition() { diff --git a/src/test/java/de/danielbechler/diff/ObjectDifferIntegrationTests.java b/src/test/java/de/danielbechler/diff/ObjectDifferIntegrationTests.java index 3e6b596b..f37d3272 100644 --- a/src/test/java/de/danielbechler/diff/ObjectDifferIntegrationTests.java +++ b/src/test/java/de/danielbechler/diff/ObjectDifferIntegrationTests.java @@ -63,7 +63,7 @@ public void testCompareCollectionWithIgnoredCollectionProperty() NodeAssertions.assertThat(node).self().hasState(Node.State.UNTOUCHED); NodeAssertions.assertThat(node).self().hasNoChildren(); } - + public void testCompareCollectionWithAddedItem() throws Exception { final Collection working = new LinkedList(asList("foo")); @@ -320,4 +320,102 @@ public void testCompareWithListContainingObjectOnceDetectsIfAnotherInstanceOfItG .withCollectionItem(new ObjectWithHashCodeAndEquals("foo")) .build()).hasState(Node.State.ADDED); } + + public void testCompareBeanWithEqualsOnlyValueProviderMethodOnGetCollectionPropertyNoChangeInMethodResult() + { + List forWorking = new ArrayList(); + forWorking.add("one"); + List forBase = new ArrayList(); + forBase.add("uno"); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection working = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection(forWorking); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection base = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection(forBase); + + final Node node = objectDiffer.compare(working, base); + + NodeAssertions.assertThat(node).self().hasState(Node.State.UNTOUCHED); + NodeAssertions.assertThat(node).self().hasNoChildren(); + } + + public void testCompareBeanWithEqualOnlyValueProviderMethodOnGetCollectionPropertyWithChangeInMethodResult() + { + List forWorking = new ArrayList(); + forWorking.add("one"); + forWorking.add("two"); + List forBase = new ArrayList(); + forBase.add("uno"); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection working = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection(forWorking); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection base = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection(forBase); + + final Node node = objectDiffer.compare(working, base); + + NodeAssertions.assertThat(node).self().hasState(Node.State.CHANGED); + assertThat(node) + .child(PropertyPath.buildWith("collection")) + .hasState(Node.State.CHANGED); + } + + public void testCompareBeanWithEqualsOnlyValueProviderMethodOnGetObjectPropertyNoChangeInMethodResult() + { + ObjectWithNestedObject forWorking = new ObjectWithNestedObject("childid"); + forWorking.setObject(new ObjectWithNestedObject("grandchildid")); + ObjectWithNestedObject forBase = new ObjectWithNestedObject("childid"); + forBase.setObject(new ObjectWithNestedObject("differentgrandchildid")); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject working = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject("id", forWorking); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject base = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject("id", forBase); + + final Node node = objectDiffer.compare(working, base); + + NodeAssertions.assertThat(node).self().hasState(Node.State.UNTOUCHED); + NodeAssertions.assertThat(node).self().hasNoChildren(); + } + + public void testCompareBeanWithEqualsOnlyValueProviderMethodOnGetObjectPropertyWithChangeInMethodResult() + { + ObjectWithNestedObject forWorking = new ObjectWithNestedObject("childid"); + forWorking.setObject(new ObjectWithNestedObject("grandchildid")); + ObjectWithNestedObject forBase = new ObjectWithNestedObject("differentchildid"); + forBase.setObject(new ObjectWithNestedObject("differentgrandchildid")); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject working = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject("id", forWorking); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject base = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject("id", forBase); + + final Node node = objectDiffer.compare(working, base); + + NodeAssertions.assertThat(node).self().hasState(Node.State.CHANGED); + assertThat(node) + .child(PropertyPath.buildWith("object")) + .hasState(Node.State.CHANGED); + } + + public void testCompareBeanWithEqualsOnlyValueProviderMethodOnGetMapPropertyNoChangeInMethodResult() + { + Map forWorking = new HashMap(); + forWorking.put("key1", "val1"); + Map forBase = new HashMap(); + forBase.put("keyone", "valone"); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap working = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap(forWorking); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap base = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap(forBase); + + final Node node = objectDiffer.compare(working, base); + + NodeAssertions.assertThat(node).self().hasState(Node.State.UNTOUCHED); + NodeAssertions.assertThat(node).self().hasNoChildren(); + } + + public void testCompareBeanWithEqualsOnlyValueProviderMethodOnGetMapPropertyWithChangeInMethodResult() + { + Map forWorking = new HashMap(); + forWorking.put("key1", "val1"); + forWorking.put("key2", "val2"); + Map forBase = new HashMap(); + forBase.put("keyone", "valone"); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap working = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap(forWorking); + final ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap base = new ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap(forBase); + + final Node node = objectDiffer.compare(working, base); + + NodeAssertions.assertThat(node).self().hasState(Node.State.CHANGED); + assertThat(node) + .child(PropertyPath.buildWith("map")) + .hasState(Node.State.CHANGED); + } } diff --git a/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection.java b/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection.java new file mode 100644 index 00000000..bb27faf7 --- /dev/null +++ b/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection.java @@ -0,0 +1,19 @@ +package de.danielbechler.diff.mock; + +import java.util.Collection; + +import de.danielbechler.diff.annotation.ObjectDiffProperty; + +public class ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection extends ObjectWithCollection { + + public ObjectWithMethodEqualsOnlyValueProviderMethodOnGetCollection(Collection collection){ + super(collection); + } + + @ObjectDiffProperty(equalsOnlyValueProviderMethod = "size") + @Override + public Collection getCollection() + { + return super.getCollection(); + } +} diff --git a/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap.java b/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap.java new file mode 100644 index 00000000..eb262690 --- /dev/null +++ b/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap.java @@ -0,0 +1,18 @@ +package de.danielbechler.diff.mock; + +import java.util.Map; + +import de.danielbechler.diff.annotation.ObjectDiffProperty; + +public class ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap extends ObjectWithMap { + public ObjectWithMethodEqualsOnlyValueProviderMethodOnGetMap(Map map){ + super(map); + } + + @ObjectDiffProperty(equalsOnlyValueProviderMethod = "size") + @Override + public Map getMap() + { + return super.getMap(); + } +} diff --git a/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject.java b/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject.java new file mode 100644 index 00000000..f0b03abf --- /dev/null +++ b/src/test/java/de/danielbechler/diff/mock/ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject.java @@ -0,0 +1,19 @@ +package de.danielbechler.diff.mock; + +import de.danielbechler.diff.annotation.ObjectDiffProperty; + +public class ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject extends ObjectWithNestedObject { + public ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject(String id){ + super(id); + } + public ObjectWithMethodEqualsOnlyValueProviderMethodOnGetNestedObject(String id, ObjectWithNestedObject object){ + super(id); + setObject(object); + } + + @ObjectDiffProperty(equalsOnlyValueProviderMethod = "getId") + @Override + public ObjectWithNestedObject getObject() { + return super.getObject(); + } +}