Skip to content

Commit 924ec08

Browse files
committed
ReadOnlyStringMap: Generalize equals/hashCode across implementations
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 2bc484c commit 924ec08

File tree

10 files changed

+169
-77
lines changed

10 files changed

+169
-77
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.logging.log4j.spi.MutableThreadContextStack;
3737
import org.apache.logging.log4j.util.BiConsumer;
3838
import org.apache.logging.log4j.util.ReadOnlyStringMap;
39+
import org.apache.logging.log4j.util.ReadOnlyStringMapUtil;
3940
import org.apache.logging.log4j.util.TriConsumer;
4041

4142
/**
@@ -213,5 +214,21 @@ public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final
213214
public <V> V getValue(final String key) {
214215
return (V) super.get(key);
215216
}
217+
218+
@Override
219+
public boolean equals(final Object obj) {
220+
if (obj == this) {
221+
return true;
222+
}
223+
if (obj instanceof ReadOnlyStringMap) {
224+
return ReadOnlyStringMapUtil.equals(this, (ReadOnlyStringMap) obj);
225+
}
226+
return super.equals(obj);
227+
}
228+
229+
@Override
230+
public int hashCode() {
231+
return ReadOnlyStringMapUtil.hashCode(this);
232+
}
216233
}
217234
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.logging.log4j.util.BiConsumer;
2525
import org.apache.logging.log4j.util.PropertiesUtil;
2626
import org.apache.logging.log4j.util.ReadOnlyStringMap;
27+
import org.apache.logging.log4j.util.ReadOnlyStringMapUtil;
2728
import org.apache.logging.log4j.util.TriConsumer;
2829

2930
/**
@@ -181,11 +182,7 @@ public String toString() {
181182

182183
@Override
183184
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;
185+
return ReadOnlyStringMapUtil.hashCode(this);
189186
}
190187

191188
@Override
@@ -196,6 +193,9 @@ public boolean equals(final Object obj) {
196193
if (obj == null) {
197194
return false;
198195
}
196+
if (obj instanceof ReadOnlyStringMap) {
197+
return ReadOnlyStringMapUtil.equals(this, (ReadOnlyStringMap) obj);
198+
}
199199
if (!(obj instanceof ThreadContextMap)) {
200200
return false;
201201
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.util;
18+
19+
import aQute.bnd.annotation.baseline.BaselineIgnore;
20+
import java.util.Map;
21+
22+
/**
23+
* Utility class for ReadOnlyStringMap implementations. Provides methods for equals and hashCode calculations.
24+
*
25+
* @since 2.25.0
26+
*/
27+
@InternalApi
28+
@BaselineIgnore("2.25.0")
29+
public final class ReadOnlyStringMapUtil {
30+
31+
private ReadOnlyStringMapUtil() {}
32+
33+
/**
34+
* Compares two ReadOnlyStringMap instances for equality.
35+
* Two ReadOnlyStringMap instances are considered equal if they have the same size
36+
* and contain the same key-value pairs.
37+
*
38+
* @param map1 the first ReadOnlyStringMap to compare
39+
* @param map2 the second ReadOnlyStringMap to compare
40+
* @return true if the maps are equal, false otherwise
41+
*/
42+
public static boolean equals(final ReadOnlyStringMap map1, final ReadOnlyStringMap map2) {
43+
if (map1 == map2) {
44+
return true;
45+
}
46+
if (map1 == null || map2 == null) {
47+
return false;
48+
}
49+
if (map1.size() != map2.size()) {
50+
return false;
51+
}
52+
53+
// Convert to maps and compare
54+
final Map<String, String> thisMap = map1.toMap();
55+
final Map<String, String> otherMap = map2.toMap();
56+
return thisMap.equals(otherMap);
57+
}
58+
59+
/**
60+
* Calculates the hash code for a ReadOnlyStringMap.
61+
* The hash code is calculated based on the key-value pairs in the map.
62+
*
63+
* @param map the ReadOnlyStringMap to calculate the hash code for
64+
* @return the hash code
65+
*/
66+
public static int hashCode(final ReadOnlyStringMap map) {
67+
if (map == null) {
68+
return 0;
69+
}
70+
return map.toMap().hashCode();
71+
}
72+
}

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

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -408,39 +408,15 @@ 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()) {
416-
return false;
417-
}
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;
414+
return ReadOnlyStringMapUtil.equals(this, (ReadOnlyStringMap) obj);
427415
}
428416

