All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.logging.log4j.internal.map.UnmodifiableArrayBackedMap Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta2
Show newest version
/*
 * 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.internal.map;

import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.TriConsumer;

/**
 * This class represents an immutable map, which stores its state inside a single Object[]:
 * 
    *
  1. [0] contains the number of entries
  2. *
  3. Others contain alternating key-value pairs, for example [1]="1" and [2]="value_for_1"
  4. *
* * Keys are calculated using (index * 2 + 1) and values are (index * 2 + 2). * * Performance: *
    *
  • Implements very low-cost copies: shallow-copy the array.
  • *
  • Doesn't matter for mutable operations, since we don't allow them.
  • *
  • Iterates very quickly, since it iterates directly across the array. This * contrasts with HashMap's requirement to scan each bucket in the table and * chase each pointer.
  • *
  • Is linear on gets, puts, and removes, since the table must be scanned to * find a matching key.
  • *
* * Allocation: *
    *
  • Zero on reads.
  • *
  • Copy-and-modify operations allocate exactly two objects: the new array * and the new Map instance. This is substantially better than HashMap, which * requires a new Node for each entry.
  • *
* */ public class UnmodifiableArrayBackedMap extends AbstractMap implements Serializable, ReadOnlyStringMap { /** * Implementation of Map.Entry. The implementation is simple since each instance * contains an index in the array, then getKey() and getValue() retrieve from * the array. Blocks modifications. */ private class UnmodifiableEntry implements Map.Entry { /** * This field is functionally final, but marking it as such can cause * performance problems. Consider marking it final after * https://bugs.openjdk.org/browse/JDK-8324186 is solved. */ private int index; public UnmodifiableEntry(int index) { this.index = index; } @Override public String getKey() { return (String) backingArray[getArrayIndexForKey(index)]; } @Override public String getValue() { return (String) backingArray[getArrayIndexForValue(index)]; } /** * Per spec, the hashcode is a function of the key and value. Calculation * exactly matches HashMap. */ public int hashCode() { String key = (String) backingArray[getArrayIndexForKey(index)]; String value = (String) backingArray[getArrayIndexForValue(index)]; return Objects.hashCode(key) ^ Objects.hashCode(value); } @Override public String setValue(String value) { throw new UnsupportedOperationException("Cannot update Entry instances in UnmodifiableArrayBackedMap"); } } /** * Simple Entry iterator, tracking solely the index in the array. Blocks * modifications. */ private class UnmodifiableEntryIterator implements Iterator> { private int index; @Override public boolean hasNext() { return index < numEntries; } @Override public Entry next() { return new UnmodifiableEntry(index++); } } /** * Simple Entry set, providing a reference to UnmodifiableEntryIterator and * blocking modifications. */ private class UnmodifiableEntrySet extends AbstractSet> { @Override public boolean add(Entry e) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection> c) { throw new UnsupportedOperationException(); } @Override public Iterator> iterator() { return new UnmodifiableEntryIterator(); } @Override public int size() { return numEntries; } } private static final long serialVersionUID = 6849423432534211514L; public static final UnmodifiableArrayBackedMap EMPTY_MAP = new UnmodifiableArrayBackedMap(0); private static final int NUM_FIXED_ARRAY_ENTRIES = 1; private static int getArrayIndexForKey(int entryIndex) { return 2 * entryIndex + NUM_FIXED_ARRAY_ENTRIES; } private static int getArrayIndexForValue(int entryIndex) { return 2 * entryIndex + 1 + NUM_FIXED_ARRAY_ENTRIES; } public static UnmodifiableArrayBackedMap getMap(Object[] backingArray) { if (backingArray == null || backingArray.length == 1) { return EMPTY_MAP; } else { return new UnmodifiableArrayBackedMap(backingArray); } } /** * backingArray is functionally final, but marking it as such can cause * performance problems. Consider marking it final after * https://bugs.openjdk.org/browse/JDK-8324186 is solved. */ private Object[] backingArray; private int numEntries; private UnmodifiableArrayBackedMap(int capacity) { this.backingArray = new Object[capacity * 2 + 1]; this.backingArray[0] = 0; } private UnmodifiableArrayBackedMap(Object[] backingArray) { this.numEntries = (backingArray == null ? 0 : (int) backingArray[0]); this.backingArray = backingArray; } UnmodifiableArrayBackedMap(UnmodifiableArrayBackedMap other) { this.backingArray = other.backingArray; this.numEntries = other.numEntries; } private void add(String key, String value) { backingArray[getArrayIndexForKey(numEntries)] = key; backingArray[getArrayIndexForValue(numEntries)] = value; numEntries++; } @Override public void clear() { throw new UnsupportedOperationException("Instance cannot be cleared, reuse EMPTY_MAP instead."); } /** * Scans the array to find a matching key. Linear performance. */ @Override public boolean containsKey(Object key) { return containsKey((String) key); } @Override public boolean containsKey(String key) { int hashCode = key.hashCode(); for (int i = 0; i < numEntries; i++) { if (backingArray[getArrayIndexForKey(i)].hashCode() == hashCode && backingArray[getArrayIndexForKey(i)].equals(key)) { return true; } } return false; } public Object[] getBackingArray() { return backingArray; } /** * Scans the array to find a matching value, with linear time. Allows null * parameter. */ @Override public boolean containsValue(Object value) { for (int i = 0; i < numEntries; i++) { Object valueInMap = backingArray[getArrayIndexForValue(i)]; if (value == null) { if (valueInMap == null) { return true; } } else if (value.equals(valueInMap)) { return true; } } return false; } /** * Creates a new instance that contains the same entries as this map, plus * either the new entry or updated value passed in the parameters. * * @param key * @param value * @return */ public UnmodifiableArrayBackedMap copyAndPut(String key, String value) { UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries + 1); // include the numEntries value (array index 0) if (this.numEntries > 0) { System.arraycopy(this.backingArray, 1, newMap.backingArray, 1, numEntries * 2); newMap.numEntries = numEntries; } newMap.addOrOverwriteKey(key, value); newMap.updateNumEntriesInArray(); return newMap; } /** * Creates a new instance that contains the same entries as this map, plus the * new entries or updated values passed in the parameters. * * @param key * @param value * @return */ public UnmodifiableArrayBackedMap copyAndPutAll(Map entriesToAdd) { // create a new array that can hold the maximum output size UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries + entriesToAdd.size()); // copy the contents of the current map (if any) if (numEntries > 0) { System.arraycopy(backingArray, 0, newMap.backingArray, 0, numEntries * 2 + 1); newMap.numEntries = numEntries; } for (Map.Entry entry : entriesToAdd.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); if (!this.isEmpty()) { // The unique elements passed in may overlap the unique elements here - must // check newMap.addOrOverwriteKey(key, value); } else { // There is no chance of overlapping keys, we can simply add newMap.add(key, value); } } newMap.updateNumEntriesInArray(); return newMap; } /** * Creates a new instance that contains the same entries as this map, minus the * entry with the specified key (if such an entry exists). * * @param key * @param value * @return */ public UnmodifiableArrayBackedMap copyAndRemove(String key) { int indexToRemove = -1; for (int oldIndex = numEntries - 1; oldIndex >= 0; oldIndex--) { if (backingArray[getArrayIndexForKey(oldIndex)].hashCode() == key.hashCode() && backingArray[getArrayIndexForKey(oldIndex)].equals(key)) { indexToRemove = oldIndex; break; } } if (indexToRemove == -1) { // key not found, no change necessary return this; } else if (numEntries == 1) { // we have 1 item and we're about to remove it return EMPTY_MAP; } UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries); if (indexToRemove > 0) { // copy entries before the removed one System.arraycopy(backingArray, 1, newMap.backingArray, 1, indexToRemove * 2); } if (indexToRemove + 1 < numEntries) { // copy entries after the removed one int nextIndexToCopy = indexToRemove + 1; int numRemainingEntries = numEntries - nextIndexToCopy; System.arraycopy( backingArray, getArrayIndexForKey(nextIndexToCopy), newMap.backingArray, getArrayIndexForKey(indexToRemove), numRemainingEntries * 2); } newMap.numEntries = numEntries - 1; newMap.updateNumEntriesInArray(); return newMap; } /** * Creates a new instance that contains the same entries as this map, minus all * of the keys passed in the arguments. * * @param key * @param value * @return */ public UnmodifiableArrayBackedMap copyAndRemoveAll(Iterable keysToRemoveIterable) { if (isEmpty()) { // shortcut: if this map is empty, the result will continue to be empty return EMPTY_MAP; } // now we build a Set of keys to remove Set keysToRemoveSet; if (keysToRemoveIterable instanceof Set) { // we already have a set, let's cast it and reuse it keysToRemoveSet = (Set) keysToRemoveIterable; } else { // iterate through the keys and build a set keysToRemoveSet = new HashSet<>(); for (String key : keysToRemoveIterable) { keysToRemoveSet.add(key); } } int firstIndexToKeep = -1; int lastIndexToKeep = -1; int destinationIndex = 0; int numEntriesKept = 0; // build the new map UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries); for (int indexInCurrentMap = 0; indexInCurrentMap < numEntries; indexInCurrentMap++) { // for each key in this map, check whether it's in the set we built above Object key = backingArray[getArrayIndexForKey(indexInCurrentMap)]; if (!keysToRemoveSet.contains(key)) { // this key should be kept if (firstIndexToKeep == -1) { firstIndexToKeep = indexInCurrentMap; } lastIndexToKeep = indexInCurrentMap; } else if (lastIndexToKeep > 0) { // we hit a remove, copy any keys that are known ready int numEntriesToCopy = lastIndexToKeep - firstIndexToKeep + 1; System.arraycopy( backingArray, getArrayIndexForKey(firstIndexToKeep), newMap.backingArray, getArrayIndexForKey(destinationIndex), numEntriesToCopy * 2); firstIndexToKeep = -1; lastIndexToKeep = -1; destinationIndex += numEntriesToCopy; numEntriesKept += numEntriesToCopy; } } if (lastIndexToKeep > -1) { // at least one key still requires copying int numEntriesToCopy = lastIndexToKeep - firstIndexToKeep + 1; System.arraycopy( backingArray, getArrayIndexForKey(firstIndexToKeep), newMap.backingArray, getArrayIndexForKey(destinationIndex), numEntriesToCopy * 2); numEntriesKept += numEntriesToCopy; } if (numEntriesKept == 0) { return EMPTY_MAP; } newMap.numEntries = numEntriesKept; newMap.updateNumEntriesInArray(); return newMap; } /** * Copies the locally-tracked numEntries into the first array slot. Requires * autoboxing so call should be minimized - for example, once per bulk update * operation. */ private void updateNumEntriesInArray() { backingArray[0] = numEntries; } /** * This version of forEach is defined on the Map interface. */ @Override public void forEach(java.util.function.BiConsumer action) { for (int i = 0; i < numEntries; i++) { // BiConsumer should be able to handle values of any type V. In our case the values are of type String. final String key = (String) backingArray[getArrayIndexForKey(i)]; final String value = (String) backingArray[getArrayIndexForValue(i)]; action.accept(key, value); } } /** * This version of forEach is defined on the ReadOnlyStringMap interface. */ @SuppressWarnings("unchecked") @Override public void forEach(final org.apache.logging.log4j.util.BiConsumer action) { for (int i = 0; i < numEntries; i++) { // BiConsumer should be able to handle values of any type V. In our case the values are of type String. final String key = (String) backingArray[getArrayIndexForKey(i)]; final V value = (V) backingArray[getArrayIndexForValue(i)]; action.accept(key, value); } } @SuppressWarnings("unchecked") public void forEach(final TriConsumer action, final S state) { for (int i = 0; i < numEntries; i++) { // TriConsumer should be able to handle values of any type V. In our case the values are of type String. final String key = (String) backingArray[getArrayIndexForKey(i)]; final V value = (V) backingArray[getArrayIndexForValue(i)]; action.accept(key, value, state); } } @Override public Set> entrySet() { return new UnmodifiableEntrySet(); } /** * Scans the array to find a matching key. Linear-time. */ @Override public String get(Object key) { return getValue((String) key); } @SuppressWarnings("unchecked") @Override public V getValue(String key) { if (numEntries == 0) { return null; } int hashCode = key.hashCode(); for (int i = 0; i < numEntries; i++) { if (backingArray[getArrayIndexForKey(i)].hashCode() == hashCode && backingArray[getArrayIndexForKey(i)].equals(key)) { return (V) backingArray[getArrayIndexForValue(i)]; } } return null; } /** * Find an existing entry (if any) and overwrites the value, if found * * @param key * @param value * @return */ private void addOrOverwriteKey(String key, String value) { int keyHashCode = key.hashCode(); for (int i = 0; i < numEntries; i++) { if (backingArray[getArrayIndexForKey(i)].hashCode() == keyHashCode && backingArray[getArrayIndexForKey(i)].equals(key)) { // found a match, overwrite then return backingArray[getArrayIndexForValue(i)] = value; return; } } // no match found, add to the end add(key, value); } @Override public String put(String key, String value) { throw new UnsupportedOperationException("put() is not supported, use copyAndPut instead"); } @Override public void putAll(Map m) { throw new UnsupportedOperationException("putAll() is not supported, use copyAndPutAll instead"); } @Override public String remove(Object key) { throw new UnsupportedOperationException("remove() is not supported, use copyAndRemove instead"); } @Override public int size() { return numEntries; } @Override public Map toMap() { return this; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy