Skip to content

Commit f06ca14

Browse files
committed
added support to deal with bi-directional relationships and cycles in
general
1 parent 90238a2 commit f06ca14

File tree

11 files changed

+613
-0
lines changed

11 files changed

+613
-0
lines changed

src/main/java/de/danielbechler/diff/BeanDiffer.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package de.danielbechler.diff;
1818

19+
import java.util.HashSet;
20+
import java.util.Set;
21+
1922
import de.danielbechler.diff.accessor.*;
2023
import de.danielbechler.diff.introspect.*;
2124
import de.danielbechler.diff.node.*;
@@ -29,6 +32,7 @@
2932
final class BeanDiffer extends AbstractDiffer
3033
{
3134
private Introspector introspector = new StandardIntrospector();
35+
private Set<InstanceOutline> visitedInstances = new HashSet<InstanceOutline>();
3236

3337
BeanDiffer()
3438
{
@@ -64,6 +68,8 @@ else if (instances.getType() == null)
6468
}
6569
else
6670
{
71+
if(checkAlreadyVisited(instances))
72+
return node;
6773
return compareBean(parentNode, instances);
6874
}
6975
return node;
@@ -143,4 +149,18 @@ void setIntrospector(final Introspector introspector)
143149
Assert.notNull(introspector, "introspector");
144150
this.introspector = introspector;
145151
}
152+
153+
private boolean checkAlreadyVisited(Instances instances)
154+
{
155+
InstanceOutline outline = InstanceOutline.from(instances);
156+
if(outline != null) {
157+
if(visitedInstances.contains(outline)) {
158+
// if line below is commented in test case bidirectionalGraphStackOverflow creates a stack overflow
159+
// visitedInstances.remove(outline);
160+
return true;
161+
}
162+
visitedInstances.add(outline);
163+
}
164+
return false;
165+
}
146166
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package de.danielbechler.diff;
2+
3+
public class InstanceOutline {
4+
5+
private Object comparisonObject;
6+
private Object working;
7+
private Object base;
8+
9+
public InstanceOutline() {
10+
super();
11+
}
12+
13+
public InstanceOutline(Object comparisonObject, Object working, Object base) {
14+
super();
15+
this.comparisonObject = comparisonObject;
16+
this.working = working;
17+
this.base = base;
18+
}
19+
20+
21+
public static InstanceOutline from(Instances instances) {
22+
if(instances.getWorking() == null)
23+
return null;
24+
if(instances.getBase() == null)
25+
return null;
26+
Object comparisonObject = instances.getComparisonObject();
27+
if(comparisonObject == null)
28+
return null;
29+
return new InstanceOutline(comparisonObject, instances.getWorking(), instances.getBase());
30+
}
31+
32+
@Override
33+
public int hashCode() {
34+
final int prime = 31;
35+
int result = 1;
36+
result = prime * result + ((base == null) ? 0 : base.hashCode());
37+
result = prime * result + ((comparisonObject == null) ? 0 : comparisonObject.hashCode());
38+
result = prime * result + ((working == null) ? 0 : working.hashCode());
39+
return result;
40+
}
41+
42+
@Override
43+
public boolean equals(Object obj) {
44+
if (this == obj)
45+
return true;
46+
if (obj == null)
47+
return false;
48+
if (getClass() != obj.getClass())
49+
return false;
50+
InstanceOutline other = (InstanceOutline) obj;
51+
if (base == null) {
52+
if (other.base != null)
53+
return false;
54+
} else if (!base.equals(other.base))
55+
return false;
56+
if (comparisonObject == null) {
57+
if (other.comparisonObject != null)
58+
return false;
59+
} else if (!comparisonObject.equals(other.comparisonObject))
60+
return false;
61+
if (working == null) {
62+
if (other.working != null)
63+
return false;
64+
} else if (!working.equals(other.working))
65+
return false;
66+
return true;
67+
}
68+
69+
}

src/main/java/de/danielbechler/diff/Instances.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,11 @@ public PropertyPath getPropertyPath(final Node parentNode)
169169
.withElement(sourceAccessor.getPathElement())
170170
.build();
171171
}
172+
173+
public Object getComparisonObject()
174+
{
175+
if(sourceAccessor == null)
176+
return null;
177+
return sourceAccessor.getComparisonObject();
178+
}
172179
}

src/main/java/de/danielbechler/diff/accessor/AbstractAccessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,9 @@ public void setIgnored(final boolean ignored)
5454
{
5555
this.ignored = ignored;
5656
}
57+
58+
public Object getComparisonObject() {
59+
// answer the default, redefine in subclass as appropriate
60+
return null;
61+
}
5762
}

src/main/java/de/danielbechler/diff/accessor/Accessor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ public interface Accessor extends PropertyDescriptor
2424
void set(Object target, Object value);
2525

2626
void unset(Object target);
27+
28+
Object getComparisonObject();
2729
}

src/main/java/de/danielbechler/diff/accessor/CollectionItemAccessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,9 @@ public void unset(final Object target)
9595
targetCollection.remove(referenceItem);
9696
}
9797
}
98+
99+
@Override
100+
public Object getComparisonObject() {
101+
return referenceItem;
102+
}
98103
}

src/main/java/de/danielbechler/diff/accessor/MapEntryAccessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,9 @@ public void unset(final Object target)
8989
targetMap.remove(getReferenceKey());
9090
}
9191
}
92+
93+
@Override
94+
public Object getComparisonObject() {
95+
return getReferenceKey();
96+
}
9297
}

src/main/java/de/danielbechler/diff/accessor/PropertyAccessor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import de.danielbechler.diff.accessor.exception.*;
2020
import de.danielbechler.diff.path.*;
2121
import de.danielbechler.util.*;
22+
2223
import org.slf4j.*;
2324

2425
import java.lang.reflect.*;
@@ -189,4 +190,13 @@ public Element getPathElement()
189190
{
190191
return new NamedPropertyElement(this.propertyName);
191192
}
193+
194+
@Override
195+
public Object getComparisonObject() {
196+
if(Classes.isSimpleType(getType())) {
197+
// ignore, because no comparison on the level of user-defined objects
198+
return null;
199+
}
200+
return getPropertyName();
201+
}
192202
}

src/main/java/de/danielbechler/diff/node/DefaultNode.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,4 +387,10 @@ else if (getChildren().size() > 1)
387387
sb.append(" }");
388388
return sb.toString();
389389
}
390+
391+
@Override
392+
public Object getComparisonObject() {
393+
// not needed here, but has to be implemented, because of the Accessor interface
394+
throw new UnsupportedOperationException();
395+
}
390396
}

0 commit comments

Comments
 (0)