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/02 14:44:16 UTC

[3/3] logging-log4j2 git commit: LOG4J2-1349 move ArrayContextData (and its test) from spi to util

LOG4J2-1349 move ArrayContextData (and its test) from spi to util


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

Branch: refs/heads/LOG4J2-1349-gcfree-threadcontext
Commit: e6cf7c9098a097ee570242877fd058d582f22131
Parents: 112a222
Author: rpopma <rp...@apache.org>
Authored: Fri Sep 2 23:44:03 2016 +0900
Committer: rpopma <rp...@apache.org>
Committed: Fri Sep 2 23:44:03 2016 +0900

----------------------------------------------------------------------
 .../logging/log4j/spi/ArrayContextData.java     | 448 ---------------
 .../CopyOnWriteSortedArrayThreadContextMap.java |   1 +
 .../GarbageFreeSortedArrayThreadContextMap.java |   1 +
 .../logging/log4j/util/ArrayContextData.java    | 449 +++++++++++++++
 .../logging/log4j/spi/ArrayContextDataTest.java | 569 -------------------
 .../log4j/util/ArrayContextDataTest.java        | 566 ++++++++++++++++++
 .../log4j/core/impl/ContextDataFactory.java     |   2 +-
 .../ContextDataAttributeConverterTest.java      |   2 +-
 .../ContextDataJsonAttributeConverterTest.java  |   2 +-
 .../log4j/core/impl/Log4jLogEventTest.java      |   2 +-
 .../log4j/core/impl/MutableLogEventTest.java    |   2 +-
 .../jmh/ArrayContextDataVsHashMapBenchmark.java |   2 +-
 .../log4j/perf/jmh/ThreadContextBenchmark.java  |   2 +-
 13 files changed, 1024 insertions(+), 1024 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-api/src/main/java/org/apache/logging/log4j/spi/ArrayContextData.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ArrayContextData.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ArrayContextData.java
