This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch feature/move-thread-context
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 366fa976ba0df7fe611c1bcbe5fd9763b22a61f4
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Mon Jun 24 15:49:14 2024 +0200

    Move `GarbageFreeSortedArrayThreadContextMap` to `log4j-core`
---
 .../log4j/test/spi/ThreadContextMapSuite.java      | 139 ++++++++++++++++++++
 .../log4j/spi/DefaultThreadContextMapTest.java     | 146 ++++++---------------
 .../logging/log4j/spi/ThreadContextMapTest.java    |  70 ----------
 .../org/apache/logging/log4j/spi/Provider.java     |   8 +-
 .../async/AbstractAsyncThreadContextTestBase.java  |   3 +-
 .../async/AsyncThreadContextGarbageFreeTest.java   |   4 +-
 ...GarbageFreeSortedArrayThreadContextMapTest.java | 116 ++++++++++++++++
 .../core/impl/ThreadContextDataInjectorTest.java   |   2 +-
 .../GarbageFreeSortedArrayThreadContextMap.java    |   7 +-
 .../logging/log4j/core/impl/Log4jProvider.java     |  66 ++--------
 .../GarbageFreeOpenHashMapThreadContextMap.java    |   1 +
 11 files changed, 314 insertions(+), 248 deletions(-)

diff --git 
a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.java
 
