Skip to content

ability to compare a bean or map or collection by the result of a method call #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 28, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/main/java/de/danielbechler/diff/BeanDiffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/de/danielbechler/diff/CollectionDiffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
68 changes: 66 additions & 2 deletions src/main/java/de/danielbechler/diff/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ public enum PrimitiveDefaultValueMode
private final Collection<PropertyPath> includedProperties = new HashSet<PropertyPath>(10);
private final Collection<PropertyPath> excludedProperties = new HashSet<PropertyPath>(10);
private final Collection<PropertyPath> equalsOnlyProperties = new LinkedHashSet<PropertyPath>(10);
private final Collection<PropertyPathAndMethod> equalsOnlyValueProviderMethods = new LinkedHashSet<PropertyPathAndMethod>(10);
private final Collection<Class<?>> compareToOnlyTypes = new LinkedHashSet<Class<?>>(10);
private final Collection<Class<?>> equalsOnlyTypes = new LinkedHashSet<Class<?>>(10);
private final Collection<ClassAndMethod> equalsOnlyValueProviderTypes = new LinkedHashSet<ClassAndMethod>(10);
private boolean returnUnchangedNodes = false;
private boolean returnIgnoredNodes = false;
private boolean returnCircularNodes = true;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -347,4 +410,5 @@ else if (node.isRemoved())
}
return true;
}

}
13 changes: 13 additions & 0 deletions src/main/java/de/danielbechler/diff/Instances.java
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/de/danielbechler/diff/MapDiffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/de/danielbechler/diff/NodeInspector.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
13 changes: 13 additions & 0 deletions src/main/java/de/danielbechler/diff/accessor/AbstractAccessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public abstract class AbstractAccessor implements Accessor
private Set<String> categories = new TreeSet<String>();
private boolean equalsOnly;
private boolean ignored;
private String equalsOnlyValueProviderMethod;

public final Set<String> getCategories()
{
Expand Down Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ public interface PropertyDescriptor
boolean isIgnored();

boolean isEqualsOnly();

boolean hasEqualsOnlyValueProviderMethod();

String getEqualsOnlyValueProviderMethod();
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,6 @@
* @return The categories for this property.
*/
public String[] categories() default {};

public String equalsOnlyValueProviderMethod() default "";
}
Original file line number Diff line number Diff line change
@@ -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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/de/danielbechler/diff/node/DefaultNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> getCategories()
{
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/de/danielbechler/diff/node/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,8 @@ public enum State

<T extends Annotation> T getPropertyAnnotation(Class<T> annotationClass);

boolean hasEqualsOnlyValueProviderMethod();

String getEqualsOnlyValueProviderMethod();

}
16 changes: 16 additions & 0 deletions src/main/java/de/danielbechler/diff/path/ClassAndMethod.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading