Skip to content

Commit 7bc7fd9

Browse files
committed
fix: equals and hashcode of several classes
Signed-off-by: christian.lutnik <[email protected]>
1 parent 67b34f8 commit 7bc7fd9

File tree

12 files changed

+329
-25
lines changed

12 files changed

+329
-25
lines changed

src/main/java/dev/openfeature/sdk/AbstractStructure.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ abstract class AbstractStructure implements Structure {
1111

1212
@Override
1313
public boolean isEmpty() {
14-
return attributes == null || attributes.size() == 0;
14+
return attributes == null || attributes.isEmpty();
1515
}
1616

1717
AbstractStructure() {
@@ -46,4 +46,46 @@ public Map<String, Object> asObjectMap() {
4646
(accumulated, entry) -> accumulated.put(entry.getKey(), convertValue(entry.getValue())),
4747
HashMap::putAll);
4848
}
49+
50+
public boolean equals(final Object o) {
51+
if (o == this) {
52+
return true;
53+
}
54+
if (!(o instanceof AbstractStructure)) {
55+
return false;
56+
}
57+
final AbstractStructure other = (AbstractStructure) o;
58+
if (other.attributes == attributes) {
59+
return true;
60+
}
61+
if (attributes == null || other.attributes == null) {
62+
return false;
63+
}
64+
if (other.attributes.size() != attributes.size()) {
65+
return false;
66+
}
67+
68+
for (Map.Entry<String, Value> thisEntry : attributes.entrySet()) {
69+
Value thisValue = thisEntry.getValue();
70+
Value otherValue = other.attributes.get(thisEntry.getKey());
71+
if (thisValue == null && otherValue == null) {
72+
continue;
73+
}
74+
if (thisValue == null || otherValue == null) {
75+
return false;
76+
}
77+
if (!thisValue.equals(otherValue)) {
78+
return false;
79+
}
80+
}
81+
82+
return true;
83+
}
84+
85+
public int hashCode() {
86+
if (attributes == null) {
87+
return 0;
88+
}
89+
return attributes.hashCode();
90+
}
4991
}

src/main/java/dev/openfeature/sdk/ImmutableContext.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,23 @@ public EvaluationContext merge(EvaluationContext overridingContext) {
9191
return new ImmutableContext(attributes);
9292
}
9393

