diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a2a61ed..97a94f7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,8 @@
To avoid this, returning a constant hashCode seems like a small price
to pay. Yes, it may have a small performance impact, but we can still
optimize when that turns out to be a problem.
+
+ **NagyGa1: This is reverted in the NagyGa1 (com.studium) fork, using hashCode as per normal.**
## 0.92.1
diff --git a/README.md b/README.md
index 204ad2a8..0993f874 100644
--- a/README.md
+++ b/README.md
@@ -1,97 +1,11 @@
-## Introduction
+## Features added to upstream project
-`java-object-diff` is a simple, yet powerful library to find differences between Java objects. It takes two objects and generates a tree structure that represents any differences between the objects and their children. This tree can then be traversed to extract more information or apply changes to the underlying data structures.
-
-[](https://travis-ci.org/SQiShER/java-object-diff)
-[](https://coveralls.io/r/SQiShER/java-object-diff?branch=master)
-[](https://bintray.com/sqisher/maven/java-object-diff/_latestVersion)
-[](https://readthedocs.org/projects/java-object-diff/?badge=latest)
-
-## Features
-
-* Works out-of-the-box with with almost any kind of object and arbitrarily deep nesting
-* Finds the differences between two objects
-* Returns the differences in shape of an easily traversable tree structure
-* Tells you everything there is to know about the detected changes
-* Provides read and write access to the underlying objects, allowing you not only to extract the changed values but even to apply the diff as a patch
-* Requires no changes to your existing classes (in most cases)
-* Provides a very flexible configuration API to tailor everything to your needs
-* Tiny, straightforward, yet very powerful API
-* Detects and handles circular references in the object graph
-* No runtime dependencies except for [SLF4J](http://www.slf4j.org/)
-* Compatible with Java 1.5 and above
-
-## Support this Project
-
-If you like this project, there are a few things you can do to show your support:
-
-* [**Follow me** on Twitter (@SQiShER)](https://twitter.com/SQiShER)
-* [**Surprise me** with something from my Amazon Wishlist](http://www.amazon.de/registry/wishlist/2JFW27V71CBGM)
-* [**Contribute** code, documentation, ideas, or insights into your use-case](https://github.com/SQiShER/java-object-diff/blob/master/CONTRIBUTING.md)
-* Star this repository (stars make me very happy!)
-* Talk about it, write about it, recommend it to others
-
-But most importantly: **don't ever hesitate to ask me for help**, if you're having trouble getting this library to work. The only way to make it better is by hearing about your use-cases and pushing the limits!
-
-## Getting Started
-
-To learn how to use **Java Object Diff** have a look at the [Getting Started Guide](http://java-object-diff.readthedocs.org/en/latest/getting-started/).
-
-### Using with Maven
-
-```xml
-
- de.danielbechler
- java-object-diff
- 0.92.1
-
-```
-
-### Using with Gradle
-
-```groovy
-compile 'de.danielbechler:java-object-diff:0.92.1'
-```
-
-## Documentation
-
-The documentation can be found over at [ReadTheDocs](http://java-object-diff.readthedocs.org/en/latest/).
-
-## Caveats
-
-* Introspection of values other than primitives and collection types is curently done via standard JavaBean introspection, which requires your objects to provide getters and setters for their properties. However, you don't need to provide setters, if you don't need write access to the properties (e.g. you don't want to apply the diff as a patch.)
-
- If this does not work for you, don't worry: you can easily write your own introspectors and just plug them in via configuration API.
-
-* Ordered lists are currently not properly supported (they are just treated as Sets). While this is something I definitely want to add before version `1.0` comes out, its a pretty big task and will be very time consuming. So far there have been quite a few people who needed this feature, but not as many as I imagined. So your call to action: if you need to diff and/or merge collection types like `ArrayList`, perhaps even with multiple occurence of the same value, please let me know. The more I'm aware of the demand and about the use-cases, the more likely it is, that I start working on it.
-
-## Why would you need this?
-
-Sometimes you need to figure out, how one version of an object differs from another one. One of the simplest solutions that'll cross your mind is most certainly to use reflection to scan the object for fields or getters and use them to compare the values of the different object instances. In many cases this is a perfectly valid strategy and the way to go. After all, we want to keep things simple, don't we?
-
-However, there are some cases that can increase the complexity dramatically. What if you need to find differences in collections or maps? What if you have to deal with nested objects that also need to be compared on a per-property basis? Or even worse: what if you need to merge such objects?
-
-You suddenly realize that you need to scan the objects recursively, figure out which collection items have been added, removed or changed; find a way to return your results in a way that allows you to easily access the information you are looking for and provide accessors to apply changes.
-
-While all this isn't exactly rocket science, it is complex enough to add quite a lot of extra code to your project. Code that needs to be tested and maintained. Since the best code is the code you didn't write, this library aims to help you with all things related to diffing and merging of Java objects by providing a robust foundation and a simple, yet powerful API.
-
-This library will hide all the complexities of deep object comparison behind one line of code:
+* IdentityStrategy adheres the Java spec on equals() and hashCode()
+* IdentityConfigurer allows to configure by element type within the collection
```java
-Node root = ObjectDifferBuilder.buildDefault().compare(workingObject, baseObject);
+ObjectDifferBuilder
+ .startBuilding()
+ .identity().ofType(ElementClass.class).toUse(codeIdentity).and()
+ ...
```
-
-This generates a tree structure of the given object type and lets you traverse its nodes via visitors. Each node represents one property (or collection item) of the underlying object and tells you exactly if and how the value differs from the base version. It also provides accessors to read, write and remove the value from or to any given instance. This way, all you need to worry about is **how to treat** changes and **not how to find** them.
-
-This library has been battle-tested in a rather big project of mine, where I use it to generate **activity streams**, resolve database **update conflics**, display **change logs** and limit the scope of entity updates to only a **subset of properties**, based on the context or user permissions. It didn't let me down so far and I hope it can help you too!
-
-## Contribute
-
-You discovered a bug or have an idea for a new feature? Great, why don't you send me a [Pull
-Request](https://help.github.com/articles/using-pull-requests) so everyone can benefit from it? To help you getting started, [here](https://github.com/SQiShER/java-object-diff/blob/master/CONTRIBUTING.md) is a brief guide with everyting you need to know to get involved!
-
----
-
-Thanks to JetBrains for supporting this project with a free open source license for their amazing IDE **IntelliJ IDEA**.
-
-[](https://www.jetbrains.com/idea/)
diff --git a/pom.xml b/pom.xml
index cc77956b..2226a423 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,10 +18,10 @@
4.0.0
- de.danielbechler
+ sg.studium
java-object-diff
Java Object Diff
- 0.92.2-SNAPSHOT
+ 0.92.2
2.2.1
@@ -440,10 +440,13 @@
- bintray-sqisher-maven-java-object-diff
- sqisher-maven-java-object-diff
- https://api.bintray.com/maven/sqisher/maven/java-object-diff
+ studium_releases
+ https://www.studium.sg/nexus/content/repositories/releases
+
+ studium_snapshots
+ https://www.studium.sg/nexus/content/repositories/snapshots
+
diff --git a/src/integration-test/java/de/danielbechler/diff/identity/IdentityStrategyConfigIT.groovy b/src/integration-test/java/de/danielbechler/diff/identity/IdentityStrategyConfigIT.groovy
new file mode 100644
index 00000000..1c3dfa0b
--- /dev/null
+++ b/src/integration-test/java/de/danielbechler/diff/identity/IdentityStrategyConfigIT.groovy
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2015 Daniel Bechler
+ *
+ * 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.
+ */
+
+package de.danielbechler.diff.identity
+
+import de.danielbechler.diff.ObjectDifferBuilder
+import de.danielbechler.diff.node.DiffNode
+import de.danielbechler.diff.node.Visit
+import de.danielbechler.diff.path.NodePath
+import de.danielbechler.diff.selector.CollectionItemElementSelector
+import de.danielbechler.diff.selector.MapKeyElementSelector
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.ToString
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class IdentityStrategyConfigIT extends Specification {
+
+ def working = new Container(
+ productMap: [
+ "PROD1": new Product(
+ id: "PROD1",
+ code: "Code1",
+ productVersions: [
+ new ProductVersion(id: "ID1", code: "PVC1"),
+ new ProductVersion(id: "ID2", code: "PVC2")
+ ]),
+ "PROD2": new Product(
+ id: "PROD2",
+ code: "Code2",
+ productVersions: [
+ new ProductVersion(id: "ID1", code: "PVC1"),
+ new ProductVersion(id: "ID2", code: "PVC2")
+ ])
+ ],
+ otherMap: [
+ "PROD1": new Product(
+ id: "PROD1",
+ code: "Code1",
+ productVersions: [
+ new ProductVersion(id: "ID1", code: "PVC1"),
+ new ProductVersion(id: "ID2", code: "PVC2")
+ ]),
+ "PROD2": new Product(
+ id: "PROD2",
+ code: "Code2",
+ productVersions: [
+ new ProductVersion(id: "ID1", code: "PVC1"),
+ new ProductVersion(id: "ID2", code: "PVC2")
+ ])
+ ]
+ )
+
+ def base = new Container(
+ productMap: [
+ "PROD1": new Product(
+ id: "PROD1",
+ code: "Code1",
+ productVersions: [
+ new ProductVersion(id: "ID3", code: "PVC1"),
+ new ProductVersion(id: "ID4", code: "PVC2")
+ ]),
+ "PROD2": new Product(
+ id: "PROD2",
+ code: "Code2",
+ productVersions: [
+ new ProductVersion(id: "ID3", code: "PVC1"),
+ new ProductVersion(id: "ID4", code: "PVC2")
+ ])
+ ],
+ otherMap: [
+ "PROD1": new Product(
+ id: "PROD1",
+ code: "Code1",
+ productVersions: [
+ new ProductVersion(id: "ID1", code: "PVC1"),
+ new ProductVersion(id: "ID2", code: "PVC2")
+ ]),
+ "PROD2": new Product(
+ id: "PROD2",
+ code: "Code2",
+ productVersions: [
+ new ProductVersion(id: "ID1", code: "PVC1"),
+ new ProductVersion(id: "ID2", code: "PVC2")
+ ])
+ ]
+ )
+
+ def 'Without IdentityStrategy'() {
+ when:
+ def node = ObjectDifferBuilder
+ .startBuilding()
+ .filtering().returnNodesWithState(DiffNode.State.UNTOUCHED).and()
+ .build().compare(working, base);
+ then: "High level nodes all changed"
+ // print(node, working, base)
+ node.getChild("otherMap").untouched
+ node.getChild("productMap").changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions").changed
+ and: "ID1 and ID2 are ADDED"
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1Selector).added
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV2Selector).added
+ and: "ID3 and ID4 are REMOVED"
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV3Selector).removed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV4Selector).removed
+ }
+
+ def 'PropertyOfType configuration WITH IdentityStrategy'() {
+ when:
+ def node = ObjectDifferBuilder
+ .startBuilding()
+ .identity().ofTypeAndProperty(Product.class, "productVersions").toUse(codeIdentity).and()
+ .filtering().returnNodesWithState(DiffNode.State.UNTOUCHED).and()
+ .build().compare(working, base);
+ then: "High level nodes"
+ // print(node, working, base)
+ node.getChild("otherMap").untouched
+ node.getChild("productMap").changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions").changed
+ and: "ID1 and ID2 are CHANGED"
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).changed
+ and: "id changed, code untouched"
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).getChild("id").changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).getChild("code").untouched
+ }
+
+ @Unroll("OfType configuration WITH IdentityStrategy #aClazz")
+ def 'OfType configuration WITH IdentityStrategy'() {
+ when:
+ def node = ObjectDifferBuilder
+ .startBuilding()
+ .identity().ofType(aClazz).toUse(codeIdentity).and()
+ .filtering().returnNodesWithState(DiffNode.State.UNTOUCHED).and()
+ .build().compare(working, base);
+ then: "High level nodes"
+// print(node, working, base)
+ node.getChild("otherMap").untouched
+ node.getChild("productMap").changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions").changed
+ and: "ID1 and ID2 are CHANGED"
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).changed
+ and: "id changed, code untouched"
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).getChild("id").changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).getChild("code").untouched
+ where:
+ aClazz << [ProductVersion, CodeId]
+ }
+
+ def 'OfNode configuration WITH IdentityStrategy'() {
+ when:
+ def node = ObjectDifferBuilder
+ .startBuilding()
+ .identity().ofNode(
+ // this is not very useful without wildcards on maps and collections...
+ NodePath.startBuilding().propertyName("productMap").mapKey("PROD1")
+ .propertyName("productVersions").build()
+ ).toUse(codeIdentity).and()
+ .filtering().returnNodesWithState(DiffNode.State.UNTOUCHED).and()
+ .build().compare(working, base);
+ then: "High level nodes"
+// print(node, working, base)
+ node.getChild("otherMap").untouched
+ node.getChild("productMap").changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions").changed
+ and: "ID1 and ID2 are CHANGED"
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).changed
+ and: "id changed, code untouched"
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).getChild("id").changed
+ node.getChild("productMap").getChild(new MapKeyElementSelector("PROD1")).getChild("productVersions")
+ .getChild(PV1CodeSelector).getChild("code").untouched
+ }
+
+
+ private void print(final DiffNode diffNode, final Object working,
+ final Object base) {
+ diffNode.visit(new DiffNode.Visitor() {
+ @Override
+ void node(final DiffNode node, final Visit visit) {
+ System.out.println("" + node.getPath() + " " + node.getState()
+ // + " " + node.canonicalGet(base) + " => " + node.canonicalGet(working)
+ )
+ }
+ })
+ }
+
+
+ public static class Container {
+ Map productMap;
+ Map otherMap;
+ }
+
+ public static interface CodeId {
+ String getCode();
+ }
+
+ @EqualsAndHashCode(includes = ["id"])
+ @ToString(includePackage = false)
+ public static class Product implements CodeId {
+ String id;
+ String code;
+ List productVersions;
+ List others;
+ }
+
+ @EqualsAndHashCode(includes = ["id"])
+ @ToString(includePackage = false)
+ public static class ProductVersion implements CodeId {
+ String id;
+ String code;
+ }
+
+ @EqualsAndHashCode(includes = ["id"])
+ @ToString(includePackage = false)
+ public static class OtherClass implements CodeId {
+ String id;
+ String code;
+ List productVersions;
+ }
+
+ def codeIdentity = new IdentityStrategy() {
+ @Override
+ boolean equals(final Object _this, final Object o) {
+ return Objects.equals(((CodeId) _this).getCode(), ((CodeId) o).getCode());
+ }
+
+ @Override
+ int hashCode(final Object _this) {
+ return Objects.hashCode(((CodeId) _this).getCode());
+ }
+ }
+
+ def PV1Selector = new CollectionItemElementSelector(new ProductVersion(id: "ID1", code: "PVC1"));
+ def PV2Selector = new CollectionItemElementSelector(new ProductVersion(id: "ID2", code: "PVC2"));
+ def PV3Selector = new CollectionItemElementSelector(new ProductVersion(id: "ID3"));
+ def PV4Selector = new CollectionItemElementSelector(new ProductVersion(id: "ID4"));
+
+ // need to fill code as well because that's used for the codeIdentity cases
+ def PV1CodeSelector = new CollectionItemElementSelector(new ProductVersion(code: "PVC1"), codeIdentity);
+ def PV2CodeSelector = new CollectionItemElementSelector(new ProductVersion(code: "PVC2"), codeIdentity);
+}
diff --git a/src/integration-test/java/de/danielbechler/diff/identity/IdentityStrategyIT.groovy b/src/integration-test/java/de/danielbechler/diff/identity/IdentityStrategyIT.groovy
new file mode 100644
index 00000000..8116b953
--- /dev/null
+++ b/src/integration-test/java/de/danielbechler/diff/identity/IdentityStrategyIT.groovy
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2015 Daniel Bechler
+ *
+ * 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.
+ */
+
+package de.danielbechler.diff.identity
+
+import de.danielbechler.diff.ObjectDifferBuilder
+import de.danielbechler.diff.node.DiffNode
+import de.danielbechler.diff.node.Visit
+import de.danielbechler.diff.path.NodePath
+import de.danielbechler.diff.selector.CollectionItemElementSelector
+import spock.lang.Specification
+
+class IdentityStrategyIT extends Specification {
+
+ List list1 = [
+ new A(id: "Id1", code: "Code1"),
+ new A(id: "Id2", code: "Code2"),
+ new A(id: "Id3", code: "Code3")
+ ]
+ List list2 = [
+ new A(id: "Id1", code: "Code1"),
+ new A(id: "Id2", code: "Code2"),
+ new A(id: "Id3", code: "Code3")
+ ]
+ List list2b = [
+ new A(id: "Id2", code: "Code2"),
+ new A(id: "Id3", code: "Code3"),
+ new A(id: "Id1", code: "Code1")
+ ]
+ List list3 = [
+ new A(id: "Id1", code: "Code1"),
+ new A(id: "Id2", code: "newCode"),
+ new A(id: "newId", code: "Code2")
+ ]
+
+// def 'Test default equals SAME'() {
+// when:
+// def diffNode = ObjectDifferBuilder.startBuilding()
+// .build().compare(list2, list1)
+// then:
+// diffNode.untouched
+// }
+//
+// def 'Test default equals SAME B'() {
+// when:
+// def diffNode = ObjectDifferBuilder.startBuilding()
+// .build().compare(list2b, list1)
+// then:
+// diffNode.untouched
+// }
+//
+// def 'Test default equals CHANGED'() {
+// when:
+// def diffNode = ObjectDifferBuilder.startBuilding()
+// .build().compare(list3, list1)
+// then:
+// diffNode.changed
+// diffNode.getChild(new CollectionItemElementSelector(new A(id: "Id1"))) == null
+// diffNode.getChild(new CollectionItemElementSelector(new A(id: "Id2"))).changed
+// diffNode.getChild(new CollectionItemElementSelector(new A(id: "newId"))).added
+// diffNode.getChild(new CollectionItemElementSelector(new A(id: "Id3"))).removed
+// }
+//
+// def 'Test field CODE equals SAME'() {
+// when:
+// def diffNode = ObjectDifferBuilder.startBuilding()
+// .comparison().ofType(A).toUseEqualsMethodOfValueProvidedByMethod("getCode").and()
+// .build().compare(list2, list1)
+// then:
+// diffNode.state == DiffNode.State.UNTOUCHED
+// }
+//
+// def 'Test field CODE equals SAME B'() {
+// when:
+// def diffNode = ObjectDifferBuilder.startBuilding()
+// .identity().ofType(A).toUse(new CodeIdentity()).and()
+// .build().compare(list2b, list1)
+// then:
+// diffNode.state == DiffNode.State.UNTOUCHED
+// }
+
+ def 'Test field CODE equals equals CHANGED'() {
+ when:
+ def codeStrategy = new CodeIdentity();
+ def diffNode = ObjectDifferBuilder.startBuilding()
+ .identity().ofType(ArrayList) // TODO configuration shouldn't be like this!
+ .toUse(codeStrategy).and()
+ .build().compare(list3, list1)
+ then:
+ diffNode.state == DiffNode.State.CHANGED
+ diffNode.getChild(new CollectionItemElementSelector(new A(code: "Code1"), codeStrategy)) == null
+ diffNode.getChild(new CollectionItemElementSelector(new A(code: "newCode"), codeStrategy)).added
+ diffNode.getChild(new CollectionItemElementSelector(new A(code: "Code2"), codeStrategy)).changed
+ diffNode.getChild(new CollectionItemElementSelector(new A(code: "Code3"), codeStrategy)).removed
+ }
+
+ private void print(final DiffNode diffNode, final Object working,
+ final Object base) {
+ diffNode.visit(new DiffNode.Visitor() {
+ @Override
+ void node(final DiffNode node, final Visit visit) {
+ System.out.println("" + node.getPath() + " " + node.getState() + " "
+ + node.canonicalGet(base) + " => " + node.canonicalGet(working))
+ }
+ })
+ }
+
+ public static class A {
+ String id;
+ String code;
+
+ String getCode() {
+ return code
+ }
+
+ @Override
+ boolean equals(final o) {
+ if (this.is(o)) return true
+ if (!(o instanceof A)) return false
+
+ A a = (A) o
+
+ if (!Objects.equals(id, a.id)) return false
+
+ return true
+ }
+
+ @Override
+ int hashCode() {
+ return (id != null ? id.hashCode() : 0)
+ }
+ }
+
+ public static class CodeIdentity implements IdentityStrategy {
+ @Override
+ boolean equals(final Object _this, final Object o) {
+ return Objects.equals(((A) _this).getCode(), ((A) o).getCode());
+ }
+
+ @Override
+ int hashCode(final Object _this) {
+ return Objects.hashCode(((A) _this).getCode());
+ }
+ }
+}
diff --git a/src/main/java/de/danielbechler/diff/ObjectDifferBuilder.java b/src/main/java/de/danielbechler/diff/ObjectDifferBuilder.java
index 1dd5f369..628c3725 100644
--- a/src/main/java/de/danielbechler/diff/ObjectDifferBuilder.java
+++ b/src/main/java/de/danielbechler/diff/ObjectDifferBuilder.java
@@ -16,6 +16,10 @@
package de.danielbechler.diff;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+
import de.danielbechler.diff.category.CategoryConfigurer;
import de.danielbechler.diff.category.CategoryService;
import de.danielbechler.diff.circular.CircularReferenceConfigurer;
@@ -34,164 +38,160 @@
import de.danielbechler.diff.differ.PrimitiveDiffer;
import de.danielbechler.diff.filtering.FilteringConfigurer;
import de.danielbechler.diff.filtering.ReturnableNodeService;
+import de.danielbechler.diff.identity.IdentityConfigurer;
+import de.danielbechler.diff.identity.IdentityService;
import de.danielbechler.diff.inclusion.InclusionConfigurer;
import de.danielbechler.diff.inclusion.InclusionService;
import de.danielbechler.diff.introspection.IntrospectionConfigurer;
import de.danielbechler.diff.introspection.IntrospectionService;
import de.danielbechler.diff.node.DiffNode;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Set;
-
/**
- * This is the entry point of every diffing operation. It acts as a factory to get hold of an actual {@link
- * ObjectDiffer} instance and exposes a configuration API to customize its behavior to
- * suit your needs.
+ * This is the entry point of every diffing operation. It acts as a factory to
+ * get hold of an actual {@link ObjectDiffer} instance and exposes a
+ * configuration API to customize its behavior to suit your needs.
*
* @author Daniel Bechler
*/
-public class ObjectDifferBuilder
-{
- private final IntrospectionService introspectionService = new IntrospectionService(this);
+public class ObjectDifferBuilder {
+ private final IntrospectionService introspectionService = new IntrospectionService(
+ this);
private final CategoryService categoryService = new CategoryService(this);
- private final InclusionService inclusionService = new InclusionService(categoryService, this);
- private final ComparisonService comparisonService = new ComparisonService(this);
- private final ReturnableNodeService returnableNodeService = new ReturnableNodeService(this);
- private final CircularReferenceService circularReferenceService = new CircularReferenceService(this);
+ private final InclusionService inclusionService = new InclusionService(
+ categoryService, this);
+ private final ComparisonService comparisonService = new ComparisonService(
+ this);
+ private final IdentityService identityService = new IdentityService(this);
+ private final ReturnableNodeService returnableNodeService = new ReturnableNodeService(
+ this);
+ private final CircularReferenceService circularReferenceService = new CircularReferenceService(
+ this);
private final DifferConfigurer differConfigurer = new DifferConfigurerImpl();
private final NodeQueryService nodeQueryService = new NodeQueryServiceImpl();
private final Collection differFactories = new ArrayList();
- private ObjectDifferBuilder()
- {
+ private ObjectDifferBuilder() {
}
- public static ObjectDiffer buildDefault()
- {
+ public static ObjectDiffer buildDefault() {
return startBuilding().build();
}
- public ObjectDiffer build()
- {
+ public ObjectDiffer build() {
final DifferProvider differProvider = new DifferProvider();
final DifferDispatcher differDispatcher = new DifferDispatcher(
- differProvider,
- circularReferenceService,
- circularReferenceService,
- inclusionService,
- returnableNodeService,
- introspectionService);
- differProvider.push(new BeanDiffer(differDispatcher, introspectionService, returnableNodeService, comparisonService, introspectionService));
- differProvider.push(new CollectionDiffer(differDispatcher, comparisonService));
+ differProvider, circularReferenceService,
+ circularReferenceService, inclusionService,
+ returnableNodeService, introspectionService);
+ differProvider.push(new BeanDiffer(differDispatcher,
+ introspectionService, returnableNodeService, comparisonService,
+ introspectionService));
+ differProvider.push(new CollectionDiffer(differDispatcher,
+ comparisonService, identityService));
differProvider.push(new MapDiffer(differDispatcher, comparisonService));
differProvider.push(new PrimitiveDiffer(comparisonService));
- for (final DifferFactory differFactory : differFactories)
- {
- differProvider.push(differFactory.createDiffer(differDispatcher, nodeQueryService));
+ for (final DifferFactory differFactory : differFactories) {
+ differProvider.push(differFactory.createDiffer(differDispatcher,
+ nodeQueryService));
}
return new ObjectDiffer(differDispatcher);
}
- public static ObjectDifferBuilder startBuilding()
- {
+ public static ObjectDifferBuilder startBuilding() {
return new ObjectDifferBuilder();
}
/**
- * Allows to exclude nodes from being added to the object graph based on criteria that are only known after
- * the diff for the affected node and all its children has been determined.
+ * Allows to exclude nodes from being added to the object graph based on
+ * criteria that are only known after the diff for the affected node and all
+ * its children has been determined.
*/
- public FilteringConfigurer filtering()
- {
+ public FilteringConfigurer filtering() {
return returnableNodeService;
}
/**
- * Allows to replace the default bean introspector with a custom implementation.
+ * Allows to replace the default bean introspector with a custom
+ * implementation.
*/
- public IntrospectionConfigurer introspection()
- {
+ public IntrospectionConfigurer introspection() {
return introspectionService;
}
/**
- * Allows to define how the circular reference detector compares object instances.
+ * Allows to define how the circular reference detector compares object
+ * instances.
*/
- public CircularReferenceConfigurer circularReferenceHandling()
- {
+ public CircularReferenceConfigurer circularReferenceHandling() {
return circularReferenceService;
}
/**
- * Allows to in- or exclude nodes based on property name, object type, category or location in the object
- * graph.
+ * Allows to in- or exclude nodes based on property name, object type,
+ * category or location in the object graph.
*/
- public InclusionConfigurer inclusion()
- {
+ public InclusionConfigurer inclusion() {
return inclusionService;
}
/**
* Allows to configure the way objects are compared.
*/
- public ComparisonConfigurer comparison()
- {
+ public ComparisonConfigurer comparison() {
return comparisonService;
}
/**
- * Allows to assign custom categories (or tags) to entire types or selected elements and properties.
+ * Allows to configure the way objects identities are established when
+ * comparing collections by CollectionDiffer.
+ */
+ public IdentityConfigurer identity() {
+ return identityService;
+ }
+
+ /**
+ * Allows to assign custom categories (or tags) to entire types or selected
+ * elements and properties.
*/
- public CategoryConfigurer categories()
- {
+ public CategoryConfigurer categories() {
return categoryService;
}
- public DifferConfigurer differs()
- {
+ public DifferConfigurer differs() {
return differConfigurer;
}
- public class DifferConfigurerImpl implements DifferConfigurer
- {
- public ObjectDifferBuilder register(final DifferFactory differFactory)
- {
+ public class DifferConfigurerImpl implements DifferConfigurer {
+ public ObjectDifferBuilder register(final DifferFactory differFactory) {
differFactories.add(differFactory);
return ObjectDifferBuilder.this;
}
}
- private class NodeQueryServiceImpl implements NodeQueryService
- {
- public Set resolveCategories(final DiffNode node)
- {
+ private class NodeQueryServiceImpl implements NodeQueryService {
+ public Set resolveCategories(final DiffNode node) {
return categoryService.resolveCategories(node);
}
- public boolean isIntrospectable(final DiffNode node)
- {
+ public boolean isIntrospectable(final DiffNode node) {
return introspectionService.isIntrospectable(node);
}
- public boolean isIgnored(final DiffNode node)
- {
+ public boolean isIgnored(final DiffNode node) {
return inclusionService.isIgnored(node);
}
- public boolean isReturnable(final DiffNode node)
- {
+ public boolean isReturnable(final DiffNode node) {
return returnableNodeService.isReturnable(node);
}
- public ComparisonStrategy resolveComparisonStrategy(final DiffNode node)
- {
+ public ComparisonStrategy resolveComparisonStrategy(final DiffNode node) {
return comparisonService.resolveComparisonStrategy(node);
}
- public PrimitiveDefaultValueMode resolvePrimitiveDefaultValueMode(final DiffNode node)
- {
+ public PrimitiveDefaultValueMode resolvePrimitiveDefaultValueMode(
+ final DiffNode node) {
return comparisonService.resolvePrimitiveDefaultValueMode(node);
}
}
diff --git a/src/main/java/de/danielbechler/diff/access/CollectionItemAccessor.java b/src/main/java/de/danielbechler/diff/access/CollectionItemAccessor.java
index b20dd87b..62fb3aa2 100644
--- a/src/main/java/de/danielbechler/diff/access/CollectionItemAccessor.java
+++ b/src/main/java/de/danielbechler/diff/access/CollectionItemAccessor.java
@@ -16,10 +16,13 @@
package de.danielbechler.diff.access;
+import java.util.Collection;
+
+import de.danielbechler.diff.identity.IdentityService;
+import de.danielbechler.diff.identity.IdentityStrategy;
import de.danielbechler.diff.selector.CollectionItemElementSelector;
import de.danielbechler.diff.selector.ElementSelector;
-
-import java.util.Collection;
+import de.danielbechler.util.Assert;
/**
* @author Daniel Bechler
@@ -27,10 +30,31 @@
public class CollectionItemAccessor implements TypeAwareAccessor, Accessor
{
private final Object referenceItem;
+ private final IdentityStrategy identityStrategy;
+ /**
+ * Default implementation uses IdentityService.EQUALS_IDENTITY_STRATEGY.
+ *
+ * @param referenceItem
+ */
public CollectionItemAccessor(final Object referenceItem)
+ {
+ this.referenceItem = referenceItem;
+ this.identityStrategy = IdentityService.EQUALS_IDENTITY_STRATEGY;
+ }
+
+ /**
+ * Allows for custom IdentityStrategy.
+ *
+ * @param referenceItem
+ * @param identityStrategy
+ */
+ public CollectionItemAccessor(final Object referenceItem,
+ final IdentityStrategy identityStrategy)
{
this.referenceItem = referenceItem;
+ Assert.notNull(identityStrategy, "identityStrategy");
+ this.identityStrategy = identityStrategy;
}
@SuppressWarnings("unchecked")
@@ -49,7 +73,8 @@ else if (object instanceof Collection)
public ElementSelector getElementSelector()
{
- return new CollectionItemElementSelector(referenceItem);
+ return new CollectionItemElementSelector(referenceItem,
+ identityStrategy);
}
public void set(final Object target, final Object value)
@@ -76,7 +101,7 @@ public Object get(final Object target)
}
for (final Object item : targetCollection)
{
- if (item != null && item.equals(referenceItem))
+ if (item != null && identityStrategy.equals(item, referenceItem))
{
return item;
}
diff --git a/src/main/java/de/danielbechler/diff/differ/CollectionDiffer.java b/src/main/java/de/danielbechler/diff/differ/CollectionDiffer.java
index 89c7a8a0..2f839bd7 100644
--- a/src/main/java/de/danielbechler/diff/differ/CollectionDiffer.java
+++ b/src/main/java/de/danielbechler/diff/differ/CollectionDiffer.java
@@ -16,129 +16,171 @@
package de.danielbechler.diff.differ;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+
import de.danielbechler.diff.access.Accessor;
import de.danielbechler.diff.access.CollectionItemAccessor;
import de.danielbechler.diff.access.Instances;
import de.danielbechler.diff.comparison.ComparisonStrategy;
import de.danielbechler.diff.comparison.ComparisonStrategyResolver;
+import de.danielbechler.diff.identity.IdentityService;
+import de.danielbechler.diff.identity.IdentityStrategy;
+import de.danielbechler.diff.identity.IdentityStrategyResolver;
import de.danielbechler.diff.node.DiffNode;
import de.danielbechler.util.Assert;
-import de.danielbechler.util.Collections;
-
-import java.util.ArrayList;
-import java.util.Collection;
/**
* Used to find differences between {@link Collection Collections}.
*
* @author Daniel Bechler
*/
-public final class CollectionDiffer implements Differ
-{
+public final class CollectionDiffer implements Differ {
private final DifferDispatcher differDispatcher;
private final ComparisonStrategyResolver comparisonStrategyResolver;
+ private final IdentityStrategyResolver identityStrategyResolver;
public CollectionDiffer(final DifferDispatcher differDispatcher,
- final ComparisonStrategyResolver comparisonStrategyResolver)
- {
+ final ComparisonStrategyResolver comparisonStrategyResolver,
+ final IdentityStrategyResolver identityStrategyResolver) {
Assert.notNull(differDispatcher, "differDispatcher");
this.differDispatcher = differDispatcher;
Assert.notNull(comparisonStrategyResolver, "comparisonStrategyResolver");
this.comparisonStrategyResolver = comparisonStrategyResolver;
+
+ Assert.notNull(identityStrategyResolver, "identityStrategyResolver");
+ this.identityStrategyResolver = identityStrategyResolver;
}
- private static void compareUsingComparisonStrategy(final DiffNode collectionNode,
- final Instances collectionInstances,
- final ComparisonStrategy comparisonStrategy)
- {
- comparisonStrategy.compare(collectionNode, collectionInstances.getType(), collectionInstances.getWorking(Collection.class), collectionInstances.getBase(Collection.class));
+ private static void compareUsingComparisonStrategy(
+ final DiffNode collectionNode, final Instances collectionInstances,
+ final ComparisonStrategy comparisonStrategy) {
+ comparisonStrategy.compare(collectionNode,
+ collectionInstances.getType(),
+ collectionInstances.getWorking(Collection.class),
+ collectionInstances.getBase(Collection.class));
}
- private static DiffNode newNode(final DiffNode parentNode, final Instances collectionInstances)
- {
+ private static DiffNode newNode(final DiffNode parentNode,
+ final Instances collectionInstances) {
final Accessor accessor = collectionInstances.getSourceAccessor();
final Class> type = collectionInstances.getType();
return new DiffNode(parentNode, accessor, type);
}
- private static Collection> addedItemsOf(final Instances instances)
- {
- final Collection> working = instances.getWorking(Collection.class);
- final Collection> base = instances.getBase(Collection.class);
- return Collections.filteredCopyOf(working, base);
- }
-
- private static Collection> removedItemsOf(final Instances instances)
- {
- final Collection> working = instances.getWorking(Collection.class);
- final Collection> base = instances.getBase(Collection.class);
- return Collections.filteredCopyOf(base, working);
- }
-
- private static Iterable> knownItemsOf(final Instances instances)
- {
- final Collection> working = instances.getWorking(Collection.class);
- final Collection