Skip to content

Commit 49cdf74

Browse files
authored
ReadOnlyStringMap: Generalize equals/hashCode across implementations (#3675)
This change rewrites the `equals` and `hashCode` implementations throughout the `ReadOnlyStringMap` hierarchy to use a common implementation that supports value-based equality between implementations. Fixes #3669.
1 parent c299479 commit 49cdf74

File tree

10 files changed

+132
-75
lines changed

10 files changed

+132
-75
lines changed

log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventWrapper.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,5 +213,24 @@ public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final
213213
public <V> V getValue(final String key) {
214214
return (V) super.get(key);
215215
}
216+
217+
@Override
218+
public boolean equals(final Object obj) {
219+
if (obj == this) {
220+
return true;
221+
}
222+
if (obj instanceof ReadOnlyStringMap) {
223+
// Convert to maps and compare
224+
final Map<String, String> thisMap = toMap();
225+
final Map<String, String> otherMap = ((ReadOnlyStringMap) obj).toMap();
226+
return thisMap.equals(otherMap);
227+
}
228+
return super.equals(obj);
229+
}
230+
231+
@Override
232+
public int hashCode() {
233+
return toMap().hashCode();
234+
}
216235
}
217236
}

log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,21 +181,25 @@ public String toString() {
181181

182182
@Override
183183
public int hashCode() {
184-
final int prime = 31;
185-
int result = 1;
186-
final Object[] state = localState.get();
187-
result = prime * result + ((state == null) ? 0 : getMap(state).hashCode());
188-
return result;
184+
return toMap().hashCode();
189185
}
190186

191187
@Override
192-
public boolean equals(final Object obj) {
188+
public boolean equals(Object obj) {
193189
if (this == obj) {
194190
return true;
195191
}
196192
if (obj == null) {
197193
return false;
198194
}
195+
if (obj instanceof ReadOnlyStringMap) {
196+
if (size() != ((ReadOnlyStringMap) obj).size()) {
197+
return false;
198+
}
199+
200+
// Convert to maps and compare
201+
obj = ((ReadOnlyStringMap) obj).toMap();
202+
}
199203
if (!(obj instanceof ThreadContextMap)) {
200204
return false;
201205
}

log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -408,39 +408,22 @@ public boolean equals(final Object obj) {
408408
if (obj == this) {
409409
return true;
410410
}
411-
if (!(obj instanceof SortedArrayStringMap)) {
411+
if (!(obj instanceof ReadOnlyStringMap)) {
412412
return false;
413413
}
414-
final SortedArrayStringMap other = (SortedArrayStringMap) obj;
415-
if (this.size() != other.size()) {
414+
if (size() != ((ReadOnlyStringMap) obj).size()) {
416415
return false;
417416
}
418-
for (int i = 0; i < size(); i++) {
419-
if (!Objects.equals(keys[i], other.keys[i])) {
420-
return false;
421-
}
422-
if (!Objects.equals(values[i], other.values[i])) {
423-
return false;
424-
}
425-
}
426-
return true;
417+
418+
// Convert to maps and compare
419+
final Map<String, String> thisMap = toMap();
420+
final Map<String, String> otherMap = ((ReadOnlyStringMap) obj).toMap();
421+
return thisMap.equals(otherMap);
427422
}
428423

429424
@Override
430425
public int hashCode() {
431-
int result = 37;
432-
result = HASHVAL * result + size;
433-
result = HASHVAL * result + hashCode(keys, size);
434-
result = HASHVAL * result + hashCode(values, size);
435-
return result;
436-
}
437-
438-
private static int hashCode(final Object[] values, final int length) {
439-
int result = 1;
440-
for (int i = 0; i < length; i++) {
441-
result = HASHVAL * result + (values[i] == null ? 0 : values[i].hashCode());
442-
}
443-
return result;
426+
return toMap().hashCode();
444427
}
445428

446429
@Override

log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* There are no guarantees for binary or logical compatibility in this package.
2121
*/
2222
@Export
23-
@Version("2.24.1")
23+
@Version("2.25.0")
2424
package org.apache.logging.log4j.util;
2525

2626
import org.osgi.annotation.bundle.Export;

log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMap.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,27 @@ public int size() {
114114
public Map<String, String> toMap() {
115115
return null;
116116
}
117+
118+
@Override
119+
public boolean equals(final Object obj) {
120+
if (obj == this) {
121+
return true;
122+
}
123+
if (!(obj instanceof ReadOnlyStringMap)) {
124+
return false;
125+
}
126+
if (size() != ((ReadOnlyStringMap) obj).size()) {
127+
return false;
128+
}
129+
130+
// Convert to maps and compare
131+
final Map<String, String> thisMap = toMap();
132+
final Map<String, String> otherMap = ((ReadOnlyStringMap) obj).toMap();
133+
return thisMap.equals(otherMap);
134+
}
135+
136+
@Override
137+
public int hashCode() {
138+
return toMap().hashCode();
139+
}
117140
}

log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMapWithoutIntConstructor.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,27 @@ public void putValue(final String key, final Object value) {}
8181

8282
@Override
8383
public void remove(final String key) {}
84+
85+
@Override
86+
public boolean equals(final Object obj) {
87+
if (obj == this) {
88+
return true;
89+
}
90+
if (!(obj instanceof ReadOnlyStringMap)) {
91+
return false;
92+
}
93+
if (size() != ((ReadOnlyStringMap) obj).size()) {
94+
return false;
95+
}
96+
97+
// Convert to maps and compare
98+
final Map<String, String> thisMap = toMap();
99+
final Map<String, String> otherMap = ((ReadOnlyStringMap) obj).toMap();
100+
return thisMap.equals(otherMap);
101+
}
102+
103+
@Override
104+
public int hashCode() {
105+
return toMap().hashCode();
106+
}
84107
}

log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.Map;
3636
import java.util.stream.Stream;
3737
import org.apache.logging.log4j.util.BiConsumer;
38+
import org.apache.logging.log4j.util.SortedArrayStringMap;
3839
import org.apache.logging.log4j.util.TriConsumer;
3940
import org.junit.jupiter.api.Test;
4041
import org.junit.jupiter.params.ParameterizedTest;
@@ -850,6 +851,26 @@ void testForEachTriConsumer() {
850851
assertEquals(state.count, original.size());
851852
}
852853

854+
@Test
855+
void testEqualityWithOtherImplementations() {
856+
final JdkMapAdapterStringMap left = new JdkMapAdapterStringMap();
857+
final SortedArrayStringMap right = new SortedArrayStringMap();
858+
assertEquals(left, right);
859+
assertEquals(left.hashCode(), right.hashCode());
860+
861+
left.putValue("a", "avalue");
862+
left.putValue("B", "Bvalue");
863+
right.putValue("B", "Bvalue");
864+
right.putValue("a", "avalue");
865+
assertEquals(left, right);
866+
assertEquals(left.hashCode(), right.hashCode());
867+
868+
left.remove("a");
869+
right.remove("a");
870+
assertEquals(left, right);
871+
assertEquals(left.hashCode(), right.hashCode());
872+
}
873+
853874
static Stream<Arguments> testImmutability() {
854875
return Stream.of(
855876
Arguments.of(new HashMap<>(), false),

log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,15 +219,21 @@ public boolean equals(final Object object) {
219219
if (object == this) {
220220
return true;
221221
}
222-
if (!(object instanceof JdkMapAdapterStringMap)) {
222+
if (!(object instanceof ReadOnlyStringMap)) {
223223
return false;
224224
}
225-
final JdkMapAdapterStringMap other = (JdkMapAdapterStringMap) object;
226-
return map.equals(other.map) && immutable == other.immutable;
225+
if (size() != ((ReadOnlyStringMap) object).size()) {
226+
return false;
227+
}
228+
229+
// Convert to maps and compare
230+
final Map<String, String> thisMap = toMap();
231+
final Map<String, String> otherMap = ((ReadOnlyStringMap) object).toMap();
232+
return thisMap.equals(otherMap);
227233
}
228234

229235
@Override
230236
public int hashCode() {
231-
return map.hashCode() + (immutable ? 31 : 0);
237+
return toMap().hashCode();
232238
}
233239
}

log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/nogc/OpenHashStringMap.java

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.ConcurrentModificationException;
2525
import java.util.HashMap;
2626
import java.util.Map;
27-
import java.util.Objects;
2827
import org.apache.logging.log4j.spi.ThreadContextMap;
2928
import org.apache.logging.log4j.util.BiConsumer;
3029
import org.apache.logging.log4j.util.ReadOnlyStringMap;
@@ -301,27 +300,14 @@ public boolean equals(final Object obj) {
301300
if (!(obj instanceof ReadOnlyStringMap)) {
302301
return false;
303302
}
304-
final ReadOnlyStringMap other = (ReadOnlyStringMap) obj;
305-
if (other.size() != size()) {
303+
if (size() != ((ReadOnlyStringMap) obj).size()) {
306304
return false;
307305
}
308-
int pos = arraySize;
309-
if (containsNullKey) {
310-
if (!Objects.equals(getObjectValue(null), other.getValue(null))) {
311-
return false;
312-
}
313-
}
314-
--pos;
315-
final K myKeys[] = this.keys;
316-
for (; pos >= 0; pos--) {
317-
K k;
318-
if ((k = myKeys[pos]) != null) {
319-
if (!Objects.equals(values[pos], other.getValue((String) k))) {
320-
return false;
321-
}
322-
}
323-
}
324-
return true;
306+
307+
// Convert to maps and compare
308+
final Map<String, String> thisMap = toMap();
309+
final Map<String, String> otherMap = ((ReadOnlyStringMap) obj).toMap();
310+
return thisMap.equals(otherMap);
325311
}
326312

327313
@Override
@@ -699,25 +685,7 @@ protected void rehash(final int newN) {
699685
*/
700686
@Override
701687
public int hashCode() {
702-
int result = 0;
703-
for (int j = realSize(), i = 0, t = 0; j-- != 0; ) {
704-
while (keys[i] == null) {
705-
i++;
706-
}
707-
if (this != keys[i]) {
708-
t = keys[i].hashCode();
709-
}
710-
if (this != values[i]) {
711-
t ^= (values[i] == null ? 0 : values[i].hashCode());
712-
}
713-
result += t;
714-
i++;
715-
}
716-
// Zero / null keys have hash zero.
717-
if (containsNullKey) {
718-
result += (values[arraySize] == null ? 0 : values[arraySize].hashCode());
719-
}
720-
return result;
688+
return toMap().hashCode();
721689
}
722690

723691
@SuppressWarnings("unchecked")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="https://logging.apache.org/xml/ns"
4+
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
5+
type="fixed">
6+
<issue id="3669" link="https://github.com/apache/logging-log4j2/issues/3669"/>
7+
<description format="asciidoc">
8+
The ReadOnlyStringMap implementations now support equality comparisons against each other.
9+
</description>
10+
</entry>

0 commit comments

Comments
 (0)