LOG4J2-1447 LOG4J2-1349 renamed ArrayContextData to SortedStringArrayMap
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/d676967b Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/d676967b Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/d676967b Branch: refs/heads/master Commit: d676967bd624da42d9042f1ff75015a4883c97e2 Parents: 35eda8a Author: rpopma <rpo...@apache.org> Authored: Thu Sep 22 21:38:33 2016 +0900 Committer: rpopma <rpo...@apache.org> Committed: Thu Sep 22 21:38:33 2016 +0900 ---------------------------------------------------------------------- .../CopyOnWriteSortedArrayThreadContextMap.java | 10 +- .../GarbageFreeSortedArrayThreadContextMap.java | 8 +- .../logging/log4j/util/ArrayContextData.java | 480 ------------ .../log4j/util/SortedStringArrayMap.java | 480 ++++++++++++ .../log4j/util/ArrayContextDataTest.java | 772 ------------------- .../log4j/util/SortedStringArrayMapTest.java | 772 +++++++++++++++++++ .../log4j/core/impl/ContextDataFactory.java | 10 +- .../ContextDataAttributeConverterTest.java | 6 +- .../ContextDataJsonAttributeConverterTest.java | 6 +- .../log4j/core/impl/Log4jLogEventTest.java | 6 +- .../log4j/core/impl/MutableLogEventTest.java | 4 +- .../jmh/ArrayContextDataVsHashMapBenchmark.java | 239 ------ .../perf/jmh/SortedArrayVsHashMapBenchmark.java | 239 ++++++ .../log4j/perf/jmh/ThreadContextBenchmark.java | 4 +- .../org/apache/logging/slf4j/MDCContextMap.java | 6 +- 15 files changed, 1521 insertions(+), 1521 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d676967b/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 71afbef..6bb13c1 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,11 +20,11 @@ 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.SortedStringArrayMap; import org.apache.logging.log4j.util.PropertiesUtil; /** - * {@code ArrayContextData}-based implementation of the {@code ThreadContextMap} interface that creates a copy of + * {@code SortedStringArrayMap}-based implementation of the {@code ThreadContextMap} interface that creates a copy of * the data structure on every modification. Any particular instance of the data structure is a snapshot of the * ThreadContext at some point in time and can safely be passed off to other threads. Since it is * expected that the Map will be passed to many more log events than the number of keys it contains the performance @@ -51,7 +51,7 @@ class CopyOnWriteSortedArrayThreadContextMap implements ThreadContextMap, Thread */ protected static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity"; - private static final MutableContextData EMPTY_CONTEXT_DATA = new ArrayContextData(); + private static final MutableContextData EMPTY_CONTEXT_DATA = new SortedStringArrayMap(); static { EMPTY_CONTEXT_DATA.freeze(); } @@ -87,7 +87,7 @@ class CopyOnWriteSortedArrayThreadContextMap implements ThreadContextMap, Thread * @return an implementation of the {@code MutableContextData} used to back this thread context map */ protected MutableContextData createMutableContextData() { - return new ArrayContextData(PropertiesUtil.getProperties().getIntegerProperty( + return new SortedStringArrayMap(PropertiesUtil.getProperties().getIntegerProperty( PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY)); } @@ -101,7 +101,7 @@ class CopyOnWriteSortedArrayThreadContextMap implements ThreadContextMap, Thread * @return an implementation of the {@code MutableContextData} used to back this thread context map */ protected MutableContextData createMutableContextData(final ContextData original) { - return new ArrayContextData(original); + return new SortedStringArrayMap(original); } @Override http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d676967b/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 4709004..7aaeae8 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,11 +20,11 @@ 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.SortedStringArrayMap; import org.apache.logging.log4j.util.PropertiesUtil; /** - * {@code ArrayContextData}-based implementation of the {@code ThreadContextMap} interface that attempts not to + * {@code SortedStringArrayMap}-based implementation of the {@code ThreadContextMap} interface that attempts not to * create temporary objects. Adding and removing key-value pairs will not create temporary objects. * <p> * This implementation does <em>not</em> make a copy of its contents on every operation, so this data structure cannot @@ -82,7 +82,7 @@ class GarbageFreeSortedArrayThreadContextMap implements ThreadContextMap, Thread * @return an implementation of the {@code MutableContextData} used to back this thread context map */ protected MutableContextData createMutableContextData() { - return new ArrayContextData(PropertiesUtil.getProperties().getIntegerProperty( + return new SortedStringArrayMap(PropertiesUtil.getProperties().getIntegerProperty( PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY)); } @@ -96,7 +96,7 @@ class GarbageFreeSortedArrayThreadContextMap implements ThreadContextMap, Thread * @return an implementation of the {@code MutableContextData} used to back this thread context map */ protected MutableContextData createMutableContextData(final ContextData original) { - return new ArrayContextData(original); + return new SortedStringArrayMap(original); } private MutableContextData getThreadLocalMap() { http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d676967b/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 deleted file mode 100644 index 41edc96..0000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ArrayContextData.java +++ /dev/null @@ -1,480 +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.util; - -import java.io.IOException; -import java.io.InvalidObjectException; -import java.util.Arrays; -import java.util.ConcurrentModificationException; -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; - -/** - * <em>Consider this class private.</em> - * 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 { - - /** - * 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 static final String FROZEN = "Frozen collection cannot be modified"; - - 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; - private boolean immutable; - private transient boolean iterating; - - 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); - } - } - - private void assertNotFrozen() { - if (immutable) { - throw new UnsupportedOperationException(FROZEN); - } - } - - private void assertNoConcurrentModification() { - if (iterating) { - throw new ConcurrentModificationException(); - } - } - - @Override - public void clear() { - if (keys == EMPTY) { - return; - } - assertNotFrozen(); - assertNoConcurrentModification(); - - 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> toMap() { - 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 void freeze() { - immutable = true; - } - - @Override - public boolean isFrozen() { - return immutable; - } - - @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 putValue(final String key, final Object value) { - assertNotFrozen(); - assertNoConcurrentModification(); - - 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 == this) { - return; // this.putAll(this) does not modify this collection - } - assertNotFrozen(); - assertNoConcurrentModification(); - - if (source instanceof ArrayContextData && this.size == 0) { - initFrom0((ArrayContextData) source); - } else if (source != null) { - source.forEach(PUT_ALL, this); - } - } - - 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(final 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) { - assertNotFrozen(); - assertNoConcurrentModification(); - - System.arraycopy(keys, index + 1, keys, index, size - 1 - index); - System.arraycopy(values, index + 1, values, index, size - 1 - index); - keys[size - 1] = null; - values[size - 1] = null; - 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(final BiConsumer<String, ? super V> action) { - iterating = true; - try { - for (int i = 0; i < size; i++) { - action.accept(keys[i], (V) values[i]); - } - } finally { - iterating = false; - } - } - - @SuppressWarnings("unchecked") - @Override - public <V, T> void forEach(final TriConsumer<String, ? super V, T> action, final T state) { - iterating = true; - try { - for (int i = 0; i < size; i++) { - action.accept(keys[i], (V) values[i], state); - } - } finally { - iterating = false; - } - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof ArrayContextData)) { - return false; - } - final 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(final Object[] values, final 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(final 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(final 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 - final int capacity = s.readInt(); - if (capacity < 0) { - throw new InvalidObjectException("Illegal capacity: " + capacity); - } - - // Read number of mappings - final 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/d676967b/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedStringArrayMap.java ---------------------------------------------------------------------- diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedStringArrayMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedStringArrayMap.java new file mode 100644 index 0000000..7cdf3a4 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedStringArrayMap.java @@ -0,0 +1,480 @@ +/* + * 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.ConcurrentModificationException; +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; + +/** + * <em>Consider this class private.</em> + * 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 SortedStringArrayMap}, 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 SortedStringArrayMap implements MutableContextData { + + /** + * 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 static final String FROZEN = "Frozen collection cannot be modified"; + + 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; + private boolean immutable; + private transient boolean iterating; + + public SortedStringArrayMap() { + this(DEFAULT_INITIAL_CAPACITY); + } + + public SortedStringArrayMap(final int initialCapacity) { + if (initialCapacity < 1) { + throw new IllegalArgumentException("Initial capacity must be at least one but was " + initialCapacity); + } + threshold = ceilingNextPowerOfTwo(initialCapacity); + } + + public SortedStringArrayMap(final ContextData other) { + if (other instanceof SortedStringArrayMap) { + initFrom0((SortedStringArrayMap) other); + } else if (other != null) { + resize(ceilingNextPowerOfTwo(other.size())); + other.forEach(PUT_ALL, this); + } + } + + private void assertNotFrozen() { + if (immutable) { + throw new UnsupportedOperationException(FROZEN); + } + } + + private void assertNoConcurrentModification() { + if (iterating) { + throw new ConcurrentModificationException(); + } + } + + @Override + public void clear() { + if (keys == EMPTY) { + return; + } + assertNotFrozen(); + assertNoConcurrentModification(); + + 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> toMap() { + 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 void freeze() { + immutable = true; + } + + @Override + public boolean isFrozen() { + return immutable; + } + + @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 putValue(final String key, final Object value) { + assertNotFrozen(); + assertNoConcurrentModification(); + + 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 == this) { + return; // this.putAll(this) does not modify this collection + } + assertNotFrozen(); + assertNoConcurrentModification(); + + if (source instanceof SortedStringArrayMap && this.size == 0) { + initFrom0((SortedStringArrayMap) source); + } else if (source != null) { + source.forEach(PUT_ALL, this); + } + } + + private void initFrom0(final SortedStringArrayMap 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(final 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) { + assertNotFrozen(); + assertNoConcurrentModification(); + + System.arraycopy(keys, index + 1, keys, index, size - 1 - index); + System.arraycopy(values, index + 1, values, index, size - 1 - index); + keys[size - 1] = null; + values[size - 1] = null; + 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(final BiConsumer<String, ? super V> action) { + iterating = true; + try { + for (int i = 0; i < size; i++) { + action.accept(keys[i], (V) values[i]); + } + } finally { + iterating = false; + } + } + + @SuppressWarnings("unchecked") + @Override + public <V, T> void forEach(final TriConsumer<String, ? super V, T> action, final T state) { + iterating = true; + try { + for (int i = 0; i < size; i++) { + action.accept(keys[i], (V) values[i], state); + } + } finally { + iterating = false; + } + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof SortedStringArrayMap)) { + return false; + } + final SortedStringArrayMap other = (SortedStringArrayMap) 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(final Object[] values, final 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 SortedStringArrayMap} instance to a stream (i.e., + * serialize it). + * + * @serialData The <i>capacity</i> of the SortedStringArrayMap (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(final 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 SortedStringArrayMap} instance from a stream (i.e., + * deserialize it). + */ + private void readObject(final 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 + final int capacity = s.readInt(); + if (capacity < 0) { + throw new InvalidObjectException("Illegal capacity: " + capacity); + } + + // Read number of mappings + final 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/d676967b/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 deleted file mode 100644 index bff68b5..0000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/ArrayContextDataTest.java +++ /dev/null @@ -1,772 +0,0 @@ -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.ConcurrentModificationException; -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 testToMap() 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.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 ArrayContextData original = new ArrayContextData(); - original.putValue("a", "aaa"); - original.putValue("b", "bbb"); - original.putValue("c", "ccc"); - assertEquals("size", 3, original.size()); - - // add empty context data - original.putAll(new ArrayContextData()); - assertEquals("size after put empty", 3, original.size()); - assertEquals("aaa", original.getValue("a")); - assertEquals("bbb", original.getValue("b")); - assertEquals("ccc", original.getValue("c")); - - final ArrayContextData other = new ArrayContextData(); - 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 testPutAllSelfDoesNotModify() { - final ArrayContextData original = new ArrayContextData(); - 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 ArrayContextData original = new ArrayContextData(); - original.putValue("a", "aaa"); - original.forEach(new BiConsumer<String, Object>() { - @Override - public void accept(final String s, final Object o) { - original.putValue("c", "other"); - } - }); - } - - @Test(expected = ConcurrentModificationException.class) - public void testConcurrentModificationBiConsumerPutValue() { - final ArrayContextData original = new ArrayContextData(); - original.putValue("a", "aaa"); - original.forEach(new BiConsumer<String, Object>() { - @Override - public void accept(final String s, final Object o) { - original.putValue("c", "other"); - } - }); - } - - @Test(expected = ConcurrentModificationException.class) - public void testConcurrentModificationBiConsumerRemove() { - final ArrayContextData original = new ArrayContextData(); - original.putValue("a", "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 ArrayContextData original = new ArrayContextData(); - original.putValue("a", "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 ArrayContextData original = new ArrayContextData(); - original.putValue("a", "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 ArrayContextData original = new ArrayContextData(); - original.putValue("a", "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 testConcurrentModificationTriConsumerRemove() { - final ArrayContextData original = new ArrayContextData(); - original.putValue("a", "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 ArrayContextData original = new ArrayContextData(); - original.putValue("a", "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 ArrayContextData().isFrozen()); - } - - @Test - public void testIsFrozenAfterCallingFreeze() { - final ArrayContextData original = new ArrayContextData(); - assertFalse("before freeze", original.isFrozen()); - original.freeze(); - assertTrue("after freeze", original.isFrozen()); - } - - @Test(expected = UnsupportedOperationException.class) - public void testFreezeProhibitsPutValue() { - final ArrayContextData original = new ArrayContextData(); - original.freeze(); - original.putValue("a", "aaa"); - } - - @Test(expected = UnsupportedOperationException.class) - public void testFreezeProhibitsRemove() { - final ArrayContextData original = new ArrayContextData(); - original.putValue("b", "bbb"); - original.freeze(); - original.remove("b"); // existing key: modifies the collection - } - - @Test - public void testFreezeAllowsRemoveOfNonExistingKey() { - final ArrayContextData original = new ArrayContextData(); - original.putValue("b", "bbb"); - original.freeze(); - original.remove("a"); // no actual modification - } - - @Test - public void testFreezeAllowsRemoveIfEmpty() { - final ArrayContextData original = new ArrayContextData(); - original.freeze(); - original.remove("a"); // no exception - } - - @Test(expected = UnsupportedOperationException.class) - public void testFreezeProhibitsClear() { - final ArrayContextData original = new ArrayContextData(); - original.putValue("a", "aaa"); - original.freeze(); - original.clear(); - } - - @Test - public void testFreezeAllowsClearIfEmpty() { - final ArrayContextData original = new ArrayContextData(); - original.freeze(); - original.clear(); - } - - @Test - public void testPutInsertsInAlphabeticOrder() 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 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()); - - 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 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 testRemoveNullsOutRemovedSlot() throws Exception { - final ArrayContextData original = new ArrayContextData(); - original.putValue("a", "avalue"); - original.putValue("b", "bvalue"); - original.putValue("c", "cvalue"); - original.putValue("d", "dvalue"); - original.remove("a"); - original.remove("b"); - original.remove("c"); - original.remove("d"); - assertNull(original.getValueAt(0)); - - // ensure slots in the values array are nulled out - final Field f = ArrayContextData.class.getDeclaredField("values"); - f.setAccessible(true); - final Object[] values = (Object[]) f.get(original); - for (int i = 0; i < values.length; i++) { - assertNull(values[i]); - } - } - - @Test - public void testRemoveWhenFull() throws Exception { - final ArrayContextData original = new ArrayContextData(); - 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 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.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 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 - final Field f = ArrayContextData.class.getDeclaredField("values"); - f.setAccessible(true); - final 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