diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/UnmodifiableArrayBackedMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/UnmodifiableArrayBackedMapTest.java new file mode 100644 index 00000000000..81ed69a8e45 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/UnmodifiableArrayBackedMapTest.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 org.apache.logging.log4j.spi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.Test; + +public class UnmodifiableArrayBackedMapTest { + private static final int TEST_DATA_SIZE = 5; + + private HashMap getTestParameters() { + return getTestParameters(TEST_DATA_SIZE); + } + + private HashMap getTestParameters(int numParams) { + HashMap params = new LinkedHashMap<>(); + for (int i = 0; i < numParams; i++) { + params.put("" + i, "value" + i); + } + + return params; + } + + @Test + public void testReads() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + for (Map.Entry entry : params.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + assertTrue(testMap.containsKey(key)); + assertTrue(testMap.containsValue(value)); + assertEquals(testMap.get(key), params.get(key)); + } + assertFalse(testMap.containsKey("not_present")); + assertFalse(testMap.containsValue("not_present")); + assertEquals(null, testMap.get("not_present")); + } + + @Test + public void testCopyAndRemoveAll() { + HashMap initialMapContents = getTestParameters(15); + initialMapContents.put("extra_key", "extra_value"); + + HashSet keysToRemove = new LinkedHashSet<>(); + keysToRemove.add("3"); + keysToRemove.add("11"); + keysToRemove.add("definitely_not_found"); + + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(initialMapContents); + testMap = testMap.copyAndRemoveAll(keysToRemove); + assertEquals(14, testMap.size()); + + assertFalse(testMap.containsKey("3")); + assertFalse(testMap.containsValue("value3")); + assertFalse(testMap.containsKey("11")); + assertFalse(testMap.containsValue("value11")); + + assertTrue(testMap.containsKey("extra_key")); + assertTrue(testMap.containsValue("extra_value")); + assertTrue(testMap.containsKey("1")); + assertTrue(testMap.containsValue("value1")); + assertTrue(testMap.containsKey("0")); + assertTrue(testMap.containsValue("value0")); + assertTrue(testMap.containsKey("14")); + assertTrue(testMap.containsValue("value14")); + } + + @Test + public void testCopyAndPut() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP; + testMap = testMap.copyAndPut("1", "value1"); + assertTrue(testMap.containsKey("1")); + assertEquals(testMap.get("1"), "value1"); + + testMap = testMap.copyAndPut("1", "another value"); + assertTrue(testMap.containsKey("1")); + assertEquals(testMap.get("1"), "another value"); + + HashMap newValues = getTestParameters(); + testMap = testMap.copyAndPutAll(newValues); + assertEquals(testMap.get("1"), "value1"); + assertEquals(testMap.get("4"), "value4"); + } + + @Test + public void testInstanceCopy() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + testMap = testMap.copyAndPut("1", "value1"); + + UnmodifiableArrayBackedMap testMap2 = new UnmodifiableArrayBackedMap(testMap); + assertEquals(testMap, testMap2); + } + + @Test + public void testEntrySetIteratorAndSize() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + Set> entrySet = testMap.entrySet(); + int numEntriesFound = 0; + for (Map.Entry entry : entrySet) { + numEntriesFound++; + } + + assertEquals(testMap.size(), numEntriesFound); + assertEquals(testMap.size(), entrySet.size()); + } + + @Test + public void testEntrySetMutatorsBlocked() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + Set> entrySet = testMap.entrySet(); + for (Map.Entry entry : entrySet) { + try { + entry.setValue("test"); + fail("Entry.setValue() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + } + for (Map.Entry entry : entrySet) { + try { + entrySet.add(null); + fail("EntrySet.add() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + } + for (Map.Entry entry : entrySet) { + try { + entrySet.addAll(new HashSet<>()); + fail("EntrySet.addAll() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + } + } + + @Test + public void testNullValue() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP; + testMap = testMap.copyAndPut("key", null); + assertTrue(testMap.containsKey("key")); + assertTrue(testMap.containsValue(null)); + assertTrue(testMap.size() == 1); + assertEquals(testMap.get("key"), null); + } + + @Test + public void testMutatorsBlocked() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + try { + testMap.put("a", "a"); + fail("put() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + + try { + testMap.putAll(new HashMap<>()); + fail("putAll() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + + try { + testMap.remove("1"); + fail("remove() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + + try { + testMap.clear(); + fail("clear() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + } + + @Test + public void testCopyAndRemove() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + testMap = testMap.copyAndRemove("2"); + testMap = testMap.copyAndRemove("not_present"); + assertEquals(4, testMap.size()); + assertFalse(testMap.containsKey("2")); + assertTrue(testMap.containsKey("1")); + assertFalse(testMap.containsValue("value2")); + } + + /** + * Tests various situations with .equals(). Test tries comparisons in both + * directions, to make sure that HashMap.equals(UnmodifiableArrayBackedMap) work + * as well as UnmodifiableArrayBackedMap.equals(HashMap). + */ + @Test + public void testEqualsHashCodeWithIdenticalContent() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + assertEquals(params, testMap); + assertEquals(testMap, params); + assertEquals(params.hashCode(), testMap.hashCode()); + } + + @Test + public void testEqualsWhenOneValueDiffers() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + assertNotEquals(params, testMap.copyAndPut("1", "different value")); + assertNotEquals(testMap.copyAndPut("1", "different value"), params); + } + + @Test + public void testEqualsHashCodeWithOneKeyRemoved() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + + params.remove("1"); + assertNotEquals(params, testMap); + assertNotEquals(testMap, params); + + testMap = testMap.copyAndRemove("1").copyAndRemove("2"); + assertNotEquals(params, testMap); + assertNotEquals(testMap, params); + } + + @Test + public void testEqualsHashCodeWithOneEmptyMap() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + // verify empty maps are not equal to non-empty maps + assertNotEquals(params, UnmodifiableArrayBackedMap.EMPTY_MAP); + assertNotEquals(new HashMap<>(), testMap); + assertNotEquals(UnmodifiableArrayBackedMap.EMPTY_MAP, params); + assertNotEquals(testMap, new HashMap<>()); + } + + @Test + public void testImmutability() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap originalMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + UnmodifiableArrayBackedMap modifiedMap = originalMap.copyAndPutAll(getTestParameters()); + assertEquals(originalMap, params); + + modifiedMap = modifiedMap.copyAndRemoveAll(modifiedMap.keySet()); + assertTrue(modifiedMap.isEmpty()); + + assertEquals(originalMap, params); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java index 3c45eee98f5..b2e8ff6bd46 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java @@ -16,7 +16,6 @@ */ package org.apache.logging.log4j.spi; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -57,7 +56,7 @@ static ThreadLocal> createThreadLocalMap(final boolean isMap @Override protected Map childValue(final Map parentValue) { return parentValue != null && isMapEnabled // - ? Collections.unmodifiableMap(new HashMap<>(parentValue)) // + ? UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(parentValue) : null; } }; @@ -84,22 +83,24 @@ public void put(final String key, final String value) { if (!useMap) { return; } - Map map = localMap.get(); - map = map == null ? new HashMap<>(1) : new HashMap<>(map); - map.put(key, value); - localMap.set(Collections.unmodifiableMap(map)); + UnmodifiableArrayBackedMap map = (UnmodifiableArrayBackedMap) localMap.get(); + if (map == null) { + map = UnmodifiableArrayBackedMap.EMPTY_MAP; + } + map = map.copyAndPut(key, value); + localMap.set(map); } public void putAll(final Map m) { if (!useMap) { return; } - Map map = localMap.get(); - map = map == null ? new HashMap<>(m.size()) : new HashMap<>(map); - for (final Map.Entry e : m.entrySet()) { - map.put(e.getKey(), e.getValue()); + UnmodifiableArrayBackedMap map = (UnmodifiableArrayBackedMap) localMap.get(); + if (map == null) { + map = UnmodifiableArrayBackedMap.EMPTY_MAP; } - localMap.set(Collections.unmodifiableMap(map)); + map = map.copyAndPutAll(m); + localMap.set(map); } @Override @@ -110,22 +111,18 @@ public String get(final String key) { @Override public void remove(final String key) { - final Map map = localMap.get(); + UnmodifiableArrayBackedMap map = (UnmodifiableArrayBackedMap) localMap.get(); if (map != null) { - final Map copy = new HashMap<>(map); - copy.remove(key); - localMap.set(Collections.unmodifiableMap(copy)); + map = map.copyAndRemove(key); + localMap.set(map); } } public void removeAll(final Iterable keys) { - final Map map = localMap.get(); + UnmodifiableArrayBackedMap map = (UnmodifiableArrayBackedMap) localMap.get(); if (map != null) { - final Map copy = new HashMap<>(map); - for (final String key : keys) { - copy.remove(key); - } - localMap.set(Collections.unmodifiableMap(copy)); + map = map.copyAndRemoveAll(keys); + localMap.set(map); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/UnmodifiableArrayBackedMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/UnmodifiableArrayBackedMap.java new file mode 100644 index 00000000000..b83005e28f3 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/UnmodifiableArrayBackedMap.java @@ -0,0 +1,417 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 org.apache.logging.log4j.spi; + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * This class represents an immutable map, which stores its key-value pairs + * inside a single array of type String[]. Keys are therefore accessed by + * accessing the (2 * index) element in the list, and values by (2 * index + 1). + * + * Performance: + *
    + *
  • Implements very low-cost copies: shallow-copy the array.
  • + *
  • Doesn't matter for mutable operations, since we don't allow them.
  • + *
  • Iterates very quickly, since it iterates directly across the array. This + * contrasts with HashMap's requirement to scan each bucket in the table and + * chase each pointer.
  • + *
  • Is linear on gets, puts, and removes, since the table must be scanned to + * find a matching key.
  • + *
+ * + * Allocation: + *
    + *
  • Zero on reads.
  • + *
  • Copy-and-modify operations allocate exactly two objects: the new array + * and the new Map instance. This is substantially better than HashMap, which + * requires a new Node for each entry.
  • + *
+ * + */ +class UnmodifiableArrayBackedMap extends AbstractMap implements Serializable { + /** + * Implementation of Map.Entry. The implementation is simple since each instance + * contains an index in the array, then getKey() and getValue() retrieve from + * the array. Blocks modifications. + */ + private class UnmodifiableEntry implements Map.Entry { + private final int index; + + public UnmodifiableEntry(int index) { + this.index = index; + } + + @Override + public String getKey() { + return keysAndValues[index * 2]; + } + + @Override + public String getValue() { + return keysAndValues[index * 2 + 1]; + } + + /** + * Per spec, the hashcode is a function of the key and value. Calculation + * exactly matches HashMap. + */ + public int hashCode() { + String key = keysAndValues[index * 2]; + String value = keysAndValues[index * 2 + 1]; + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + + @Override + public String setValue(String value) { + throw new UnsupportedOperationException("Cannot update Entry instances in UnmodifiableArrayBackedMap"); + } + } + + /** + * Simple Entry iterator, tracking solely the index in the array. Blocks + * modifications. + */ + private class UnmodifiableEntryIterator implements Iterator> { + private int index; + + @Override + public boolean hasNext() { + return index < numEntries; + } + + @Override + public Entry next() { + return new UnmodifiableEntry(index++); + } + } + + /** + * Simple Entry set, providing a reference to UnmodifiableEntryIterator and + * blocking modifications. + */ + private class UnmodifiableEntrySet extends AbstractSet> { + + @Override + public boolean add(Entry e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection> c) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator> iterator() { + return new UnmodifiableEntryIterator(); + } + + @Override + public int size() { + return numEntries; + } + } + + private static final long serialVersionUID = 6849423432534211514L; + + public static final UnmodifiableArrayBackedMap EMPTY_MAP = new UnmodifiableArrayBackedMap(); + + private final String[] keysAndValues; + + private int numEntries; + + private UnmodifiableArrayBackedMap() { + this(0); + } + + private UnmodifiableArrayBackedMap(int capacity) { + this.keysAndValues = new String[capacity * 2]; + } + + UnmodifiableArrayBackedMap(UnmodifiableArrayBackedMap other) { + this.keysAndValues = other.keysAndValues; + this.numEntries = other.numEntries; + } + + private void add(String key, String value) { + keysAndValues[numEntries * 2] = key; + keysAndValues[numEntries * 2 + 1] = value; + numEntries++; + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Instance cannot be cleared, reuse EMPTY_MAP instead."); + } + + /** + * Scans the array to find a matching key. Linear performance. + */ + @Override + public boolean containsKey(Object key) { + int hashCode = key.hashCode(); + for (int i = 0; i < numEntries; i++) { + if (keysAndValues[i * 2].hashCode() == hashCode && keysAndValues[i * 2].equals(key)) { + return true; + } + } + + return false; + } + + /** + * Scans the array to find a matching value, with linear time. Allows null + * parameter. + */ + @Override + public boolean containsValue(Object value) { + for (int i = 0; i < numEntries; i++) { + String valueInMap = keysAndValues[i * 2 + 1]; + if (value == null) { + if (valueInMap == null) { + return true; + } + } else if (value.equals(valueInMap)) { + return true; + } + } + return false; + } + + /** + * Creates a new instance that contains the same entries as this map, plus + * either the new entry or updated value passed in the parameters. + * + * @param key + * @param value + * @return + */ + UnmodifiableArrayBackedMap copyAndPut(String key, String value) { + UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries + 1); + System.arraycopy(this.keysAndValues, 0, newMap.keysAndValues, 0, numEntries * 2); + newMap.numEntries = numEntries; + newMap.addOrOverwriteKey(key, value); + + return newMap; + } + + /** + * Creates a new instance that contains the same entries as this map, plus the + * new entries or updated values passed in the parameters. + * + * @param key + * @param value + * @return + */ + UnmodifiableArrayBackedMap copyAndPutAll(Map entriesToAdd) { + // create a new array that can hold the maximum output size + UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries + entriesToAdd.size()); + + // copy the contents of the current map (if any) + System.arraycopy(keysAndValues, 0, newMap.keysAndValues, 0, numEntries * 2); + newMap.numEntries = numEntries; + + for (Map.Entry entry : entriesToAdd.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (!this.isEmpty()) { + // The unique elements passed in may overlap the unique elements here - must + // check + newMap.addOrOverwriteKey(key, value); + } else { + // There is no chance of overlapping keys, we can simply add + newMap.add(key, value); + } + } + + return newMap; + } + + /** + * Creates a new instance that contains the same entries as this map, minus the + * entry with the specified key (if such an entry exists). + * + * @param key + * @param value + * @return + */ + UnmodifiableArrayBackedMap copyAndRemove(String key) { + UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries); + int indexToRemove = -1; + for (int oldIndex = 0; oldIndex < numEntries; oldIndex++) { + if (keysAndValues[oldIndex * 2].hashCode() == key.hashCode() && keysAndValues[oldIndex * 2].equals(key)) { + indexToRemove = oldIndex; + break; + } + } + + if (indexToRemove == -1) { + // key not found, no change necessary + return this; + } + if (indexToRemove > 0) { + // copy entries before the removed one + System.arraycopy(keysAndValues, 0, newMap.keysAndValues, 0, indexToRemove * 2); + } + if (indexToRemove < (numEntries + 1)) { + // copy entries after the removed one + int nextIndexToCopy = indexToRemove + 1; + int numRemainingEntries = numEntries - nextIndexToCopy; + System.arraycopy( + keysAndValues, + nextIndexToCopy * 2, + newMap.keysAndValues, + indexToRemove * 2, + numRemainingEntries * 2); + } + + newMap.numEntries = numEntries - 1; + + return newMap; + } + + /** + * Creates a new instance that contains the same entries as this map, minus all + * of the keys passed in the arguments. + * + * @param key + * @param value + * @return + */ + UnmodifiableArrayBackedMap copyAndRemoveAll(Iterable keysToRemoveIterable) { + // we invest time building a HashSet of the keys to remove, allowing for fast + // lookups below + Set keysToRemoveSet = new HashSet<>(); + for (String key : keysToRemoveIterable) { + keysToRemoveSet.add(key); + } + + int firstIndexToKeep = -1; + int lastIndexToKeep = -1; + int destinationIndex = 0; + int numEntriesKept = 0; + // build the new map + UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries); + for (int indexInCurrentMap = 0; indexInCurrentMap < numEntries; indexInCurrentMap++) { + // for each key in this map, check whether it's in the set we built above + String key = keysAndValues[indexInCurrentMap * 2]; + if (!keysToRemoveSet.contains(key)) { + if (firstIndexToKeep == -1) { + firstIndexToKeep = indexInCurrentMap; + } + lastIndexToKeep = indexInCurrentMap; + } else if (lastIndexToKeep > 0) { + // we hit a remove, copy any keys that are known ready + int numEntriesToCopy = lastIndexToKeep - firstIndexToKeep + 1; + System.arraycopy( + keysAndValues, + firstIndexToKeep * 2, + newMap.keysAndValues, + destinationIndex * 2, + numEntriesToCopy * 2); + firstIndexToKeep = -1; + lastIndexToKeep = -1; + destinationIndex += numEntriesToCopy; + numEntriesKept += numEntriesToCopy; + } + } + + if (lastIndexToKeep > -1) { + int numEntriesToCopy = lastIndexToKeep - firstIndexToKeep + 1; + System.arraycopy( + keysAndValues, + firstIndexToKeep * 2, + newMap.keysAndValues, + destinationIndex * 2, + numEntriesToCopy * 2); + numEntriesKept += numEntriesToCopy; + } + + newMap.numEntries = numEntriesKept; + + return newMap; + } + + @Override + public Set> entrySet() { + return new UnmodifiableEntrySet(); + } + + /** + * Scans the array to find a matching key. Linear-time. + */ + @Override + public String get(Object key) { + int hashCode = key.hashCode(); + for (int i = 0; i < numEntries; i++) { + if (keysAndValues[i * 2].hashCode() == hashCode && keysAndValues[i * 2].equals(key)) { + return keysAndValues[i * 2 + 1]; + } + } + return null; + } + + /** + * Find an existing entry (if any) and overwrites the value, if found + * + * @param key + * @param value + * @return + */ + private void addOrOverwriteKey(String key, String value) { + int keyHashCode = key.hashCode(); + for (int i = 0; i < numEntries; i++) { + if (keysAndValues[i * 2].hashCode() == keyHashCode && keysAndValues[i * 2].equals(key)) { + // found a match, overwrite then return + keysAndValues[i * 2 + 1] = value; + return; + } + } + + // no match found, add to the end + add(key, value); + } + + @Override + public String put(String key, String value) { + throw new UnsupportedOperationException("put() is not supported, use copyAndPut instead"); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("putAll() is not supported, use copyAndPutAll instead"); + } + + @Override + public String remove(Object key) { + throw new UnsupportedOperationException("remove() is not supported, use copyAndRemove instead"); + } + + @Override + public int size() { + return numEntries; + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java index 22200e3206a..8fff7b80978 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java @@ -19,7 +19,7 @@ * API classes. */ @Export -@Version("2.20.1") +@Version("2.24.0") package org.apache.logging.log4j.spi; import org.osgi.annotation.bundle.Export; diff --git a/src/changelog/.2.x.x/improve_DefaultThreadContextMap.xml b/src/changelog/.2.x.x/improve_DefaultThreadContextMap.xml new file mode 100644 index 00000000000..33d1d9c31f0 --- /dev/null +++ b/src/changelog/.2.x.x/improve_DefaultThreadContextMap.xml @@ -0,0 +1,8 @@ + + + + Improve performance of `DefaultThreadContextMap` +