94+
@Override
95+
public boolean equals(Object object) {
96+
if (object == this) {
97+
return true;
98+
}
99+
if (!(object instanceof ImmutableContext)) {
100+
return false;
101+
}
102+
ImmutableContext other = (ImmutableContext) object;
103+
return this.structure.equals(other.structure);
104+
}
105+
106+
@Override
107+
public int hashCode() {
108+
return structure.hashCode();
109+
}
110+
94111
@SuppressWarnings("all")
95112
private static class DelegateExclusions {
96113
@ExcludeFromGeneratedCoverageReport

src/main/java/dev/openfeature/sdk/ImmutableMetadata.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,43 @@ public boolean isEmpty() {
101101
return metadata.isEmpty();
102102
}
103103

104+
105+
public boolean equals(final Object o) {
106+
if (o == this) {
107+
return true;
108+
}
109+
if (!(o instanceof ImmutableMetadata)) {
110+
return false;
111+
}
112+
final ImmutableMetadata other = (ImmutableMetadata) o;
113+
if (other.metadata == metadata) {
114+
return true;
115+
}
116+
if (other.metadata.size() != metadata.size()) {
117+
return false;
118+
}
119+
120+
for (Map.Entry<String, Object> thisEntry : metadata.entrySet()) {
121+
Object thisValue = thisEntry.getValue();
122+
Object otherValue = other.metadata.get(thisEntry.getKey());
123+
if (thisValue == null && otherValue == null) {
124+
continue;
125+
}
126+
if (thisValue == null || otherValue == null) {
127+
return false;
128+
}
129+
if (!thisValue.equals(otherValue)) {
130+
return false;
131+
}
132+
}
133+
134+
return true;
135+
}
136+
137+
public int hashCode() {
138+
return metadata.hashCode();
139+
}
140+
104141
/**
105142
* Obtain a builder for {@link ImmutableMetadata}.
106143
*/

src/main/java/dev/openfeature/sdk/ImmutableStructure.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import java.util.Map.Entry;
77
import java.util.Optional;
88
import java.util.Set;
9-
import lombok.EqualsAndHashCode;
109
import lombok.ToString;
1110

1211
/**
@@ -18,7 +17,6 @@
1817
* not be modified after instantiation. All references are clones.
1918
*/
2019
@ToString
21-
@EqualsAndHashCode
2220
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
2321
public final class ImmutableStructure extends AbstractStructure {
2422

@@ -38,7 +36,7 @@ public ImmutableStructure(Map<String, Value> attributes) {
3836
super(copyAttributes(attributes, null));
3937
}
4038

41-
protected ImmutableStructure(String targetingKey, Map<String, Value> attributes) {
39+
ImmutableStructure(String targetingKey, Map<String, Value> attributes) {
4240
super(copyAttributes(attributes, targetingKey));
4341
}
4442

src/main/java/dev/openfeature/sdk/MutableContext.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.HashMap;
66
import java.util.List;
77
import java.util.Map;
8+
import java.util.Objects;
89
import java.util.function.Function;
910
import lombok.EqualsAndHashCode;
1011
import lombok.ToString;
@@ -17,7 +18,6 @@
1718
* be modified after instantiation.
1819
*/
1920
@ToString
20-
@EqualsAndHashCode
2121
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
2222
public class MutableContext implements EvaluationContext {
2323

@@ -125,6 +125,23 @@ public EvaluationContext merge(EvaluationContext overridingContext) {
125125
return new MutableContext(attributes);
126126
}
127127

128+
@Override
129+
public boolean equals(Object object) {
130+
if (object == this) {
131+
return true;
132+
}
133+
if (!(object instanceof MutableContext)) {
134+
return false;
135+
}
136+
MutableContext other = (MutableContext) object;
137+
return this.structure.equals(other.structure);
138+
}
139+
140+
@Override
141+
public int hashCode() {
142+
return structure.hashCode();
143+
}
144+
128145
/**
129146
* Hidden class to tell Lombok not to copy these methods over via delegation.
130147
*/

src/main/java/dev/openfeature/sdk/MutableStructure.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import java.util.List;
66
import java.util.Map;
77
import java.util.Set;
8-
import lombok.EqualsAndHashCode;
98
import lombok.ToString;
109

1110
/**
@@ -15,7 +14,6 @@
1514
* be modified after instantiation.
1615
*/
1716
@ToString
18-
@EqualsAndHashCode
1917
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
2018
public class MutableStructure extends AbstractStructure {
2119

src/main/java/dev/openfeature/sdk/Value.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import java.util.List;
88
import java.util.Map;
99
import java.util.stream.Collectors;
10-
import lombok.EqualsAndHashCode;
1110
import lombok.SneakyThrows;
1211
import lombok.ToString;
1312

@@ -17,7 +16,6 @@
1716
* This intermediate representation provides a good medium of exchange.
1817
*/
1918
@ToString
20-
@EqualsAndHashCode
2119
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType", "checkstyle:NoFinalizer"})
2220
public class Value implements Cloneable {
2321

@@ -316,4 +314,22 @@ public static Value objectToValue(Object object) {
316314
throw new TypeMismatchError("Flag value " + object + " had unexpected type " + object.getClass() + ".");
317315
}
318316
}
317+
318+
public boolean equals(final Object o) {
319+
if (o == this) {
320+
return true;
321+
}
322+
if (!(o instanceof Value)) {
323+
return false;
324+
}
325+
final Value other = (Value) o;
326+
return innerObject.equals(other.innerObject);
327+
}
328+
329+
public int hashCode() {
330+
if (innerObject == null) {
331+
return 0;
332+
}
333+
return innerObject.hashCode();
334+
}
319335
}

src/test/java/dev/openfeature/sdk/ImmutableContextTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static dev.openfeature.sdk.EvaluationContext.TARGETING_KEY;
44
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
55
import static org.junit.jupiter.api.Assertions.assertEquals;
6+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
67
import static org.junit.jupiter.api.Assertions.assertTrue;
78

89
import java.util.Collections;
@@ -133,4 +134,31 @@ void mergeShouldRetainItsSubkeysWhenOverridingContextHasNoTargetingKey() {
133134
Structure value = key1.asStructure();
134135
assertArrayEquals(new Object[] {"key1_1"}, value.keySet().toArray());
135136
}
137+
138+
@DisplayName("Two different MutableContext objects with the different contents are not considered equal")
139+
@Test
140+
void unequalImmutableContextsAreNotEqual() {
141+
final Map<String, Value> attributes = new HashMap<>();
142+
attributes.put("key1", new Value("val1"));
143+
final ImmutableContext ctx = new ImmutableContext(attributes);
144+
145+
final Map<String, Value> attributes2 = new HashMap<>();
146+
final ImmutableContext ctx2 = new ImmutableContext(attributes2);
147+
148+
assertNotEquals(ctx, ctx2);
149+
}
150+
151+
@DisplayName("Two different MutableContext objects with the same content are considered equal")
152+
@Test
153+
void equalImmutableContextsAreEqual() {
154+
final Map<String, Value> attributes = new HashMap<>();
155+
attributes.put("key1", new Value("val1"));
156+
final ImmutableContext ctx = new ImmutableContext(attributes);
157+
158+
final Map<String, Value> attributes2 = new HashMap<>();
159+
attributes2.put("key1", new Value("val1"));
160+
final ImmutableContext ctx2 = new ImmutableContext(attributes2);
161+
162+
assertEquals(ctx, ctx2);
163+
}
136164
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.openfeature.sdk;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
class ImmutableMetadataTest {
9+
@Test
10+
void unequalImmutableMetadataAreUnequal() {
11+
ImmutableMetadata i1 = ImmutableMetadata.builder().addString("key1", "value1").build();
12+
ImmutableMetadata i2 = ImmutableMetadata.builder().addString("key1", "value2").build();
13+
14+
assertNotEquals(i1, i2);
15+
}
16+
17+
@Test
18+
void equalImmutableMetadataAreEqual() {
19+
ImmutableMetadata i1 = ImmutableMetadata.builder().addString("key1", "value1").build();
20+
ImmutableMetadata i2 = ImmutableMetadata.builder().addString("key1", "value1").build();
21+
22+
assertEquals(i1, i2);
23+
}
24+
}

src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package dev.openfeature.sdk;
22

3-
import static org.junit.jupiter.api.Assertions.*;
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
6+
import static org.junit.jupiter.api.Assertions.assertNotSame;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
49

510
import java.time.Instant;
611
import java.time.temporal.ChronoUnit;
@@ -154,4 +159,74 @@ void constructorHandlesNullValue() {
154159
attrs.put("null", null);
155160
new ImmutableStructure(attrs);
156161
}
162+
163+
@Test
164+
void unequalImmutableStructuresAreNotEqual() {
165+
Map<String, Value> attrs1 = new HashMap<>();
166+
attrs1.put("test", new Value(45));
167+
ImmutableStructure structure1 = new ImmutableStructure(attrs1);
168+
169+
Map<String, Value> attrs2 = new HashMap<>();
170+
attrs2.put("test", new Value(2));
171+
ImmutableStructure structure2 = new ImmutableStructure(attrs2);
172+
173+
assertNotEquals(structure1, structure2);
174+
}
175+
176+
@Test
177+
void equalImmutableStructuresAreEqual() {
178+
Map<String, Value> attrs1 = new HashMap<>();
179+
attrs1.put("test", new Value(45));
180+
ImmutableStructure structure1 = new ImmutableStructure(attrs1);
181+
182+
Map<String, Value> attrs2 = new HashMap<>();
183+
attrs2.put("test", new Value(45));
184+
ImmutableStructure structure2 = new ImmutableStructure(attrs2);
185+
186+
assertEquals(structure1, structure2);
187+
}
188+
189+
@Test
190+
void unequalMutableStructuresAreNotEqual() {
191+
MutableStructure m1 = new MutableStructure();
192+
m1.add("key1", "val1");
193+
MutableStructure m2 = new MutableStructure();
194+
m2.add("key2", "val2");
195+
assertNotEquals(m1, m2);
196+
}
197+
198+
@Test
199+
void equalMutableStructuresAreEqual() {
200+
MutableStructure m1 = new MutableStructure();
201+
m1.add("key1", "val1");
202+
MutableStructure m2 = new MutableStructure();
203+
m2.add("key1", "val1");
204+
assertEquals(m1, m2);
205+
}
206+
207+
@Test
208+
void equalAbstractStructuresOfDifferentTypesAreEqual() {
209+
MutableStructure m1 = new MutableStructure();
210+
m1.add("key1", "val1");
211+
HashMap<String, Value> map = new HashMap<>();
212+
map.put("key1", new Value("val1"));
213+
AbstractStructure m2 = new AbstractStructure(map) {
214+
@Override
215+
public Set<String> keySet() {
216+
return attributes.keySet();
217+
}
218+
219+
@Override
220+
public Value getValue(String key) {
221+
return attributes.get(key);
222+
}
223+
224+
@Override
225+
public Map<String, Value> asMap() {
226+
return attributes;
227+
}
228+
};
229+
230+
assertEquals(m1, m2);
231+
}
157232
}

0 commit comments

Comments
 (0)