You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rp...@apache.org on 2016/09/24 13:38:02 UTC

logging-log4j2 git commit: LOG4J2-1611 productionize JdkMapAdapterStringMap (null checks, toString/equals/hashCode); allow mutation; add unit tests

Repository: logging-log4j2
Updated Branches:
  refs/heads/master e1902e97f -> d16716a58


LOG4J2-1611 productionize JdkMapAdapterStringMap (null checks, toString/equals/hashCode); allow mutation; add unit tests


Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/d16716a5
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/d16716a5
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/d16716a5

Branch: refs/heads/master
Commit: d16716a58e6bba87f7874f23dfc43db41edbf1b6
Parents: e1902e9
Author: rpopma <rp...@apache.org>
Authored: Sat Sep 24 22:37:53 2016 +0900
Committer: rpopma <rp...@apache.org>
Committed: Sat Sep 24 22:37:53 2016 +0900

----------------------------------------------------------------------
 .../log4j/core/impl/JdkMapAdapterStringMap.java |  69 +-
 .../core/impl/JdkMapAdapterStringMapTest.java   | 882 +++++++++++++++++++
 2 files changed, 942 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d16716a5/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java
index b9387b9a..504b194 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java
@@ -16,7 +16,9 @@
  */
 package org.apache.logging.log4j.core.impl;
 
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 import org.apache.logging.log4j.util.BiConsumer;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
@@ -28,10 +30,17 @@ import org.apache.logging.log4j.util.TriConsumer;
  */
 class JdkMapAdapterStringMap implements StringMap {
     private static final long serialVersionUID = -7348247784983193612L;
-    private Map<String, String> map;
+    private static final String FROZEN = "Frozen collection cannot be modified";
+
+    private final Map<String, String> map;
+    private boolean immutable = false;
+
+    public JdkMapAdapterStringMap() {
+        this(new HashMap<String, String>());
+    }
 
     public JdkMapAdapterStringMap(final Map<String, String> map) {
-        this.map = map;
+        this.map = Objects.requireNonNull(map, "map");
     }
 
     @Override
@@ -39,6 +48,12 @@ class JdkMapAdapterStringMap implements StringMap {
         return map;
     }
 
+    private void assertNotFrozen() {
+        if (immutable) {
+            throw new UnsupportedOperationException(FROZEN);
+        }
+    }
+
     @Override
     public boolean containsKey(final String key) {
         return map.containsKey(key);
@@ -78,34 +93,70 @@ class JdkMapAdapterStringMap implements StringMap {
 
     @Override
     public void clear() {
-        fail();
+        if (map.isEmpty()) {
+            return;
+        }
+        assertNotFrozen();
+        map.clear();
     }
 
     @Override
     public void freeze() {
+        immutable = true;
     }
 
     @Override
     public boolean isFrozen() {
-        return true;
+        return immutable;
     }
 
     @Override
     public void putAll(final ReadOnlyStringMap source) {
-        fail();
+        assertNotFrozen();
+        source.forEach(PUT_ALL, map);
     }
 
+    private static TriConsumer<String, String, Map<String, String>> PUT_ALL = new TriConsumer<String, String, Map<String, String>>() {
+        @Override
+        public void accept(final String key, final String value, final Map<String, String> stringStringMap) {
+            stringStringMap.put(key, value);
+        }
+    };
+
     @Override
     public void putValue(final String key, final Object value) {
-        fail();
+        assertNotFrozen();
+        map.put(key, value == null ? null : String.valueOf(value));
     }
 
     @Override
     public void remove(final String key) {
-        fail();
+        if (!map.containsKey(key)) {
+            return;
+        }
+        assertNotFrozen();
+        map.remove(key);
     }
 
-    private void fail() {
-        throw new UnsupportedOperationException("This is a read-only data structure");
+    @Override
+    public String toString() {
+        return map.toString();
+    }
+
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (!(object instanceof JdkMapAdapterStringMap)) {
+            return false;
+        }
+        JdkMapAdapterStringMap other = (JdkMapAdapterStringMap) object;
+        return map.equals(other.map) && immutable == other.immutable;
+    }
+
+    @Override
+    public int hashCode() {
+        return map.hashCode() + (immutable ? 31 : 0);
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d16716a5/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java
new file mode 100644
index 0000000..482d399
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java
@@ -0,0 +1,882 @@
+/*
+ * 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.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ConcurrentModificationException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.TriConsumer;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests the JdkMapAdapterStringMap class.
+ */
+public class JdkMapAdapterStringMapTest {
+
+    @Test(expected = NullPointerException.class)
+    public void testConstructorDisallowsNull() throws Exception {
+        new JdkMapAdapterStringMap(null);
+    }
+
+    @Test
+    public void testToString() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        assertEquals("{3=3value, B=Bvalue, a=avalue}", original.toString());
+    }
+
+    @Test
+    public void testSerialization() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        final byte[] binary = serialize(original);
+        final JdkMapAdapterStringMap copy = deserialize(binary);
+        assertEquals(original, copy);
+    }
+
+    private byte[] serialize(final JdkMapAdapterStringMap data) throws IOException {
+        final ByteArrayOutputStream arr = new ByteArrayOutputStream();
+        final ObjectOutputStream out = new ObjectOutputStream(arr);
+        out.writeObject(data);
+        return arr.toByteArray();
+    }
+
+    private JdkMapAdapterStringMap deserialize(final byte[] binary) throws IOException, ClassNotFoundException {
+        final ByteArrayInputStream inArr = new ByteArrayInputStream(binary);
+        final ObjectInputStream in = new ObjectInputStream(inArr);
+        final JdkMapAdapterStringMap result = (JdkMapAdapterStringMap) in.readObject();
+        return result;
+    }
+
+    @Test
+    public void testPutAll() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putAll(original);
+        assertEquals(original, other);
+
+        other.putValue("3", "otherValue");
+        assertNotEquals(original, other);
+
+        other.putValue("3", null);
+        assertNotEquals(original, other);
+
+        other.putValue("3", "3value");
+        assertEquals(original, other);
+    }
+
+    @Test
+    public void testPutAll_overwritesSameKeys2() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aORIG");
+        original.putValue("b", "bORIG");
+        original.putValue("c", "cORIG");
+        original.putValue("d", "dORIG");
+        original.putValue("e", "eORIG");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue("1", "11");
+        other.putValue("2", "22");
+        other.putValue("a", "aa");
+        other.putValue("c", "cc");
+        original.putAll(other);
+
+        assertEquals("size after put other", 7, original.size());
+        assertEquals("aa", original.getValue("a"));
+        assertEquals("bORIG", original.getValue("b"));
+        assertEquals("cc", original.getValue("c"));
+        assertEquals("dORIG", original.getValue("d"));
+        assertEquals("eORIG", original.getValue("e"));
+        assertEquals("11", original.getValue("1"));
+        assertEquals("22", original.getValue("2"));
+    }
+
+    @Test
+    public void testPutAll_nullKeyInLargeOriginal() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue(null, "nullORIG");
+        original.putValue("a", "aORIG");
+        original.putValue("b", "bORIG");
+        original.putValue("c", "cORIG");
+        original.putValue("d", "dORIG");
+        original.putValue("e", "eORIG");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue("1", "11");
+        other.putValue("a", "aa");
+        original.putAll(other);
+
+        assertEquals("size after put other", 7, original.size());
+        assertEquals("aa", original.getValue("a"));
+        assertEquals("bORIG", original.getValue("b"));
+        assertEquals("cORIG", original.getValue("c"));
+        assertEquals("dORIG", original.getValue("d"));
+        assertEquals("eORIG", original.getValue("e"));
+        assertEquals("11", original.getValue("1"));
+        assertEquals("nullORIG", original.getValue(null));
+    }
+
+    @Test
+    public void testPutAll_nullKeyInSmallOriginal() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue(null, "nullORIG");
+        original.putValue("a", "aORIG");
+        original.putValue("b", "bORIG");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue("1", "11");
+        other.putValue("2", "22");
+        other.putValue("3", "33");
+        other.putValue("a", "aa");
+        original.putAll(other);
+
+        assertEquals("size after put other", 6, original.size());
+        assertEquals("aa", original.getValue("a"));
+        assertEquals("bORIG", original.getValue("b"));
+        assertEquals("11", original.getValue("1"));
+        assertEquals("22", original.getValue("2"));
+        assertEquals("33", original.getValue("3"));
+        assertEquals("nullORIG", original.getValue(null));
+    }
+
+    @Test
+    public void testPutAll_nullKeyInSmallAdditional() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aORIG");
+        original.putValue("b", "bORIG");
+        original.putValue("c", "cORIG");
+        original.putValue("d", "dORIG");
+        original.putValue("e", "eORIG");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue(null, "nullNEW");
+        other.putValue("1", "11");
+        other.putValue("a", "aa");
+        original.putAll(other);
+
+        assertEquals("size after put other", 7, original.size());
+        assertEquals("aa", original.getValue("a"));
+        assertEquals("bORIG", original.getValue("b"));
+        assertEquals("cORIG", original.getValue("c"));
+        assertEquals("dORIG", original.getValue("d"));
+        assertEquals("eORIG", original.getValue("e"));
+        assertEquals("11", original.getValue("1"));
+        assertEquals("nullNEW", original.getValue(null));
+    }
+
+    @Test
+    public void testPutAll_nullKeyInLargeAdditional() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aORIG");
+        original.putValue("b", "bORIG");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue(null, "nullNEW");
+        other.putValue("1", "11");
+        other.putValue("2", "22");
+        other.putValue("3", "33");
+        other.putValue("a", "aa");
+        original.putAll(other);
+
+        assertEquals("size after put other", 6, original.size());
+        assertEquals("aa", original.getValue("a"));
+        assertEquals("bORIG", original.getValue("b"));
+        assertEquals("11", original.getValue("1"));
+        assertEquals("22", original.getValue("2"));
+        assertEquals("33", original.getValue("3"));
+        assertEquals("nullNEW", original.getValue(null));
+    }
+
+    @Test
+    public void testPutAll_nullKeyInBoth_LargeOriginal() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue(null, "nullORIG");
+        original.putValue("a", "aORIG");
+        original.putValue("b", "bORIG");
+        original.putValue("c", "cORIG");
+        original.putValue("d", "dORIG");
+        original.putValue("e", "eORIG");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue(null, "nullNEW");
+        other.putValue("1", "11");
+        other.putValue("a", "aa");
+        original.putAll(other);
+
+        assertEquals("size after put other", 7, original.size());
+        assertEquals("aa", original.getValue("a"));
+        assertEquals("bORIG", original.getValue("b"));
+        assertEquals("cORIG", original.getValue("c"));
+        assertEquals("dORIG", original.getValue("d"));
+        assertEquals("eORIG", original.getValue("e"));
+        assertEquals("11", original.getValue("1"));
+        assertEquals("nullNEW", original.getValue(null));
+    }
+
+    @Test
+    public void testPutAll_nullKeyInBoth_SmallOriginal() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue(null, "nullORIG");
+        original.putValue("a", "aORIG");
+        original.putValue("b", "bORIG");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue(null, "nullNEW");
+        other.putValue("1", "11");
+        other.putValue("2", "22");
+        other.putValue("3", "33");
+        other.putValue("a", "aa");
+        original.putAll(other);
+
+        assertEquals("size after put other", 6, original.size());
+        assertEquals("aa", original.getValue("a"));
+        assertEquals("bORIG", original.getValue("b"));
+        assertEquals("11", original.getValue("1"));
+        assertEquals("22", original.getValue("2"));
+        assertEquals("33", original.getValue("3"));
+        assertEquals("nullNEW", original.getValue(null));
+    }
+
+    @Test
+    public void testPutAll_overwritesSameKeys1() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aORIG");
+        original.putValue("b", "bORIG");
+        original.putValue("c", "cORIG");
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue("1", "11");
+        other.putValue("2", "22");
+        other.putValue("a", "aa");
+        other.putValue("c", "cc");
+        original.putAll(other);
+
+        assertEquals("size after put other", 5, original.size());
+        assertEquals("aa", original.getValue("a"));
+        assertEquals("bORIG", original.getValue("b"));
+        assertEquals("cc", original.getValue("c"));
+        assertEquals("11", original.getValue("1"));
+        assertEquals("22", original.getValue("2"));
+    }
+
+    @Test
+    public void testEquals() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        assertEquals(original, original); // equal to itself
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue("a", "avalue");
+        assertNotEquals(original, other);
+
+        other.putValue("B", "Bvalue");
+        assertNotEquals(original, other);
+
+        other.putValue("3", "3value");
+        assertEquals(original, other);
+
+        other.putValue("3", "otherValue");
+        assertNotEquals(original, other);
+
+        other.putValue("3", null);
+        assertNotEquals(original, other);
+
+        other.putValue("3", "3value");
+        assertEquals(original, other);
+    }
+
+    @Test
+    public void testToMap() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        final Map<String, Object> expected = new HashMap<>();
+        expected.put("a", "avalue");
+        expected.put("B", "Bvalue");
+        expected.put("3", "3value");
+
+        assertEquals(expected, original.toMap());
+
+        try {
+            original.toMap().put("abc", "xyz");
+        } catch (final UnsupportedOperationException ex) {
+            fail("Expected map to be mutable, but " + ex);
+        }
+    }
+
+    @Test
+    public void testPutAll_KeepsExistingValues() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "bbb");
+        original.putValue("c", "ccc");
+        assertEquals("size", 3, original.size());
+
+        // add empty context data
+        original.putAll(new JdkMapAdapterStringMap());
+        assertEquals("size after put empty", 3, original.size());
+        assertEquals("aaa", original.getValue("a"));
+        assertEquals("bbb", original.getValue("b"));
+        assertEquals("ccc", original.getValue("c"));
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue("1", "111");
+        other.putValue("2", "222");
+        other.putValue("3", "333");
+        original.putAll(other);
+
+        assertEquals("size after put other", 6, original.size());
+        assertEquals("aaa", original.getValue("a"));
+        assertEquals("bbb", original.getValue("b"));
+        assertEquals("ccc", original.getValue("c"));
+        assertEquals("111", original.getValue("1"));
+        assertEquals("222", original.getValue("2"));
+        assertEquals("333", original.getValue("3"));
+    }
+
+    @Test
+    public void testPutAll_sizePowerOfTwo() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "bbb");
+        original.putValue("c", "ccc");
+        original.putValue("d", "ddd");
+        assertEquals("size", 4, original.size());
+
+        // add empty context data
+        original.putAll(new JdkMapAdapterStringMap());
+        assertEquals("size after put empty", 4, original.size());
+        assertEquals("aaa", original.getValue("a"));
+        assertEquals("bbb", original.getValue("b"));
+        assertEquals("ccc", original.getValue("c"));
+        assertEquals("ddd", original.getValue("d"));
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        other.putValue("1", "111");
+        other.putValue("2", "222");
+        other.putValue("3", "333");
+        other.putValue("4", "444");
+        original.putAll(other);
+
+        assertEquals("size after put other", 8, original.size());
+        assertEquals("aaa", original.getValue("a"));
+        assertEquals("bbb", original.getValue("b"));
+        assertEquals("ccc", original.getValue("c"));
+        assertEquals("ddd", original.getValue("d"));
+        assertEquals("111", original.getValue("1"));
+        assertEquals("222", original.getValue("2"));
+        assertEquals("333", original.getValue("3"));
+        assertEquals("444", original.getValue("4"));
+    }
+
+    @Test
+    public void testPutAll_largeAddition() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue(null, "nullVal");
+        original.putValue("a", "aaa");
+        original.putValue("b", "bbb");
+        original.putValue("c", "ccc");
+        original.putValue("d", "ddd");
+        assertEquals("size", 5, original.size());
+
+        final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap();
+        for (int i = 0 ; i < 500; i++) {
+            other.putValue(String.valueOf(i), String.valueOf(i));
+        }
+        other.putValue(null, "otherVal");
+        original.putAll(other);
+
+        assertEquals("size after put other", 505, original.size());
+        assertEquals("otherVal", original.getValue(null));
+        assertEquals("aaa", original.getValue("a"));
+        assertEquals("bbb", original.getValue("b"));
+        assertEquals("ccc", original.getValue("c"));
+        assertEquals("ddd", original.getValue("d"));
+        for (int i = 0 ; i < 500; i++) {
+            assertEquals(String.valueOf(i), original.getValue(String.valueOf(i)));
+        }
+    }
+
+    @Test
+    public void testPutAllSelfDoesNotModify() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "bbb");
+        original.putValue("c", "ccc");
+        assertEquals("size", 3, original.size());
+
+        // putAll with self
+        original.putAll(original);
+        assertEquals("size after put empty", 3, original.size());
+        assertEquals("aaa", original.getValue("a"));
+        assertEquals("bbb", original.getValue("b"));
+        assertEquals("ccc", original.getValue("c"));
+    }
+
+    @Test(expected = ConcurrentModificationException.class)
+    public void testConcurrentModificationBiConsumerPut() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "aaa");
+        original.putValue("c", "aaa");
+        original.putValue("d", "aaa");
+        original.putValue("e", "aaa");
+        original.forEach(new BiConsumer<String, Object>() {
+            @Override
+            public void accept(final String s, final Object o) {
+                original.putValue("c" + s, "other");
+            }
+        });
+    }
+
+    @Test(expected = ConcurrentModificationException.class)
+    public void testConcurrentModificationBiConsumerPutValue() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "aaa");
+        original.putValue("c", "aaa");
+        original.putValue("d", "aaa");
+        original.putValue("e", "aaa");
+        original.forEach(new BiConsumer<String, Object>() {
+            @Override
+            public void accept(final String s, final Object o) {
+                original.putValue("c" + s, "other");
+            }
+        });
+    }
+
+    @Test(expected = ConcurrentModificationException.class)
+    public void testConcurrentModificationBiConsumerRemove() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "aaa");
+        original.putValue("c", "aaa");
+        original.forEach(new BiConsumer<String, Object>() {
+            @Override
+            public void accept(final String s, final Object o) {
+                original.remove("a");
+            }
+        });
+    }
+
+    @Test(expected = ConcurrentModificationException.class)
+    public void testConcurrentModificationBiConsumerClear() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "aaa");
+        original.putValue("c", "aaa");
+        original.putValue("d", "aaa");
+        original.putValue("e", "aaa");
+        original.forEach(new BiConsumer<String, Object>() {
+            @Override
+            public void accept(final String s, final Object o) {
+                original.clear();
+            }
+        });
+    }
+
+    @Test(expected = ConcurrentModificationException.class)
+    public void testConcurrentModificationTriConsumerPut() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "aaa");
+        original.putValue("d", "aaa");
+        original.putValue("e", "aaa");
+        original.forEach(new TriConsumer<String, Object, Object>() {
+            @Override
+            public void accept(final String s, final Object o, final Object o2) {
+                original.putValue("c", "other");
+            }
+        }, null);
+    }
+
+    @Test(expected = ConcurrentModificationException.class)
+    public void testConcurrentModificationTriConsumerPutValue() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "aaa");
+        original.putValue("c", "aaa");
+        original.putValue("d", "aaa");
+        original.putValue("e", "aaa");
+        original.forEach(new TriConsumer<String, Object, Object>() {
+            @Override
+            public void accept(final String s, final Object o, final Object o2) {
+                original.putValue("c" + s, "other");
+            }
+        }, null);
+    }
+
+    @Test(expected = ConcurrentModificationException.class)
+    public void testConcurrentModificationTriConsumerRemove() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "aaa");
+        original.putValue("c", "aaa");
+        original.forEach(new TriConsumer<String, Object, Object>() {
+            @Override
+            public void accept(final String s, final Object o, final Object o2) {
+                original.remove("a");
+            }
+        }, null);
+    }
+
+    @Test(expected = ConcurrentModificationException.class)
+    public void testConcurrentModificationTriConsumerClear() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.putValue("b", "aaa");
+        original.putValue("c", "aaa");
+        original.putValue("d", "aaa");
+        original.forEach(new TriConsumer<String, Object, Object>() {
+            @Override
+            public void accept(final String s, final Object o, final Object o2) {
+                original.clear();
+            }
+        }, null);
+    }
+
+    @Test
+    public void testInitiallyNotFrozen() {
+        assertFalse(new JdkMapAdapterStringMap().isFrozen());
+    }
+
+    @Test
+    public void testIsFrozenAfterCallingFreeze() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        assertFalse("before freeze", original.isFrozen());
+        original.freeze();
+        assertTrue("after freeze", original.isFrozen());
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testFreezeProhibitsPutValue() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.freeze();
+        original.putValue("a", "aaa");
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testFreezeProhibitsRemove() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("b", "bbb");
+        original.freeze();
+        original.remove("b"); // existing key: modifies the collection
+    }
+
+    @Test
+    public void testFreezeAllowsRemoveOfNonExistingKey() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("b", "bbb");
+        original.freeze();
+        original.remove("a"); // no actual modification
+    }
+
+    @Test
+    public void testFreezeAllowsRemoveIfEmpty() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.freeze();
+        original.remove("a"); // no exception
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testFreezeProhibitsClear() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "aaa");
+        original.freeze();
+        original.clear();
+    }
+
+    @Test
+    public void testFreezeAllowsClearIfEmpty() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.freeze();
+        original.clear();
+    }
+
+    @Test
+    public void testNullKeysAllowed() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        original.putValue("c", "cvalue");
+        original.putValue("d", "dvalue");
+        assertEquals(5, original.size());
+
+        original.putValue(null, "nullvalue");
+        assertEquals(6, original.size());
+        assertEquals("nullvalue", original.getValue(null));
+
+        original.putValue(null, "otherNullvalue");
+        assertEquals("otherNullvalue", original.getValue(null));
+        assertEquals(6, original.size());
+
+        original.putValue(null, "nullvalue");
+        assertEquals(6, original.size());
+        assertEquals("nullvalue", original.getValue(null));
+
+        original.putValue(null, "abc");
+        assertEquals(6, original.size());
+        assertEquals("abc", original.getValue(null));
+
+    }
+
+    @Test
+    public void testNullKeysCopiedToAsMap() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        original.putValue("c", "cvalue");
+        original.putValue("d", "dvalue");
+        assertEquals(5, original.size());
+
+        final HashMap<String, String> expected = new HashMap<>();
+        expected.put("a", "avalue");
+        expected.put("B", "Bvalue");
+        expected.put("3", "3value");
+        expected.put("c", "cvalue");
+        expected.put("d", "dvalue");
+        assertEquals("initial", expected, original.toMap());
+
+        original.putValue(null, "nullvalue");
+        expected.put(null, "nullvalue");
+        assertEquals(6, original.size());
+        assertEquals("with null key", expected, original.toMap());
+
+        original.putValue(null, "otherNullvalue");
+        expected.put(null, "otherNullvalue");
+        assertEquals(6, original.size());
+        assertEquals("with null key value2", expected, original.toMap());
+
+        original.putValue(null, "nullvalue");
+        expected.put(null, "nullvalue");
+        assertEquals(6, original.size());
+        assertEquals("with null key value1 again", expected, original.toMap());
+
+        original.putValue(null, "abc");
+        expected.put(null, "abc");
+        assertEquals(6, original.size());
+        assertEquals("with null key value3", expected, original.toMap());
+    }
+
+    @Test
+    public void testRemove() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        assertEquals(1, original.size());
+        assertEquals("avalue", original.getValue("a"));
+
+        original.remove("a");
+        assertEquals(0, original.size());
+        assertNull("no a val", original.getValue("a"));
+
+        original.remove("B");
+        assertEquals(0, original.size());
+        assertNull("no B val", original.getValue("B"));
+    }
+
+    @Test
+    public void testRemoveWhenFull() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("b", "bvalue");
+        original.putValue("c", "cvalue");
+        original.putValue("d", "dvalue"); // default capacity = 4
+        original.remove("d");
+    }
+
+    @Test
+    public void testNullValuesArePreserved() {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        assertEquals(1, original.size());
+        assertEquals("avalue", original.getValue("a"));
+
+        original.putValue("a", null);
+        assertEquals(1, original.size());
+        assertNull("no a val", original.getValue("a"));
+
+        original.putValue("B", null);
+        assertEquals(2, original.size());
+        assertNull("no B val", original.getValue("B"));
+    }
+
+    @Test
+    public void testGet() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        assertEquals("avalue", original.getValue("a"));
+        assertEquals("Bvalue", original.getValue("B"));
+        assertEquals("3value", original.getValue("3"));
+
+        original.putValue("0", "0value");
+        assertEquals("0value", original.getValue("0"));
+        assertEquals("3value", original.getValue("3"));
+        assertEquals("Bvalue", original.getValue("B"));
+        assertEquals("avalue", original.getValue("a"));
+    }
+
+    @Test
+    public void testClear() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        assertEquals(3, original.size());
+
+        original.clear();
+        assertEquals(0, original.size());
+    }
+
+    @Test
+    public void testContainsKey() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        assertFalse("a", original.containsKey("a"));
+        assertFalse("B", original.containsKey("B"));
+        assertFalse("3", original.containsKey("3"));
+        assertFalse("A", original.containsKey("A"));
+
+        original.putValue("a", "avalue");
+        assertTrue("a", original.containsKey("a"));
+        assertFalse("B", original.containsKey("B"));
+        assertFalse("3", original.containsKey("3"));
+        assertFalse("A", original.containsKey("A"));
+
+        original.putValue("B", "Bvalue");
+        assertTrue("a", original.containsKey("a"));
+        assertTrue("B", original.containsKey("B"));
+        assertFalse("3", original.containsKey("3"));
+        assertFalse("A", original.containsKey("A"));
+
+        original.putValue("3", "3value");
+        assertTrue("a", original.containsKey("a"));
+        assertTrue("B", original.containsKey("B"));
+        assertTrue("3", original.containsKey("3"));
+        assertFalse("A", original.containsKey("A"));
+
+        original.putValue("A", "AAA");
+        assertTrue("a", original.containsKey("a"));
+        assertTrue("B", original.containsKey("B"));
+        assertTrue("3", original.containsKey("3"));
+        assertTrue("A", original.containsKey("A"));
+    }
+
+    @Test
+    public void testSizeAndIsEmpty() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        assertEquals(0, original.size());
+        assertTrue("initial", original.isEmpty());
+
+        original.putValue("a", "avalue");
+        assertEquals(1, original.size());
+        assertFalse("size=" + original.size(), original.isEmpty());
+
+        original.putValue("B", "Bvalue");
+        assertEquals(2, original.size());
+        assertFalse("size=" + original.size(), original.isEmpty());
+
+        original.putValue("3", "3value");
+        assertEquals(3, original.size());
+        assertFalse("size=" + original.size(), original.isEmpty());
+
+        original.remove("B");
+        assertEquals(2, original.size());
+        assertFalse("size=" + original.size(), original.isEmpty());
+
+        original.remove("3");
+        assertEquals(1, original.size());
+        assertFalse("size=" + original.size(), original.isEmpty());
+
+        original.remove("a");
+        assertEquals(0, original.size());
+        assertTrue("size=" + original.size(), original.isEmpty());
+    }
+
+    @Test
+    public void testForEachBiConsumer() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        original.forEach(new BiConsumer<String, String>() {
+            int count = 0;
+            @Override
+            public void accept(final String key, final String value) {
+//                assertEquals("key", key, original.getKeyAt(count));
+//                assertEquals("val", value, original.getValueAt(count));
+                count++;
+                assertTrue("count should not exceed size but was " + count, count <= original.size());
+            }
+        });
+    }
+
+    static class State {
+        JdkMapAdapterStringMap data;
+        int count;
+    }
+    static TriConsumer<String, String, JdkMapAdapterStringMapTest.State> COUNTER = new TriConsumer<String, String, JdkMapAdapterStringMapTest.State>() {
+        @Override
+        public void accept(final String key, final String value, final JdkMapAdapterStringMapTest.State state) {
+//            assertEquals("key", key, state.data.getKeyAt(state.count));
+//            assertEquals("val", value, state.data.getValueAt(state.count));
+            state.count++;
+            assertTrue("count should not exceed size but was " + state.count,
+                    state.count <= state.data.size());
+        }
+    };
+
+    @Test
+    public void testForEachTriConsumer() throws Exception {
+        final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        final JdkMapAdapterStringMapTest.State state = new JdkMapAdapterStringMapTest.State();
+        state.data = original;
+        original.forEach(COUNTER, state);
+        assertEquals(state.count, original.size());
+    }
+
+}
\ No newline at end of file