deleted file mode 100644
index d934a3d..0000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ArrayContextData.java
+++ /dev/null
@@ -1,448 +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 java.io.IOException;
-import java.io.InvalidObjectException;
-import java.util.Arrays;
-import java.util.Collections;
-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.TriConsumer;
-
-/**
- * Array-based implementation of the {@code ContextData} interface. Keys are held in a sorted array.
- * <p>
- * This is not a generic collection, but makes some trade-offs to optimize for the Log4j ContextData use case:
- * </p>
- * <ul>
- *   <li>Garbage-free iteration over key-value pairs with {@code BiConsumer} and {@code TriConsumer}.</li>
- *   <li>Fast copy. If the ThreadContextMap is also an instance of {@code ArrayContextData}, the full thread context
- *     data can be transferred with two array copies and two field updates.</li>
- *   <li>Acceptable performance for small data sets. The current implementation stores keys in a sorted array, values
- *     are stored in a separate array at the same index.
- *     Worst-case performance of {@code get} and {@code containsKey} is O(log N),
- *     worst-case performance of {@code put} and {@code remove} is O(N log N).
- *     The expectation is that for the small values of {@code N} (less than 100) that are the vast majority of
- *     ThreadContext use cases, the constants dominate performance more than the asymptotic performance of the
- *     algorithms used.
- *     </li>
- *     <li>Compact representation.</li>
- * </ul>
- *
- * @since 2.7
- */
-public class ArrayContextData implements MutableContextData, ThreadContextMap {
-
-    /**
-     * The default initial capacity.
-     */
-    private static final int DEFAULT_INITIAL_CAPACITY = 4;
-    private static final long serialVersionUID = -5748905872274478116L;
-    private static final int HASHVAL = 31;
-
-    private static final TriConsumer<String, Object, MutableContextData> PUT_ALL = new TriConsumer<String, Object, MutableContextData>() {
-        @Override
-        public void accept(final String key, final Object value, final MutableContextData contextData) {
-            contextData.putValue(key, value);
-        }
-    };
-
-    /**
-     * An empty array instance to share when the table is not inflated.
-     */
-    private static final String[] EMPTY = {};
-
-    private transient String[] keys = EMPTY;
-    private transient Object[] values = EMPTY;
-
-    /**
-     * The number of key-value mappings contained in this map.
-     */
-    private transient int size;
-
-    /**
-     * The next size value at which to resize (capacity * load factor).
-     * @serial
-     */
-    // If table == EMPTY_TABLE then this is the initial capacity at which the
-    // table will be created when inflated.
-    private int threshold;
-
-    public ArrayContextData() {
-        this(DEFAULT_INITIAL_CAPACITY);
-    }
-
-    public ArrayContextData(final int initialCapacity) {
-        if (initialCapacity < 1) {
-            throw new IllegalArgumentException("Initial capacity must be at least one but was " + initialCapacity);
-        }
-        threshold = ceilingNextPowerOfTwo(initialCapacity);
-    }
-
-    public ArrayContextData(final ContextData other) {
-        if (other instanceof ArrayContextData) {
-            initFrom0((ArrayContextData) other);
-        } else if (other != null) {
-            resize(ceilingNextPowerOfTwo(other.size()));
-            other.forEach(PUT_ALL, this);
-        }
-    }
-
-    @Override
-    public void clear() {
-        Arrays.fill(keys, 0, size, null);
-        Arrays.fill(values, 0, size, null);
-        size = 0;
-    }
-
-    @Override
-    public boolean containsKey(final String key) {
-        return indexOfKey(key) >= 0;
-    }
-
-    @Override
-    public Map<String, String> asMap() {
-        final Map<String, String> result = new HashMap<>(size());
-        for (int i = 0; i < size(); i++) {
-            final Object value = getValueAt(i);
-            result.put(getKeyAt(i), value == null ? null : String.valueOf(value));
-        }
-        return result;
-    }
-
-    @Override
-    public Map<String, String> getCopy() {
-        return asMap();
-    }
-
-    @Override
-    public Map<String, String> getImmutableMapOrNull() {
-        return isEmpty() ? null : Collections.unmodifiableMap(asMap());
-    }
-
-    @Override
-    public String get(final String key) {
-        final Object result = getValue(key);
-        return result == null ? null : String.valueOf(result);
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public <V> V getValue(final String key) {
-        final int index = indexOfKey(key);
-        if (index < 0) {
-            return null;
-        }
-        return (V) values[index];
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return size == 0;
-    }
-
-    int indexOfKey(final String key) {
-        if (keys == EMPTY) {
-            return -1;
-        }
-        if (key == null) { // null key is located at the start of the array
-            return nullKeyIndex(); // insert at index zero
-        }
-        final int start = size > 0 && keys[0] == null ? 1 : 0;
-        return Arrays.binarySearch(keys, start, size, key);
-    }
-
-    private int nullKeyIndex() {
-        return size > 0 && keys[0] == null ? 0 : ~0;
-    }
-
-    @Override
-    public void put(final String key, final String value) {
-        putValue(key, value);
-    }
-
-    @Override
-    public void putValue(final String key, final Object value) {
-        if (keys == EMPTY) {
-            inflateTable(threshold);
-        }
-        final int index = indexOfKey(key);
-        if (index >= 0) {
-            keys[index] = key;
-            values[index] = value;
-        } else { // not found, so insert.
-            insertAt(~index, key, value);
-        }
-    }
-
-    private void insertAt(final int index, final String key, final Object value) {
-        ensureCapacity();
-        System.arraycopy(keys, index, keys, index + 1, size - index);
-        System.arraycopy(values, index, values, index + 1, size - index);
-        keys[index] = key;
-        values[index] = value;
-        size++;
-    }
-
-    @Override
-    public void putAll(final ContextData source) {
-        if (source instanceof ArrayContextData) {
-            initFrom0((ArrayContextData) source);
-        } else if (source != null) {
-            source.forEach(PUT_ALL, this);
-        }
-    }
-
-    public void initFrom(final ArrayContextData other) {
-        initFrom0(other);
-    }
-
-    private void initFrom0(final ArrayContextData other) {
-        if (keys.length < other.size) {
-            keys = new String[other.threshold];
-            values = new Object[other.threshold];
-        }
-        System.arraycopy(other.keys, 0, keys, 0, other.size);
-        System.arraycopy(other.values, 0, values, 0, other.size);
-
-        size = other.size;
-        threshold = other.threshold;
-    }
-
-    private void ensureCapacity() {
-        if (size >= threshold) {
-            resize(threshold * 2);
-        }
-    }
-
-    private void resize(final int newCapacity) {
-        final String[] oldKeys = keys;
-        final Object[] oldValues = values;
-
-        keys = new String[newCapacity];
-        values = new Object[newCapacity];
-
-        System.arraycopy(oldKeys, 0, keys, 0, size);
-        System.arraycopy(oldValues, 0, values, 0, size);
-
-        threshold = newCapacity;
-    }
-
-    /**
-     * Inflates the table.
-     */
-    private void inflateTable(int toSize) {
-        threshold = toSize;
-        keys = new String[toSize];
-        values = new Object[toSize];
-    }
-
-    @Override
-    public void remove(final String key) {
-        if (keys == EMPTY) {
-            return;
-        }
-        final int index = indexOfKey(key);
-        if (index >= 0) {
-            System.arraycopy(keys, index + 1, keys, index, size - index);
-            System.arraycopy(values, index + 1, values, index, size - index);
-            size--;
-        }
-    }
-
-    String getKeyAt(final int index) {
-        if (index < 0 || index >= size) {
-            return null;
-        }
-        return keys[index];
-    }
-
-    @SuppressWarnings("unchecked")
-    <V> V getValueAt(final int index) {
-        if (index < 0 || index >= size) {
-            return null;
-        }
-        return (V) values[index];
-    }
-
-    @Override
-    public int size() {
-        return size;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public <V> void forEach(BiConsumer<String, ? super V> action) {
-        for (int i = 0; i < size; i++) {
-            action.accept(keys[i], (V) values[i]);
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public <V, T> void forEach(TriConsumer<String, ? super V, T> action, T state) {
-        for (int i = 0; i < size; i++) {
-            action.accept(keys[i], (V) values[i], state);
-        }
-    }
-
-    @Override
-    public boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (!(obj instanceof ArrayContextData)) {
-            return false;
-        }
-        ArrayContextData other = (ArrayContextData) obj;
-        if (this.size() != other.size()) {
-            return false;
-        }
-        for (int i = 0; i < size(); i++) {
-            if (!Objects.equals(keys[i], other.keys[i])) {
-                return false;
-            }
-            if (!Objects.equals(values[i], other.values[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 37;
-        result = HASHVAL * result + size;
-        result = HASHVAL * result + hashCode(keys, size);
-        result = HASHVAL * result + hashCode(values, size);
-        return result;
-    }
-
-    private static int hashCode(Object[] values, int length) {
-        int result = 1;
-        for (int i = 0; i < length; i++) {
-            result = HASHVAL * result + (values[i] == null ? 0 : values[i].hashCode());
-        }
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder(256);
-        sb.append('{');
-        for (int i = 0; i < size; i++) {
-            if (i > 0) {
-                sb.append(", ");
-            }
-            sb.append(keys[i]).append('=');
-            sb.append(values[i] == this ? "(this map)" : values[i]);
-        }
-        sb.append('}');
-        return sb.toString();
-    }
-
-    /**
-     * Save the state of the {@code ArrayContextData} instance to a stream (i.e.,
-     * serialize it).
-     *
-     * @serialData The <i>capacity</i> of the ArrayContextData (the length of the
-     *             bucket array) is emitted (int), followed by the
-     *             <i>size</i> (an int, the number of key-value
-     *             mappings), followed by the key (Object) and value (Object)
-     *             for each key-value mapping.  The key-value mappings are
-     *             emitted in no particular order.
-     */
-    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
-        // Write out the threshold, and any hidden stuff
-        s.defaultWriteObject();
-
-        // Write out number of buckets
-        if (keys == EMPTY) {
-            s.writeInt(ceilingNextPowerOfTwo(threshold));
-        } else {
-            s.writeInt(keys.length);
-        }
-
-        // Write out size (number of Mappings)
-        s.writeInt(size);
-
-        // Write out keys and values (alternating)
-        if (size > 0) {
-            for (int i = 0; i < size; i++) {
-                s.writeObject(keys[i]);
-                s.writeObject(values[i]);
-            }
-        }
-    }
-
-
-    /**
-     * Calculate the next power of 2, greater than or equal to x.
-     * <p>
-     * From Hacker's Delight, Chapter 3, Harry S. Warren Jr.
-     *
-     * @param x Value to round up
-     * @return The next power of 2 from x inclusive
-     */
-    private static int ceilingNextPowerOfTwo(final int x) {
-        final int BITS_PER_INT = 32;
-        return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1));
-    }
-
-    /**
-     * Reconstitute the {@code ArrayContextData} instance from a stream (i.e.,
-     * deserialize it).
-     */
-    private void readObject(java.io.ObjectInputStream s)  throws IOException, ClassNotFoundException {
-        // Read in the threshold (ignored), and any hidden stuff
-        s.defaultReadObject();
-
-        // set other fields that need values
-        keys = EMPTY;
-        values = EMPTY;
-
-        // Read in number of buckets
-        int capacity = s.readInt();
-        if (capacity < 0) {
-            throw new InvalidObjectException("Illegal capacity: " + capacity);
-        }
-
-        // Read number of mappings
-        int mappings = s.readInt();
-        if (mappings < 0) {
-            throw new InvalidObjectException("Illegal mappings count: " + mappings);
-        }
-
-        // allocate the bucket array;
-        if (mappings > 0) {
-            inflateTable(capacity);
-        } else {
-            threshold = capacity;
-        }
-
-        // Read the keys and values, and put the mappings in the arrays
-        for (int i = 0; i < mappings; i++) {
-            keys[i] = (String) s.readObject();
-            values[i] = s.readObject();
-        }
-        size = mappings;
-    }
-}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
index 08bac8e..80c0e4d 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
@@ -20,6 +20,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.util.PropertiesUtil;
 
 /**

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
index 1fe2f65..04705dd 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
@@ -20,6 +20,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.util.PropertiesUtil;
 
 /**

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-api/src/main/java/org/apache/logging/log4j/util/ArrayContextData.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ArrayContextData.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ArrayContextData.java
new file mode 100644
index 0000000..bdc9951
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ArrayContextData.java
@@ -0,0 +1,449 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.logging.log4j.spi.ContextData;
+import org.apache.logging.log4j.spi.MutableContextData;
+import org.apache.logging.log4j.spi.ThreadContextMap;
+
+/**
+ * Array-based implementation of the {@code ContextData} interface. Keys are held in a sorted array.
+ * <p>
+ * This is not a generic collection, but makes some trade-offs to optimize for the Log4j ContextData use case:
+ * </p>
+ * <ul>
+ *   <li>Garbage-free iteration over key-value pairs with {@code BiConsumer} and {@code TriConsumer}.</li>
+ *   <li>Fast copy. If the ThreadContextMap is also an instance of {@code ArrayContextData}, the full thread context
+ *     data can be transferred with two array copies and two field updates.</li>
+ *   <li>Acceptable performance for small data sets. The current implementation stores keys in a sorted array, values
+ *     are stored in a separate array at the same index.
+ *     Worst-case performance of {@code get} and {@code containsKey} is O(log N),
+ *     worst-case performance of {@code put} and {@code remove} is O(N log N).
+ *     The expectation is that for the small values of {@code N} (less than 100) that are the vast majority of
+ *     ThreadContext use cases, the constants dominate performance more than the asymptotic performance of the
+ *     algorithms used.
+ *     </li>
+ *     <li>Compact representation.</li>
+ * </ul>
+ *
+ * @since 2.7
+ */
+public class ArrayContextData implements MutableContextData, ThreadContextMap {
+
+    /**
+     * The default initial capacity.
+     */
+    private static final int DEFAULT_INITIAL_CAPACITY = 4;
+    private static final long serialVersionUID = -5748905872274478116L;
+    private static final int HASHVAL = 31;
+
+    private static final TriConsumer<String, Object, MutableContextData> PUT_ALL = new TriConsumer<String, Object, MutableContextData>() {
+        @Override
+        public void accept(final String key, final Object value, final MutableContextData contextData) {
+            contextData.putValue(key, value);
+        }
+    };
+
+    /**
+     * An empty array instance to share when the table is not inflated.
+     */
+    private static final String[] EMPTY = {};
+
+    private transient String[] keys = EMPTY;
+    private transient Object[] values = EMPTY;
+
+    /**
+     * The number of key-value mappings contained in this map.
+     */
+    private transient int size;
+
+    /**
+     * The next size value at which to resize (capacity * load factor).
+     * @serial
+     */
+    // If table == EMPTY_TABLE then this is the initial capacity at which the
+    // table will be created when inflated.
+    private int threshold;
+
+    public ArrayContextData() {
+        this(DEFAULT_INITIAL_CAPACITY);
+    }
+
+    public ArrayContextData(final int initialCapacity) {
+        if (initialCapacity < 1) {
+            throw new IllegalArgumentException("Initial capacity must be at least one but was " + initialCapacity);
+        }
+        threshold = ceilingNextPowerOfTwo(initialCapacity);
+    }
+
+    public ArrayContextData(final ContextData other) {
+        if (other instanceof ArrayContextData) {
+            initFrom0((ArrayContextData) other);
+        } else if (other != null) {
+            resize(ceilingNextPowerOfTwo(other.size()));
+            other.forEach(PUT_ALL, this);
+        }
+    }
+
+    @Override
+    public void clear() {
+        Arrays.fill(keys, 0, size, null);
+        Arrays.fill(values, 0, size, null);
+        size = 0;
+    }
+
+    @Override
+    public boolean containsKey(final String key) {
+        return indexOfKey(key) >= 0;
+    }
+
+    @Override
+    public Map<String, String> asMap() {
+        final Map<String, String> result = new HashMap<>(size());
+        for (int i = 0; i < size(); i++) {
+            final Object value = getValueAt(i);
+            result.put(getKeyAt(i), value == null ? null : String.valueOf(value));
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, String> getCopy() {
+        return asMap();
+    }
+
+    @Override
+    public Map<String, String> getImmutableMapOrNull() {
+        return isEmpty() ? null : Collections.unmodifiableMap(asMap());
+    }
+
+    @Override
+    public String get(final String key) {
+        final Object result = getValue(key);
+        return result == null ? null : String.valueOf(result);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <V> V getValue(final String key) {
+        final int index = indexOfKey(key);
+        if (index < 0) {
+            return null;
+        }
+        return (V) values[index];
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    int indexOfKey(final String key) {
+        if (keys == EMPTY) {
+            return -1;
+        }
+        if (key == null) { // null key is located at the start of the array
+            return nullKeyIndex(); // insert at index zero
+        }
+        final int start = size > 0 && keys[0] == null ? 1 : 0;
+        return Arrays.binarySearch(keys, start, size, key);
+    }
+
+    private int nullKeyIndex() {
+        return size > 0 && keys[0] == null ? 0 : ~0;
+    }
+
+    @Override
+    public void put(final String key, final String value) {
+        putValue(key, value);
+    }
+
+    @Override
+    public void putValue(final String key, final Object value) {
+        if (keys == EMPTY) {
+            inflateTable(threshold);
+        }
+        final int index = indexOfKey(key);
+        if (index >= 0) {
+            keys[index] = key;
+            values[index] = value;
+        } else { // not found, so insert.
+            insertAt(~index, key, value);
+        }
+    }
+
+    private void insertAt(final int index, final String key, final Object value) {
+        ensureCapacity();
+        System.arraycopy(keys, index, keys, index + 1, size - index);
+        System.arraycopy(values, index, values, index + 1, size - index);
+        keys[index] = key;
+        values[index] = value;
+        size++;
+    }
+
+    @Override
+    public void putAll(final ContextData source) {
+        if (source instanceof ArrayContextData) {
+            initFrom0((ArrayContextData) source);
+        } else if (source != null) {
+            source.forEach(PUT_ALL, this);
+        }
+    }
+
+    public void initFrom(final ArrayContextData other) {
+        initFrom0(other);
+    }
+
+    private void initFrom0(final ArrayContextData other) {
+        if (keys.length < other.size) {
+            keys = new String[other.threshold];
+            values = new Object[other.threshold];
+        }
+        System.arraycopy(other.keys, 0, keys, 0, other.size);
+        System.arraycopy(other.values, 0, values, 0, other.size);
+
+        size = other.size;
+        threshold = other.threshold;
+    }
+
+    private void ensureCapacity() {
+        if (size >= threshold) {
+            resize(threshold * 2);
+        }
+    }
+
+    private void resize(final int newCapacity) {
+        final String[] oldKeys = keys;
+        final Object[] oldValues = values;
+
+        keys = new String[newCapacity];
+        values = new Object[newCapacity];
+
+        System.arraycopy(oldKeys, 0, keys, 0, size);
+        System.arraycopy(oldValues, 0, values, 0, size);
+
+        threshold = newCapacity;
+    }
+
+    /**
+     * Inflates the table.
+     */
+    private void inflateTable(int toSize) {
+        threshold = toSize;
+        keys = new String[toSize];
+        values = new Object[toSize];
+    }
+
+    @Override
+    public void remove(final String key) {
+        if (keys == EMPTY) {
+            return;
+        }
+        final int index = indexOfKey(key);
+        if (index >= 0) {
+            System.arraycopy(keys, index + 1, keys, index, size - index);
+            System.arraycopy(values, index + 1, values, index, size - index);
+            size--;
+        }
+    }
+
+    String getKeyAt(final int index) {
+        if (index < 0 || index >= size) {
+            return null;
+        }
+        return keys[index];
+    }
+
+    @SuppressWarnings("unchecked")
+    <V> V getValueAt(final int index) {
+        if (index < 0 || index >= size) {
+            return null;
+        }
+        return (V) values[index];
+    }
+
+    @Override
+    public int size() {
+        return size;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <V> void forEach(BiConsumer<String, ? super V> action) {
+        for (int i = 0; i < size; i++) {
+            action.accept(keys[i], (V) values[i]);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <V, T> void forEach(TriConsumer<String, ? super V, T> action, T state) {
+        for (int i = 0; i < size; i++) {
+            action.accept(keys[i], (V) values[i], state);
+        }
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof ArrayContextData)) {
+            return false;
+        }
+        ArrayContextData other = (ArrayContextData) obj;
+        if (this.size() != other.size()) {
+            return false;
+        }
+        for (int i = 0; i < size(); i++) {
+            if (!Objects.equals(keys[i], other.keys[i])) {
+                return false;
+            }
+            if (!Objects.equals(values[i], other.values[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 37;
+        result = HASHVAL * result + size;
+        result = HASHVAL * result + hashCode(keys, size);
+        result = HASHVAL * result + hashCode(values, size);
+        return result;
+    }
+
+    private static int hashCode(Object[] values, int length) {
+        int result = 1;
+        for (int i = 0; i < length; i++) {
+            result = HASHVAL * result + (values[i] == null ? 0 : values[i].hashCode());
+        }
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder(256);
+        sb.append('{');
+        for (int i = 0; i < size; i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append(keys[i]).append('=');
+            sb.append(values[i] == this ? "(this map)" : values[i]);
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+
+    /**
+     * Save the state of the {@code ArrayContextData} instance to a stream (i.e.,
+     * serialize it).
+     *
+     * @serialData The <i>capacity</i> of the ArrayContextData (the length of the
+     *             bucket array) is emitted (int), followed by the
+     *             <i>size</i> (an int, the number of key-value
+     *             mappings), followed by the key (Object) and value (Object)
+     *             for each key-value mapping.  The key-value mappings are
+     *             emitted in no particular order.
+     */
+    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
+        // Write out the threshold, and any hidden stuff
+        s.defaultWriteObject();
+
+        // Write out number of buckets
+        if (keys == EMPTY) {
+            s.writeInt(ceilingNextPowerOfTwo(threshold));
+        } else {
+            s.writeInt(keys.length);
+        }
+
+        // Write out size (number of Mappings)
+        s.writeInt(size);
+
+        // Write out keys and values (alternating)
+        if (size > 0) {
+            for (int i = 0; i < size; i++) {
+                s.writeObject(keys[i]);
+                s.writeObject(values[i]);
+            }
+        }
+    }
+
+
+    /**
+     * Calculate the next power of 2, greater than or equal to x.
+     * <p>
+     * From Hacker's Delight, Chapter 3, Harry S. Warren Jr.
+     *
+     * @param x Value to round up
+     * @return The next power of 2 from x inclusive
+     */
+    private static int ceilingNextPowerOfTwo(final int x) {
+        final int BITS_PER_INT = 32;
+        return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1));
+    }
+
+    /**
+     * Reconstitute the {@code ArrayContextData} instance from a stream (i.e.,
+     * deserialize it).
+     */
+    private void readObject(java.io.ObjectInputStream s)  throws IOException, ClassNotFoundException {
+        // Read in the threshold (ignored), and any hidden stuff
+        s.defaultReadObject();
+
+        // set other fields that need values
+        keys = EMPTY;
+        values = EMPTY;
+
+        // Read in number of buckets
+        int capacity = s.readInt();
+        if (capacity < 0) {
+            throw new InvalidObjectException("Illegal capacity: " + capacity);
+        }
+
+        // Read number of mappings
+        int mappings = s.readInt();
+        if (mappings < 0) {
+            throw new InvalidObjectException("Illegal mappings count: " + mappings);
+        }
+
+        // allocate the bucket array;
+        if (mappings > 0) {
+            inflateTable(capacity);
+        } else {
+            threshold = capacity;
+        }
+
+        // Read the keys and values, and put the mappings in the arrays
+        for (int i = 0; i < mappings; i++) {
+            keys[i] = (String) s.readObject();
+            values[i] = s.readObject();
+        }
+        size = mappings;
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-api/src/test/java/org/apache/logging/log4j/spi/ArrayContextDataTest.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/spi/ArrayContextDataTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/spi/ArrayContextDataTest.java
deleted file mode 100644
index f85db8a..0000000
--- a/log4j-api/src/test/java/org/apache/logging/log4j/spi/ArrayContextDataTest.java
+++ /dev/null
@@ -1,569 +0,0 @@
-package org.apache.logging.log4j.spi;/*
- * 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.
- */
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.lang.reflect.Field;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.logging.log4j.spi.ArrayContextData;
-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 ArrayContextData class.
- */
-public class ArrayContextDataTest {
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructorDisallowsNegativeCapacity() throws Exception {
-        new ArrayContextData(-1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructorDisallowsZeroCapacity() throws Exception {
-        new ArrayContextData(0);
-    }
-
-    @Test
-    public void testConstructorIgnoresNull() throws Exception {
-        assertEquals(0, new ArrayContextData(null).size());
-    }
-
-    @Test
-    public void testToString() {
-        final ArrayContextData original = new ArrayContextData();
-        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 ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-
-        final byte[] binary = serialize(original);
-        final ArrayContextData copy = deserialize(binary);
-        assertEquals(original, copy);
-    }
-
-    private byte[] serialize(final ArrayContextData data) throws IOException {
-        final ByteArrayOutputStream arr = new ByteArrayOutputStream();
-        final ObjectOutputStream out = new ObjectOutputStream(arr);
-        out.writeObject(data);
-        return arr.toByteArray();
-    }
-
-    private ArrayContextData deserialize(final byte[] binary) throws IOException, ClassNotFoundException {
-        final ByteArrayInputStream inArr = new ByteArrayInputStream(binary);
-        final ObjectInputStream in = new ObjectInputStream(inArr);
-        final ArrayContextData result = (ArrayContextData) in.readObject();
-        return result;
-    }
-
-    @Test
-    public void testPutAll() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-
-        final ArrayContextData other = new ArrayContextData();
-        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 testEquals() {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-        assertEquals(original, original); // equal to itself
-
-        final ArrayContextData other = new ArrayContextData();
-        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 testAsMap() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        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.asMap());
-    }
-
-    @Test
-    public void testGetCopyDelegatesToAsMap() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        assertEquals(original.getCopy(), original.asMap());
-
-        original.putValue("B", "Bvalue");
-        assertEquals(original.getCopy(), original.asMap());
-
-        original.putValue("3", "3value");
-        assertEquals(original.getCopy(), original.asMap());
-    }
-
-    @Test
-    public void testGetImmutableMapOrNull() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        assertEquals(original.getImmutableMapOrNull(), original.asMap());
-
-        original.putValue("B", "Bvalue");
-        assertEquals(original.getImmutableMapOrNull(), original.asMap());
-
-        original.putValue("3", "3value");
-        assertEquals(original.getImmutableMapOrNull(), original.asMap());
-
-        try {
-            original.getImmutableMapOrNull().put("abc", "xyz");
-            fail("Expected map to be immutable");
-        } catch (final UnsupportedOperationException ok) {
-            //ok
-        }
-    }
-
-    @Test
-    public void testPutInsertsInAlphabeticOrder() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.put("a", "avalue");
-        original.put("B", "Bvalue");
-        original.put("3", "3value");
-        original.put("c", "cvalue");
-        original.put("d", "dvalue");
-
-        assertEquals("avalue", original.getValue("a"));
-        assertEquals("avalue", original.getValueAt(2));
-
-        assertEquals("Bvalue", original.getValue("B"));
-        assertEquals("Bvalue", original.getValueAt(1));
-
-        assertEquals("3value", original.getValue("3"));
-        assertEquals("3value", original.getValueAt(0));
-
-        assertEquals("cvalue", original.getValue("c"));
-        assertEquals("cvalue", original.getValueAt(3));
-
-        assertEquals("dvalue", original.getValue("d"));
-        assertEquals("dvalue", original.getValueAt(4));
-    }
-
-    @Test
-    public void testPutValueInsertsInAlphabeticOrder() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-        original.putValue("c", "cvalue");
-        original.putValue("d", "dvalue");
-
-        assertEquals("avalue", original.getValue("a"));
-        assertEquals("avalue", original.getValueAt(2));
-
-        assertEquals("Bvalue", original.getValue("B"));
-        assertEquals("Bvalue", original.getValueAt(1));
-
-        assertEquals("3value", original.getValue("3"));
-        assertEquals("3value", original.getValueAt(0));
-
-        assertEquals("cvalue", original.getValue("c"));
-        assertEquals("cvalue", original.getValueAt(3));
-
-        assertEquals("dvalue", original.getValue("d"));
-        assertEquals("dvalue", original.getValueAt(4));
-    }
-
-    @Test
-    public void testNullKeysAllowed() {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-        original.putValue("c", "cvalue");
-        original.putValue("d", "dvalue");
-        assertEquals(5, original.size());
-        assertEquals("{3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
-
-        original.putValue(null, "nullvalue");
-        assertEquals(6, original.size());
-        assertEquals("{null=nullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
-
-        original.putValue(null, "otherNullvalue");
-        assertEquals("{null=otherNullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
-        assertEquals(6, original.size());
-
-        original.putValue(null, "nullvalue");
-        assertEquals(6, original.size());
-        assertEquals("{null=nullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
-
-        original.putValue(null, "abc");
-        assertEquals(6, original.size());
-        assertEquals("{null=abc, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
-    }
-
-    @Test
-    public void testNullKeysCopiedToAsMap() {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-        original.putValue("c", "cvalue");
-        original.putValue("d", "dvalue");
-        assertEquals(5, original.size());
-
-        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.asMap());
-
-        original.putValue(null, "nullvalue");
-        expected.put(null, "nullvalue");
-        assertEquals(6, original.size());
-        assertEquals("with null key", expected, original.asMap());
-
-        original.putValue(null, "otherNullvalue");
-        expected.put(null, "otherNullvalue");
-        assertEquals(6, original.size());
-        assertEquals("with null key value2", expected, original.asMap());
-
-        original.putValue(null, "nullvalue");
-        expected.put(null, "nullvalue");
-        assertEquals(6, original.size());
-        assertEquals("with null key value1 again", expected, original.asMap());
-
-        original.putValue(null, "abc");
-        expected.put(null, "abc");
-        assertEquals(6, original.size());
-        assertEquals("with null key value3", expected, original.asMap());
-    }
-
-    @Test
-    public void testRemove() {
-        final ArrayContextData original = new ArrayContextData();
-        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 testNullValuesArePreserved() {
-        final ArrayContextData original = new ArrayContextData();
-        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 ArrayContextData original = new ArrayContextData();
-        original.put("a", "avalue");
-        original.put("B", "Bvalue");
-        original.put("3", "3value");
-
-        assertEquals("avalue", original.get("a"));
-        assertEquals("Bvalue", original.get("B"));
-        assertEquals("3value", original.get("3"));
-
-        original.putValue("0", "0value");
-        assertEquals("0value", original.get("0"));
-        assertEquals("3value", original.get("3"));
-        assertEquals("Bvalue", original.get("B"));
-        assertEquals("avalue", original.get("a"));
-    }
-
-    @Test
-    public void testGetValue_GetValueAt() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-
-        assertEquals("avalue", original.getValue("a"));
-        assertEquals("avalue", original.getValueAt(2));
-
-        assertEquals("Bvalue", original.getValue("B"));
-        assertEquals("Bvalue", original.getValueAt(1));
-
-        assertEquals("3value", original.getValue("3"));
-        assertEquals("3value", original.getValueAt(0));
-
-        original.putValue("0", "0value");
-        assertEquals("0value", original.getValue("0"));
-        assertEquals("0value", original.getValueAt(0));
-        assertEquals("3value", original.getValue("3"));
-        assertEquals("3value", original.getValueAt(1));
-        assertEquals("Bvalue", original.getValue("B"));
-        assertEquals("Bvalue", original.getValueAt(2));
-        assertEquals("avalue", original.getValue("a"));
-        assertEquals("avalue", original.getValueAt(3));
-    }
-
-    @Test
-    public void testClear() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-        assertEquals(3, original.size());
-
-        original.clear();
-        assertEquals(0, original.size());
-
-        // ensure slots in the values array are nulled out
-        Field f = ArrayContextData.class.getDeclaredField("values");
-        f.setAccessible(true);
-        Object[] values = (Object[]) f.get(original);
-        for (int i = 0; i < values.length; i++) {
-            assertNull(values[i]);
-        }
-    }
-
-    @Test
-    public void testIndexOfKey() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        assertEquals(0, original.indexOfKey("a"));
-
-        original.putValue("B", "Bvalue");
-        assertEquals(1, original.indexOfKey("a"));
-        assertEquals(0, original.indexOfKey("B"));
-
-        original.putValue("3", "3value");
-        assertEquals(2, original.indexOfKey("a"));
-        assertEquals(1, original.indexOfKey("B"));
-        assertEquals(0, original.indexOfKey("3"));
-
-        original.putValue("A", "AAA");
-        assertEquals(3, original.indexOfKey("a"));
-        assertEquals(2, original.indexOfKey("B"));
-        assertEquals(1, original.indexOfKey("A"));
-        assertEquals(0, original.indexOfKey("3"));
-
-        original.putValue("C", "CCC");
-        assertEquals(4, original.indexOfKey("a"));
-        assertEquals(3, original.indexOfKey("C"));
-        assertEquals(2, original.indexOfKey("B"));
-        assertEquals(1, original.indexOfKey("A"));
-        assertEquals(0, original.indexOfKey("3"));
-
-        original.putValue("2", "222");
-        assertEquals(5, original.indexOfKey("a"));
-        assertEquals(4, original.indexOfKey("C"));
-        assertEquals(3, original.indexOfKey("B"));
-        assertEquals(2, original.indexOfKey("A"));
-        assertEquals(1, original.indexOfKey("3"));
-        assertEquals(0, original.indexOfKey("2"));
-    }
-
-    @Test
-    public void testContainsKey() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        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 testGetValueAt() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        assertEquals("a", original.getKeyAt(0));
-        assertEquals("avalue", original.getValueAt(0));
-
-        original.putValue("B", "Bvalue");
-        assertEquals("B", original.getKeyAt(0));
-        assertEquals("Bvalue", original.getValueAt(0));
-        assertEquals("a", original.getKeyAt(1));
-        assertEquals("avalue", original.getValueAt(1));
-
-        original.putValue("3", "3value");
-        assertEquals("3", original.getKeyAt(0));
-        assertEquals("3value", original.getValueAt(0));
-        assertEquals("B", original.getKeyAt(1));
-        assertEquals("Bvalue", original.getValueAt(1));
-        assertEquals("a", original.getKeyAt(2));
-        assertEquals("avalue", original.getValueAt(2));
-    }
-
-    @Test
-    public void testSizeAndIsEmpty() throws Exception {
-        final ArrayContextData original = new ArrayContextData();
-        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 ArrayContextData original = new ArrayContextData();
-        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 {
-        ArrayContextData data;
-        int count;
-    }
-    static TriConsumer<String, String, State> COUNTER = new TriConsumer<String, String, State>() {
-        @Override
-        public void accept(final String key, final String value, final 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 ArrayContextData original = new ArrayContextData();
-        original.putValue("a", "avalue");
-        original.putValue("B", "Bvalue");
-        original.putValue("3", "3value");
-
-        final State state = new State();
-        state.data = original;
-        original.forEach(COUNTER, state);
-        assertEquals(state.count, original.size());
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-api/src/test/java/org/apache/logging/log4j/util/ArrayContextDataTest.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/ArrayContextDataTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/ArrayContextDataTest.java
new file mode 100644
index 0000000..e3ac0a4
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/util/ArrayContextDataTest.java
@@ -0,0 +1,566 @@
+package org.apache.logging.log4j.util;/*
+ * 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.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests the ArrayContextData class.
+ */
+public class ArrayContextDataTest {
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructorDisallowsNegativeCapacity() throws Exception {
+        new ArrayContextData(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructorDisallowsZeroCapacity() throws Exception {
+        new ArrayContextData(0);
+    }
+
+    @Test
+    public void testConstructorIgnoresNull() throws Exception {
+        assertEquals(0, new ArrayContextData(null).size());
+    }
+
+    @Test
+    public void testToString() {
+        final ArrayContextData original = new ArrayContextData();
+        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 ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        final byte[] binary = serialize(original);
+        final ArrayContextData copy = deserialize(binary);
+        assertEquals(original, copy);
+    }
+
+    private byte[] serialize(final ArrayContextData data) throws IOException {
+        final ByteArrayOutputStream arr = new ByteArrayOutputStream();
+        final ObjectOutputStream out = new ObjectOutputStream(arr);
+        out.writeObject(data);
+        return arr.toByteArray();
+    }
+
+    private ArrayContextData deserialize(final byte[] binary) throws IOException, ClassNotFoundException {
+        final ByteArrayInputStream inArr = new ByteArrayInputStream(binary);
+        final ObjectInputStream in = new ObjectInputStream(inArr);
+        final ArrayContextData result = (ArrayContextData) in.readObject();
+        return result;
+    }
+
+    @Test
+    public void testPutAll() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        final ArrayContextData other = new ArrayContextData();
+        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 testEquals() {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        assertEquals(original, original); // equal to itself
+
+        final ArrayContextData other = new ArrayContextData();
+        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 testAsMap() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        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.asMap());
+    }
+
+    @Test
+    public void testGetCopyDelegatesToAsMap() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        assertEquals(original.getCopy(), original.asMap());
+
+        original.putValue("B", "Bvalue");
+        assertEquals(original.getCopy(), original.asMap());
+
+        original.putValue("3", "3value");
+        assertEquals(original.getCopy(), original.asMap());
+    }
+
+    @Test
+    public void testGetImmutableMapOrNull() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        assertEquals(original.getImmutableMapOrNull(), original.asMap());
+
+        original.putValue("B", "Bvalue");
+        assertEquals(original.getImmutableMapOrNull(), original.asMap());
+
+        original.putValue("3", "3value");
+        assertEquals(original.getImmutableMapOrNull(), original.asMap());
+
+        try {
+            original.getImmutableMapOrNull().put("abc", "xyz");
+            fail("Expected map to be immutable");
+        } catch (final UnsupportedOperationException ok) {
+            //ok
+        }
+    }
+
+    @Test
+    public void testPutInsertsInAlphabeticOrder() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.put("a", "avalue");
+        original.put("B", "Bvalue");
+        original.put("3", "3value");
+        original.put("c", "cvalue");
+        original.put("d", "dvalue");
+
+        assertEquals("avalue", original.getValue("a"));
+        assertEquals("avalue", original.getValueAt(2));
+
+        assertEquals("Bvalue", original.getValue("B"));
+        assertEquals("Bvalue", original.getValueAt(1));
+
+        assertEquals("3value", original.getValue("3"));
+        assertEquals("3value", original.getValueAt(0));
+
+        assertEquals("cvalue", original.getValue("c"));
+        assertEquals("cvalue", original.getValueAt(3));
+
+        assertEquals("dvalue", original.getValue("d"));
+        assertEquals("dvalue", original.getValueAt(4));
+    }
+
+    @Test
+    public void testPutValueInsertsInAlphabeticOrder() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        original.putValue("c", "cvalue");
+        original.putValue("d", "dvalue");
+
+        assertEquals("avalue", original.getValue("a"));
+        assertEquals("avalue", original.getValueAt(2));
+
+        assertEquals("Bvalue", original.getValue("B"));
+        assertEquals("Bvalue", original.getValueAt(1));
+
+        assertEquals("3value", original.getValue("3"));
+        assertEquals("3value", original.getValueAt(0));
+
+        assertEquals("cvalue", original.getValue("c"));
+        assertEquals("cvalue", original.getValueAt(3));
+
+        assertEquals("dvalue", original.getValue("d"));
+        assertEquals("dvalue", original.getValueAt(4));
+    }
+
+    @Test
+    public void testNullKeysAllowed() {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        original.putValue("c", "cvalue");
+        original.putValue("d", "dvalue");
+        assertEquals(5, original.size());
+        assertEquals("{3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
+
+        original.putValue(null, "nullvalue");
+        assertEquals(6, original.size());
+        assertEquals("{null=nullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
+
+        original.putValue(null, "otherNullvalue");
+        assertEquals("{null=otherNullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
+        assertEquals(6, original.size());
+
+        original.putValue(null, "nullvalue");
+        assertEquals(6, original.size());
+        assertEquals("{null=nullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
+
+        original.putValue(null, "abc");
+        assertEquals(6, original.size());
+        assertEquals("{null=abc, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString());
+    }
+
+    @Test
+    public void testNullKeysCopiedToAsMap() {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        original.putValue("c", "cvalue");
+        original.putValue("d", "dvalue");
+        assertEquals(5, original.size());
+
+        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.asMap());
+
+        original.putValue(null, "nullvalue");
+        expected.put(null, "nullvalue");
+        assertEquals(6, original.size());
+        assertEquals("with null key", expected, original.asMap());
+
+        original.putValue(null, "otherNullvalue");
+        expected.put(null, "otherNullvalue");
+        assertEquals(6, original.size());
+        assertEquals("with null key value2", expected, original.asMap());
+
+        original.putValue(null, "nullvalue");
+        expected.put(null, "nullvalue");
+        assertEquals(6, original.size());
+        assertEquals("with null key value1 again", expected, original.asMap());
+
+        original.putValue(null, "abc");
+        expected.put(null, "abc");
+        assertEquals(6, original.size());
+        assertEquals("with null key value3", expected, original.asMap());
+    }
+
+    @Test
+    public void testRemove() {
+        final ArrayContextData original = new ArrayContextData();
+        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 testNullValuesArePreserved() {
+        final ArrayContextData original = new ArrayContextData();
+        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 ArrayContextData original = new ArrayContextData();
+        original.put("a", "avalue");
+        original.put("B", "Bvalue");
+        original.put("3", "3value");
+
+        assertEquals("avalue", original.get("a"));
+        assertEquals("Bvalue", original.get("B"));
+        assertEquals("3value", original.get("3"));
+
+        original.putValue("0", "0value");
+        assertEquals("0value", original.get("0"));
+        assertEquals("3value", original.get("3"));
+        assertEquals("Bvalue", original.get("B"));
+        assertEquals("avalue", original.get("a"));
+    }
+
+    @Test
+    public void testGetValue_GetValueAt() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        assertEquals("avalue", original.getValue("a"));
+        assertEquals("avalue", original.getValueAt(2));
+
+        assertEquals("Bvalue", original.getValue("B"));
+        assertEquals("Bvalue", original.getValueAt(1));
+
+        assertEquals("3value", original.getValue("3"));
+        assertEquals("3value", original.getValueAt(0));
+
+        original.putValue("0", "0value");
+        assertEquals("0value", original.getValue("0"));
+        assertEquals("0value", original.getValueAt(0));
+        assertEquals("3value", original.getValue("3"));
+        assertEquals("3value", original.getValueAt(1));
+        assertEquals("Bvalue", original.getValue("B"));
+        assertEquals("Bvalue", original.getValueAt(2));
+        assertEquals("avalue", original.getValue("a"));
+        assertEquals("avalue", original.getValueAt(3));
+    }
+
+    @Test
+    public void testClear() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+        assertEquals(3, original.size());
+
+        original.clear();
+        assertEquals(0, original.size());
+
+        // ensure slots in the values array are nulled out
+        Field f = ArrayContextData.class.getDeclaredField("values");
+        f.setAccessible(true);
+        Object[] values = (Object[]) f.get(original);
+        for (int i = 0; i < values.length; i++) {
+            assertNull(values[i]);
+        }
+    }
+
+    @Test
+    public void testIndexOfKey() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        assertEquals(0, original.indexOfKey("a"));
+
+        original.putValue("B", "Bvalue");
+        assertEquals(1, original.indexOfKey("a"));
+        assertEquals(0, original.indexOfKey("B"));
+
+        original.putValue("3", "3value");
+        assertEquals(2, original.indexOfKey("a"));
+        assertEquals(1, original.indexOfKey("B"));
+        assertEquals(0, original.indexOfKey("3"));
+
+        original.putValue("A", "AAA");
+        assertEquals(3, original.indexOfKey("a"));
+        assertEquals(2, original.indexOfKey("B"));
+        assertEquals(1, original.indexOfKey("A"));
+        assertEquals(0, original.indexOfKey("3"));
+
+        original.putValue("C", "CCC");
+        assertEquals(4, original.indexOfKey("a"));
+        assertEquals(3, original.indexOfKey("C"));
+        assertEquals(2, original.indexOfKey("B"));
+        assertEquals(1, original.indexOfKey("A"));
+        assertEquals(0, original.indexOfKey("3"));
+
+        original.putValue("2", "222");
+        assertEquals(5, original.indexOfKey("a"));
+        assertEquals(4, original.indexOfKey("C"));
+        assertEquals(3, original.indexOfKey("B"));
+        assertEquals(2, original.indexOfKey("A"));
+        assertEquals(1, original.indexOfKey("3"));
+        assertEquals(0, original.indexOfKey("2"));
+    }
+
+    @Test
+    public void testContainsKey() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        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 testGetValueAt() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        assertEquals("a", original.getKeyAt(0));
+        assertEquals("avalue", original.getValueAt(0));
+
+        original.putValue("B", "Bvalue");
+        assertEquals("B", original.getKeyAt(0));
+        assertEquals("Bvalue", original.getValueAt(0));
+        assertEquals("a", original.getKeyAt(1));
+        assertEquals("avalue", original.getValueAt(1));
+
+        original.putValue("3", "3value");
+        assertEquals("3", original.getKeyAt(0));
+        assertEquals("3value", original.getValueAt(0));
+        assertEquals("B", original.getKeyAt(1));
+        assertEquals("Bvalue", original.getValueAt(1));
+        assertEquals("a", original.getKeyAt(2));
+        assertEquals("avalue", original.getValueAt(2));
+    }
+
+    @Test
+    public void testSizeAndIsEmpty() throws Exception {
+        final ArrayContextData original = new ArrayContextData();
+        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 ArrayContextData original = new ArrayContextData();
+        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 {
+        ArrayContextData data;
+        int count;
+    }
+    static TriConsumer<String, String, State> COUNTER = new TriConsumer<String, String, State>() {
+        @Override
+        public void accept(final String key, final String value, final 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 ArrayContextData original = new ArrayContextData();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("3", "3value");
+
+        final State state = new State();
+        state.data = original;
+        original.forEach(COUNTER, state);
+        assertEquals(state.count, original.size());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java
index 6661863..8f65b61 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java
@@ -16,7 +16,7 @@
  */
 package org.apache.logging.log4j.core.impl;
 
-import org.apache.logging.log4j.spi.ArrayContextData;
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.spi.MutableContextData;
 import org.apache.logging.log4j.util.PropertiesUtil;
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataAttributeConverterTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataAttributeConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataAttributeConverterTest.java
index 00ee55f..32dc8e5 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataAttributeConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataAttributeConverterTest.java
@@ -16,7 +16,7 @@
  */
 package org.apache.logging.log4j.core.appender.db.jpa.converter;
 
-import org.apache.logging.log4j.spi.ArrayContextData;
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.spi.MutableContextData;
 import org.junit.After;
 import org.junit.Before;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataJsonAttributeConverterTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataJsonAttributeConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataJsonAttributeConverterTest.java
index 6930ed5..f486452 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataJsonAttributeConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/converter/ContextDataJsonAttributeConverterTest.java
@@ -17,7 +17,7 @@
 package org.apache.logging.log4j.core.appender.db.jpa.converter;
 
 import org.apache.logging.log4j.spi.ContextData;
-import org.apache.logging.log4j.spi.ArrayContextData;
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.spi.MutableContextData;
 import org.junit.After;
 import org.junit.Before;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
index 931fb15..b13bb8e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
@@ -40,7 +40,7 @@ import org.apache.logging.log4j.core.util.DummyNanoClock;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ObjectMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
-import org.apache.logging.log4j.spi.ArrayContextData;
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.spi.MutableContextData;
 import org.apache.logging.log4j.util.Strings;
 import org.junit.AfterClass;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
index 0b2babf..70727a6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
@@ -29,7 +29,7 @@ import org.apache.logging.log4j.MarkerManager;
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.message.ParameterizedMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
-import org.apache.logging.log4j.spi.ArrayContextData;
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.spi.MutableContextData;
 import org.apache.logging.log4j.spi.MutableThreadContextStack;
 import org.junit.Test;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ArrayContextDataVsHashMapBenchmark.java
----------------------------------------------------------------------
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ArrayContextDataVsHashMapBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ArrayContextDataVsHashMapBenchmark.java
index fae27de..24148e1 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ArrayContextDataVsHashMapBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ArrayContextDataVsHashMapBenchmark.java
@@ -22,8 +22,8 @@ import java.util.Map;
 import java.util.Random;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.logging.log4j.spi.ArrayContextData;
 import org.apache.logging.log4j.perf.nogc.OpenHashMapContextData;
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.util.BiConsumer;
 import org.apache.logging.log4j.util.TriConsumer;
 import org.openjdk.jmh.annotations.Benchmark;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e6cf7c90/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
----------------------------------------------------------------------
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
index cfa2eb7..6e07abb 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
@@ -30,7 +30,7 @@ import org.apache.logging.log4j.ThreadContextBenchmarkAccess;
 import org.apache.logging.log4j.core.config.Property;
 import org.apache.logging.log4j.core.impl.ContextDataInjector;
 import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
-import org.apache.logging.log4j.spi.ArrayContextData;
+import org.apache.logging.log4j.util.ArrayContextData;
 import org.apache.logging.log4j.perf.nogc.CopyOnWriteOpenHashMapThreadContextMap;
 import org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap;
 import org.apache.logging.log4j.spi.DefaultThreadContextMap;