b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.java
new file mode 100644
index 0000000000..23bb42d483
--- /dev/null
+++ 
b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.java
@@ -0,0 +1,139 @@
+/*
+ * 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.test.spi;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.apache.logging.log4j.spi.ThreadContextMap;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+
+/**
+ * Provides test cases to apply to all implementations of {@link 
ThreadContextMap}.
+ * @since 2.24.0
+ */
+@Execution(ExecutionMode.CONCURRENT)
+public abstract class ThreadContextMapSuite {
+
+    private static final String KEY = "key";
+
+    /**
+     * Checks if the context map does not propagate to other threads by 
default.
+     */
+    protected static void threadLocalNotInheritableByDefault(final 
ThreadContextMap contextMap) {
+        contextMap.put(KEY, "threadLocalNotInheritableByDefault");
+        verifyThreadContextValueFromANewThread(contextMap, null);
+    }
+
+    /**
+     * Checks if the context map can be configured to propagate to other 
threads.
+     */
+    protected static void threadLocalInheritableIfConfigured(final 
ThreadContextMap contextMap) {
+        contextMap.put(KEY, "threadLocalInheritableIfConfigured");
+        verifyThreadContextValueFromANewThread(contextMap, 
"threadLocalInheritableIfConfigured");
+    }
+
+    /**
+     * Checks basic put/remove pattern.
+     */
+    protected static void singleValue(final ThreadContextMap contextMap) {
+        assertThat(contextMap.isEmpty()).as("Map is empty").isTrue();
+        contextMap.put(KEY, "testPut");
+        assertThat(contextMap.isEmpty()).as("Map is not empty").isFalse();
+        assertThat(contextMap.containsKey(KEY)).as("Map key exists").isTrue();
+        assertThat(contextMap.get(KEY)).as("Map contains expected 
value").isEqualTo("testPut");
+        contextMap.remove(KEY);
+        assertThat(contextMap.isEmpty()).as("Map is empty").isTrue();
+    }
+
+    /**
+     * Checks mutable copy
+     */
+    protected static void getCopyReturnsMutableCopy(final ThreadContextMap 
contextMap) {
+        contextMap.put(KEY, "testGetCopyReturnsMutableCopy");
+
+        final Map<String, String> copy = contextMap.getCopy();
+        assertThat(copy).as("Copy contains same 
value").containsExactly(entry(KEY, "testGetCopyReturnsMutableCopy"));
+
+        copy.put(KEY, "testGetCopyReturnsMutableCopy2");
+        assertThat(contextMap.get(KEY))
+                .as("Original map is not affected by changes in the copy")
+                .isEqualTo("testGetCopyReturnsMutableCopy");
+
+        contextMap.clear();
+        assertThat(contextMap.isEmpty()).as("Original map is empty").isTrue();
+        assertThat(copy)
+                .as("Copy is not affected by changes in the map.")
+                .containsExactly(entry(KEY, "testGetCopyReturnsMutableCopy2"));
+    }
+
+    /**
+     * The immutable copy must be {@code null} if the map is empty.
+     */
+    protected static void getImmutableMapReturnsNullIfEmpty(final 
ThreadContextMap contextMap) {
+        assertThat(contextMap.isEmpty()).as("Original map is empty").isTrue();
+        assertThat(contextMap.getImmutableMapOrNull())
+                .as("Immutable copy is null")
+                .isNull();
+    }
+
+    /**
+     * The result of {@link ThreadContextMap#getImmutableMapOrNull()} must be 
immutable.
+     */
+    protected static void getImmutableMapReturnsImmutableMapIfNonEmpty(final 
ThreadContextMap contextMap) {
+        contextMap.put(KEY, "getImmutableMapReturnsImmutableMapIfNonEmpty");
+
+        final Map<String, String> immutable = 
contextMap.getImmutableMapOrNull();
+        assertThat(immutable)
+                .as("Immutable copy contains same value")
+                .containsExactly(entry(KEY, 
"getImmutableMapReturnsImmutableMapIfNonEmpty"));
+
+        assertThrows(
+                UnsupportedOperationException.class, () -> immutable.put(KEY, 
"getImmutableMapReturnsNullIfEmpty2"));
+    }
+
+    /**
+     * The immutable copy is not affected by changes to the original map.
+     */
+    protected static void 
getImmutableMapCopyNotAffectedByContextMapChanges(final ThreadContextMap 
contextMap) {
+        contextMap.put(KEY, 
"getImmutableMapCopyNotAffectedByContextMapChanges");
+
+        final Map<String, String> immutable = 
contextMap.getImmutableMapOrNull();
+        contextMap.put(KEY, 
"getImmutableMapCopyNotAffectedByContextMapChanges2");
+        assertThat(immutable)
+                .as("Immutable copy contains the original value")
+                .containsExactly(entry(KEY, 
"getImmutableMapCopyNotAffectedByContextMapChanges"));
+    }
+
+    private static void verifyThreadContextValueFromANewThread(
+            final ThreadContextMap contextMap, final String expected) {
+        final ExecutorService executorService = 
Executors.newSingleThreadExecutor();
+        try {
+            assertThat(executorService.submit(() -> contextMap.get(KEY)))
+                    .succeedsWithin(Duration.ofSeconds(1))
+                    .isEqualTo(expected);
+        } finally {
+            executorService.shutdown();
+        }
+    }
+}
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
index 243ce0e9fc..4e3356aad3 100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
@@ -16,55 +16,43 @@
  */
 package org.apache.logging.log4j.spi;
 
-import static org.junit.Assert.assertNull;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Properties;
 import org.apache.logging.log4j.test.junit.UsingThreadContextMap;
+import org.apache.logging.log4j.test.spi.ThreadContextMapSuite;
+import org.apache.logging.log4j.util.PropertiesUtil;
 import org.junit.jupiter.api.Test;
 
 /**
  * Tests the {@code DefaultThreadContextMap} class.
  */
 @UsingThreadContextMap
-public class DefaultThreadContextMapTest {
+class DefaultThreadContextMapTest extends ThreadContextMapSuite {
 
-    @Test
-    public void testEqualsVsSameKind() {
-        final DefaultThreadContextMap map1 = createMap();
-        final DefaultThreadContextMap map2 = createMap();
-        assertEquals(map1, map1);
-        assertEquals(map2, map2);
-        assertEquals(map1, map2);
-        assertEquals(map2, map1);
+    private ThreadContextMap createThreadContextMap() {
+        return new DefaultThreadContextMap();
     }
 
-    @Test
-    public void testHashCodeVsSameKind() {
-        final DefaultThreadContextMap map1 = createMap();
-        final DefaultThreadContextMap map2 = createMap();
-        assertEquals(map1.hashCode(), map2.hashCode());
+    private ThreadContextMap createInheritableThreadContextMap() {
+        final Properties props = new Properties();
+        props.setProperty("log4j2.isThreadContextMapInheritable", "true");
+        final PropertiesUtil util = new PropertiesUtil(props);
+        return new DefaultThreadContextMap(util);
     }
 
     @Test
-    public void testPut() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isEmpty());
-        assertFalse(map.containsKey("key"));
-        map.put("key", "value");
-
-        assertFalse(map.isEmpty());
-        assertTrue(map.containsKey("key"));
-        assertEquals("value", map.get("key"));
+    void singleValue() {
+        singleValue(createThreadContextMap());
     }
 
     @Test
-    public void testPutAll() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
+    void testPutAll() {
+        final DefaultThreadContextMap map = new DefaultThreadContextMap();
         assertTrue(map.isEmpty());
         assertFalse(map.containsKey("key"));
         final int mapSize = 10;
@@ -80,24 +68,8 @@ public class DefaultThreadContextMapTest {
         }
     }
 
-    /**
-     * Test method for
-     * {@link 
org.apache.logging.log4j.spi.DefaultThreadContextMap#remove(java.lang.String)}
-     * .
-     */
-    @Test
-    public void testRemove() {
-        final DefaultThreadContextMap map = createMap();
-        assertEquals("value", map.get("key"));
-        assertEquals("value2", map.get("key2"));
-
-        map.remove("key");
-        assertFalse(map.containsKey("key"));
-        assertEquals("value2", map.get("key2"));
-    }
-
     @Test
-    public void testClear() {
+    void testClear() {
         final DefaultThreadContextMap map = createMap();
 
         map.clear();
@@ -106,11 +78,8 @@ public class DefaultThreadContextMapTest {
         assertFalse(map.containsKey("key2"));
     }
 
-    /**
-     * @return
-     */
     private DefaultThreadContextMap createMap() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
+        final DefaultThreadContextMap map = new DefaultThreadContextMap();
         assertTrue(map.isEmpty());
         map.put("key", "value");
         map.put("key2", "value2");
@@ -120,79 +89,28 @@ public class DefaultThreadContextMapTest {
     }
 
     @Test
-    public void testGetCopyReturnsMutableMap() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isEmpty());
-        final Map<String, String> copy = map.getCopy();
-        assertTrue(copy.isEmpty());
-
-        copy.put("key", "value"); // mutable
-        assertEquals("value", copy.get("key"));
-
-        // thread context map not affected
-        assertTrue(map.isEmpty());
+    void getCopyReturnsMutableCopy() {
+        getCopyReturnsMutableCopy(createThreadContextMap());
     }
 
     @Test
-    public void testGetCopyReturnsMutableCopy() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        map.put("key1", "value1");
-        assertFalse(map.isEmpty());
-        final Map<String, String> copy = map.getCopy();
-        assertEquals("value1", copy.get("key1")); // copy has values too
-
-        copy.put("key", "value"); // copy is mutable
-        assertEquals("value", copy.get("key"));
-
-        // thread context map not affected
-        assertFalse(map.containsKey("key"));
-
-        // clearing context map does not affect copy
-        map.clear();
-        assertTrue(map.isEmpty());
-
-        assertFalse(copy.isEmpty());
+    void getImmutableMapReturnsNullIfEmpty() {
+        getImmutableMapReturnsNullIfEmpty(createThreadContextMap());
     }
 
     @Test
-    public void testGetImmutableMapReturnsNullIfEmpty() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isEmpty());
-        assertNull(map.getImmutableMapOrNull());
+    void getImmutableMapReturnsImmutableMapIfNonEmpty() {
+        getImmutableMapReturnsImmutableMapIfNonEmpty(createThreadContextMap());
     }
 
     @Test
-    public void testGetImmutableMapReturnsImmutableMapIfNonEmpty() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        map.put("key1", "value1");
-        assertFalse(map.isEmpty());
-
-        final Map<String, String> immutable = map.getImmutableMapOrNull();
-        assertEquals("value1", immutable.get("key1")); // copy has values too
-
-        // immutable
-        assertThrows(UnsupportedOperationException.class, () -> 
immutable.put("key", "value"));
+    void getImmutableMapCopyNotAffectedByContextMapChanges() {
+        
getImmutableMapCopyNotAffectedByContextMapChanges(createThreadContextMap());
     }
 
     @Test
-    public void testGetImmutableMapCopyNotAffectdByContextMapChanges() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        map.put("key1", "value1");
-        assertFalse(map.isEmpty());
-
-        final Map<String, String> immutable = map.getImmutableMapOrNull();
-        assertEquals("value1", immutable.get("key1")); // copy has values too
-
-        // clearing context map does not affect copy
-        map.clear();
-        assertTrue(map.isEmpty());
-
-        assertFalse(immutable.isEmpty());
-    }
-
-    @Test
-    public void testToStringShowsMapContext() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
+    void testToStringShowsMapContext() {
+        final DefaultThreadContextMap map = new DefaultThreadContextMap();
         assertEquals("{}", map.toString());
 
         map.put("key1", "value1");
@@ -202,4 +120,14 @@ public class DefaultThreadContextMapTest {
         map.put("key2", "value2");
         assertEquals("{key2=value2}", map.toString());
     }
+
+    @Test
+    void threadLocalNotInheritableByDefault() {
+        threadLocalNotInheritableByDefault(createThreadContextMap());
+    }
+
+    @Test
+    void threadLocalInheritableIfConfigured() {
+        
threadLocalInheritableIfConfigured(createInheritableThreadContextMap());
+    }
 }
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java
deleted file mode 100644
index 2eb63dedf0..0000000000
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.assertj.core.api.Assertions.assertThat;
-
-import java.time.Duration;
-import java.util.Properties;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.stream.Stream;
-import org.apache.logging.log4j.util.PropertiesUtil;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.MethodSource;
-
-class ThreadContextMapTest {
-
-    private static final String KEY = "key";
-
-    static Stream<ThreadContextMap> defaultMaps() {
-        return Stream.of(new DefaultThreadContextMap(), new 
GarbageFreeSortedArrayThreadContextMap());
-    }
-
-    static Stream<ThreadContextMap> inheritableMaps() {
-        final Properties props = new Properties();
-        props.setProperty("log4j2.isThreadContextMapInheritable", "true");
-        final PropertiesUtil util = new PropertiesUtil(props);
-        return Stream.of(new DefaultThreadContextMap(util), new 
GarbageFreeSortedArrayThreadContextMap(util));
-    }
-
-    @ParameterizedTest
-    @MethodSource("defaultMaps")
-    void threadLocalNotInheritableByDefault(final ThreadContextMap contextMap) 
{
-        contextMap.put(KEY, "threadLocalNotInheritableByDefault");
-        verifyThreadContextValueFromANewThread(contextMap, null);
-    }
-
-    @ParameterizedTest
-    @MethodSource("inheritableMaps")
-    void threadLocalInheritableIfConfigured(final ThreadContextMap contextMap) 
{
-        contextMap.put(KEY, "threadLocalInheritableIfConfigured");
-        verifyThreadContextValueFromANewThread(contextMap, 
"threadLocalInheritableIfConfigured");
-    }
-
-    private static void verifyThreadContextValueFromANewThread(
-            final ThreadContextMap contextMap, final String expected) {
-        final ExecutorService executorService = 
Executors.newSingleThreadExecutor();
-        try {
-            assertThat(executorService.submit(() -> contextMap.get(KEY)))
-                    .succeedsWithin(Duration.ofSeconds(1))
-                    .isEqualTo(expected);
-        } finally {
-            executorService.shutdown();
-        }
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java
index bad793160d..f29dfdbd41 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java
@@ -291,12 +291,8 @@ public class Provider {
      */
     public ThreadContextMap getThreadContextMapInstance() {
         final PropertiesUtil props = PropertiesUtil.getProperties();
-        if (props.getBooleanProperty(DISABLE_CONTEXT_MAP) || 
props.getBooleanProperty(DISABLE_THREAD_CONTEXT)) {
-            return NoOpThreadContextMap.INSTANCE;
-        }
-        final String threadContextMap = getThreadContextMap();
-        return 
"org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap".equals(threadContextMap)
-                ? new GarbageFreeSortedArrayThreadContextMap()
+        return props.getBooleanProperty(DISABLE_CONTEXT_MAP) || 
props.getBooleanProperty(DISABLE_THREAD_CONTEXT)
+                ? NoOpThreadContextMap.INSTANCE
                 : new DefaultThreadContextMap();
     }
 
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java
index a1c3e7053c..572b2ca4d5 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java
@@ -92,7 +92,8 @@ public abstract class AbstractAsyncThreadContextTestBase {
 
     protected enum ContextImpl {
         WEBAPP("WebApp", 
"org.apache.logging.log4j.spi.DefaultThreadContextMap"),
-        GARBAGE_FREE("GarbageFree", 
"org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap");
+        GARBAGE_FREE(
+                "GarbageFree", 
"org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap");
 
         private final String threadContextMap;
         private final String implClass;
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java
index 4ede816304..e029580b01 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java
@@ -27,14 +27,14 @@ import org.junit.jupiter.params.provider.EnumSource;
 // ThreadContext initialization will result in static final fields being set 
in various components.
 // To use a different ThreadContextMap, the test needs to be run in a new JVM.
 @Tag(Tags.ASYNC_LOGGERS)
-public class AsyncThreadContextGarbageFreeTest extends 
AbstractAsyncThreadContextTestBase {
+class AsyncThreadContextGarbageFreeTest extends 
AbstractAsyncThreadContextTestBase {
 
     @TempLoggingDir
     private static Path loggingPath;
 
     @ParameterizedTest
     @EnumSource
-    public void testAsyncLogWritesToLog(Mode asyncMode) throws Exception {
+    void testAsyncLogWritesToLog(Mode asyncMode) throws Exception {
         testAsyncLogWritesToLog(ContextImpl.GARBAGE_FREE, asyncMode, 
loggingPath);
     }
 }
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMapTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMapTest.java
new file mode 100644
index 0000000000..192c72e85c
--- /dev/null
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMapTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.core.context.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import org.apache.logging.log4j.spi.ThreadContextMap;
+import org.apache.logging.log4j.test.spi.ThreadContextMapSuite;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.junit.jupiter.api.Test;
+
+public class GarbageFreeSortedArrayThreadContextMapTest extends 
ThreadContextMapSuite {
+
+    private GarbageFreeSortedArrayThreadContextMap createThreadContextMap() {
+        return new GarbageFreeSortedArrayThreadContextMap();
+    }
+
+    private ThreadContextMap createInheritableThreadContextMap() {
+        final Properties props = new Properties();
+        props.setProperty("log4j2.isThreadContextMapInheritable", "true");
+        final PropertiesUtil util = new PropertiesUtil(props);
+        return new GarbageFreeSortedArrayThreadContextMap(util);
+    }
+
+    @Test
+    void singleValue() {
+        singleValue(createThreadContextMap());
+    }
+
+    @Test
+    void testPutAll() {
+        final GarbageFreeSortedArrayThreadContextMap map = 
createThreadContextMap();
+        assertTrue(map.isEmpty());
+        assertFalse(map.containsKey("key"));
+        final int mapSize = 10;
+        final Map<String, String> newMap = new HashMap<>(mapSize);
+        for (int i = 1; i <= mapSize; i++) {
+            newMap.put("key" + i, "value" + i);
+        }
+        map.putAll(newMap);
+        assertFalse(map.isEmpty());
+        for (int i = 1; i <= mapSize; i++) {
+            assertTrue(map.containsKey("key" + i));
+            assertEquals("value" + i, map.get("key" + i));
+        }
+    }
+
+    @Test
+    void testClear() {
+        final GarbageFreeSortedArrayThreadContextMap map = createMap();
+
+        map.clear();
+        assertTrue(map.isEmpty());
+        assertFalse(map.containsKey("key"));
+        assertFalse(map.containsKey("key2"));
+    }
+
+    private GarbageFreeSortedArrayThreadContextMap createMap() {
+        final GarbageFreeSortedArrayThreadContextMap map = 
createThreadContextMap();
+        assertTrue(map.isEmpty());
+        map.put("key", "value");
+        map.put("key2", "value2");
+        assertEquals("value", map.get("key"));
+        assertEquals("value2", map.get("key2"));
+        return map;
+    }
+
+    @Test
+    void getCopyReturnsMutableCopy() {
+        getCopyReturnsMutableCopy(createThreadContextMap());
+    }
+
+    @Test
+    void getImmutableMapReturnsNullIfEmpty() {
+        getImmutableMapReturnsNullIfEmpty(createThreadContextMap());
+    }
+
+    @Test
+    void getImmutableMapReturnsImmutableMapIfNonEmpty() {
+        getImmutableMapReturnsImmutableMapIfNonEmpty(createThreadContextMap());
+    }
+
+    @Test
+    void getImmutableMapCopyNotAffectedByContextMapChanges() {
+        
getImmutableMapCopyNotAffectedByContextMapChanges(createThreadContextMap());
+    }
+
+    @Test
+    void threadLocalNotInheritableByDefault() {
+        threadLocalNotInheritableByDefault(createThreadContextMap());
+    }
+
+    @Test
+    void threadLocalInheritableIfConfigured() {
+        
threadLocalInheritableIfConfigured(createInheritableThreadContextMap());
+    }
+}
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java
index 47034eb7bf..24e3e1c93f 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java
@@ -50,7 +50,7 @@ public class ThreadContextDataInjectorTest {
     @Parameters(name = "{0}")
     public static Collection<String[]> threadContextMapClassNames() {
         return asList(new String[][] {
-            
{"org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap"},
+            
{"org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap"},
             {"org.apache.logging.log4j.spi.DefaultThreadContextMap"}
         });
     }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMap.java
similarity index 95%
rename from 
log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
rename to 
log4j-core/src/main/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMap.java
index 1622a0c455..b9ee2be759 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMap.java
@@ -14,12 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.spi;
+package org.apache.logging.log4j.core.context.internal;
 
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import org.apache.logging.log4j.spi.ObjectThreadContextMap;
+import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
+import org.apache.logging.log4j.spi.ThreadContextMap;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.SortedArrayStringMap;
@@ -34,7 +37,7 @@ import org.apache.logging.log4j.util.StringMap;
  * </p>
  * @since 2.7
  */
-class GarbageFreeSortedArrayThreadContextMap implements 
ReadOnlyThreadContextMap, ObjectThreadContextMap {
+public class GarbageFreeSortedArrayThreadContextMap implements 
ReadOnlyThreadContextMap, ObjectThreadContextMap {
 
     /**
      * Property name ({@value} ) for selecting {@code InheritableThreadLocal} 
(value "true") or plain
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
index 222eafc858..10e06cb387 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
@@ -20,6 +20,7 @@ import aQute.bnd.annotation.Resolution;
 import aQute.bnd.annotation.spi.ServiceConsumer;
 import aQute.bnd.annotation.spi.ServiceProvider;
 import org.apache.logging.log4j.Logger;
+import 
org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap;
 import org.apache.logging.log4j.spi.DefaultThreadContextMap;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
 import org.apache.logging.log4j.spi.NoOpThreadContextMap;
@@ -27,12 +28,10 @@ import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.spi.ScopedContextProvider;
 import org.apache.logging.log4j.spi.ThreadContextMap;
 import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Constants;
 import org.apache.logging.log4j.util.Lazy;
 import org.apache.logging.log4j.util.LoaderUtil;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.jspecify.annotations.NullMarked;
-import org.jspecify.annotations.Nullable;
 
 /**
  * Binding for the Log4j API.
@@ -63,15 +62,6 @@ public class Log4jProvider extends Provider {
      */
     private static final String WEB_APP_CONTEXT_MAP = "WebApp";
 
-    /**
-     * Constant used to select a copy-on-write implementation of {@link 
ThreadContextMap}.
-     * <p>
-     *     <strong>Warning:</strong> the value of this constant does not point 
to a concrete class name.
-     * </p>
-     * @see #getThreadContextMap
-     */
-    private static final String COPY_ON_WRITE_CONTEXT_MAP = "CopyOnWrite";
-
     /**
      * Constant used to select a garbage-free implementation of {@link 
ThreadContextMap}.
      * <p>
@@ -94,9 +84,7 @@ public class Log4jProvider extends Provider {
     // Name of the context map implementations
     private static final String WEB_APP_CLASS_NAME = 
"org.apache.logging.log4j.spi.DefaultThreadContextMap";
     private static final String GARBAGE_FREE_CLASS_NAME =
-            
"org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap";
-    private static final String COPY_ON_WRITE_CLASS_NAME =
-            
"org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap";
+            
"org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap";
 
     private static final Logger LOGGER = StatusLogger.getLogger();
 
@@ -126,13 +114,9 @@ public class Log4jProvider extends Provider {
         String threadContextMapClass = 
props.getStringProperty(THREAD_CONTEXT_MAP_PROPERTY);
         // Default based on properties
         if (threadContextMapClass == null) {
-            if (props.getBooleanProperty(GC_FREE_THREAD_CONTEXT_PROPERTY)) {
-                threadContextMapClass = GARBAGE_FREE_CONTEXT_MAP;
-            } else if (Constants.ENABLE_THREADLOCALS) {
-                threadContextMapClass = COPY_ON_WRITE_CONTEXT_MAP;
-            } else {
-                threadContextMapClass = WEB_APP_CONTEXT_MAP;
-            }
+            threadContextMapClass = 
props.getBooleanProperty(GC_FREE_THREAD_CONTEXT_PROPERTY)
+                    ? GARBAGE_FREE_CONTEXT_MAP
+                    : WEB_APP_CONTEXT_MAP;
         }
         /*
          * The constructors are called explicitly to improve GraalVM support.
@@ -146,11 +130,11 @@ public class Log4jProvider extends Provider {
             case WEB_APP_CONTEXT_MAP:
             case WEB_APP_CLASS_NAME:
                 return new DefaultThreadContextMap();
-            case GARBAGE_FREE_CONTEXT_MAP:
+                // Old FQCN of the garbage-free context map
             case 
"org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap":
-            case COPY_ON_WRITE_CONTEXT_MAP:
-            case 
"org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap":
-                return super.getThreadContextMapInstance();
+            case GARBAGE_FREE_CONTEXT_MAP:
+            case GARBAGE_FREE_CLASS_NAME:
+                return new GarbageFreeSortedArrayThreadContextMap();
             default:
                 try {
                     return 
LoaderUtil.newCheckedInstanceOf(threadContextMapClass, ThreadContextMap.class);
@@ -162,38 +146,6 @@ public class Log4jProvider extends Provider {
         return NoOpThreadContextMap.INSTANCE;
     }
 
-    @Override
-    public @Nullable String getThreadContextMap() {
-        // Properties
-        final PropertiesUtil props = PropertiesUtil.getProperties();
-        if (props.getBooleanProperty(DISABLE_CONTEXT_MAP) || 
props.getBooleanProperty(DISABLE_THREAD_CONTEXT)) {
-            return NoOpThreadContextMap.class.getName();
-        }
-        String threadContextMapClass = 
props.getStringProperty(THREAD_CONTEXT_MAP_PROPERTY);
-        // Default based on properties
-        if (threadContextMapClass == null) {
-            if (props.getBooleanProperty(GC_FREE_THREAD_CONTEXT_PROPERTY)) {
-                threadContextMapClass = GARBAGE_FREE_CONTEXT_MAP;
-            } else if (Constants.ENABLE_THREADLOCALS) {
-                threadContextMapClass = COPY_ON_WRITE_CONTEXT_MAP;
-            } else {
-                threadContextMapClass = WEB_APP_CONTEXT_MAP;
-            }
-        }
-        switch (threadContextMapClass) {
-            case NO_OP_CONTEXT_MAP:
-                return NoOpThreadContextMap.class.getName();
-            case WEB_APP_CONTEXT_MAP:
-                return DefaultThreadContextMap.class.getName();
-            case GARBAGE_FREE_CONTEXT_MAP:
-                return GARBAGE_FREE_CLASS_NAME;
-            case COPY_ON_WRITE_CONTEXT_MAP:
-                return COPY_ON_WRITE_CLASS_NAME;
-            default:
-                return threadContextMapClass;
-        }
-    }
-
     // Used in tests
     void resetThreadContextMap() {
         threadContextMapLazy.set(null);
diff --git 
a/log4j-perf-test/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java
 
b/log4j-perf-test/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java
index 45283a2fb0..a39ce88abc 100644
--- 
a/log4j-perf-test/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java
+++ 
b/log4j-perf-test/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java
@@ -16,6 +16,7 @@
  */
 package org.apache.logging.log4j.spi;
 
+import 
org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap;
 import org.apache.logging.log4j.perf.nogc.OpenHashStringMap;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;


Reply via email to