429417
@Override
430418
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;
419+
return ReadOnlyStringMapUtil.hashCode(this);
444420
}
445421

446422
@Override

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.apache.logging.log4j.util.BiConsumer;
2121
import org.apache.logging.log4j.util.IndexedStringMap;
2222
import org.apache.logging.log4j.util.ReadOnlyStringMap;
23+
import org.apache.logging.log4j.util.ReadOnlyStringMapUtil;
2324
import org.apache.logging.log4j.util.TriConsumer;
2425

2526
/**
@@ -114,4 +115,20 @@ public int size() {
114115
public Map<String, String> toMap() {
115116
return null;
116117
}
118+
119+
@Override
120+
public boolean equals(final Object obj) {
121+
if (obj == this) {
122+
return true;
123+
}
124+
if (!(obj instanceof ReadOnlyStringMap)) {
125+
return false;
126+
}
127+
return ReadOnlyStringMapUtil.equals(this, (ReadOnlyStringMap) obj);
128+
}
129+
130+
@Override
131+
public int hashCode() {
132+
return ReadOnlyStringMapUtil.hashCode(this);
133+
}
117134
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Map;
2020
import org.apache.logging.log4j.util.BiConsumer;
2121
import org.apache.logging.log4j.util.ReadOnlyStringMap;
22+
import org.apache.logging.log4j.util.ReadOnlyStringMapUtil;
2223
import org.apache.logging.log4j.util.StringMap;
2324
import org.apache.logging.log4j.util.TriConsumer;
2425

@@ -81,4 +82,20 @@ public void putValue(final String key, final Object value) {}
8182

8283
@Override
8384
public void remove(final String key) {}
85+
86+
@Override
87+
public boolean equals(final Object obj) {
88+
if (obj == this) {
89+
return true;
90+
}
91+
if (!(obj instanceof ReadOnlyStringMap)) {
92+
return false;
93+
}
94+
return ReadOnlyStringMapUtil.equals(this, (ReadOnlyStringMap) obj);
95+
}
96+
97+
@Override
98+
public int hashCode() {
99+
return ReadOnlyStringMapUtil.hashCode(this);
100+
}
84101
}

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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.WeakHashMap;
2525
import org.apache.logging.log4j.util.BiConsumer;
2626
import org.apache.logging.log4j.util.ReadOnlyStringMap;
27+
import org.apache.logging.log4j.util.ReadOnlyStringMapUtil;
2728
import org.apache.logging.log4j.util.StringMap;
2829
import org.apache.logging.log4j.util.Strings;
2930
import org.apache.logging.log4j.util.TriConsumer;
@@ -219,15 +220,14 @@ public boolean equals(final Object object) {
219220
if (object == this) {
220221
return true;
221222
}
222-
if (!(object instanceof JdkMapAdapterStringMap)) {
223+
if (!(object instanceof ReadOnlyStringMap)) {
223224
return false;
224225
}
225-
final JdkMapAdapterStringMap other = (JdkMapAdapterStringMap) object;
226-
return map.equals(other.map) && immutable == other.immutable;
226+
return ReadOnlyStringMapUtil.equals(this, (ReadOnlyStringMap) object);
227227
}
228228

229229
@Override
230230
public int hashCode() {
231-
return map.hashCode() + (immutable ? 31 : 0);
231+
return ReadOnlyStringMapUtil.hashCode(this);
232232
}
233233
}

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

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
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;
30+
import org.apache.logging.log4j.util.ReadOnlyStringMapUtil;
3131
import org.apache.logging.log4j.util.StringMap;
3232
import org.apache.logging.log4j.util.TriConsumer;
3333

@@ -301,27 +301,7 @@ public boolean equals(final Object obj) {
301301
if (!(obj instanceof ReadOnlyStringMap)) {
302302
return false;
303303
}
304-
final ReadOnlyStringMap other = (ReadOnlyStringMap) obj;
305-
if (other.size() != size()) {
306-
return false;
307-
}
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;
304+
return ReadOnlyStringMapUtil.equals(this, (ReadOnlyStringMap) obj);
325305
}
326306

327307
@Override
@@ -699,25 +679,7 @@ protected void rehash(final int newN) {
699679
*/
700680
@Override
701681
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;
682+
return ReadOnlyStringMapUtil.hashCode(this);
721683
}
722684

723685
@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)