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

org.plumelib.util.ArrayMap Maven / Gradle / Ivy

There is a newer version: 1.10.0
Show newest version
package org.plumelib.util;

import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import org.checkerframework.checker.index.qual.GTENegativeOne;
import org.checkerframework.checker.index.qual.IndexOrHigh;
import org.checkerframework.checker.index.qual.LTEqLengthOf;
import org.checkerframework.checker.index.qual.LessThan;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.SameLen;
import org.checkerframework.checker.lock.qual.GuardSatisfied;
import org.checkerframework.checker.nullness.qual.EnsuresKeyFor;
import org.checkerframework.checker.nullness.qual.KeyFor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import org.checkerframework.checker.signedness.qual.PolySigned;
import org.checkerframework.checker.signedness.qual.UnknownSignedness;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;

/**
 * A map backed by two arrays. It permits null keys and values, and its iterator has deterministic
 * ordering.
 *
 * 

Compared to a HashMap or LinkedHashMap: For very small maps, this uses much less space, has * comparable performance, and (like a LinkedHashMap) is deterministic, with elements returned in * the order their keys were inserted. For large maps, this is significantly less performant than * other map implementations. * *

Compared to a TreeMap: This uses somewhat less space, and it does not require defining a * comparator. This isn't sorted but does have deterministic ordering. For large maps, this is * significantly less performant than other map implementations. * *

A number of other ArrayMap implementations exist, including * *

    *
  • android.util.ArrayMap *
  • com.google.api.client.util.ArrayMap *
  • it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap *
  • oracle.dss.util.ArrayMap *
  • org.apache.myfaces.trinidad.util.ArrayMap *
* * All of those use the Apache License, version 2.0, whereas this implementation is licensed under * the more libral MIT License. In addition, some of those implementations forbid nulls or * nondeterministically reorder the contents, and others don't specify their behavior regarding * nulls and ordering. * * @param the type of keys maintained by this map * @param the type of mapped values */ @SuppressWarnings({ "index", // TODO "keyfor", // https://tinyurl.com/cfissue/4558 "lock", // not yet annotated for the Lock Checker "nullness" // temporary; nullness is tricky because of null-padded arrays }) public class ArrayMap extends AbstractMap { // An alternate internal representation would be a list of Map.Entry objects (e.g., // AbstractMap.SimpleEntry) instead of two arrays for lists and values. That is a bad idea // because it both uses more memory and makes some operations more expensive. /** The keys. Null if capacity=0. */ private @Nullable K @SameLen("values") [] keys; /** The values. Null if capacity=0. */ private @Nullable V @SameLen("keys") [] values; /** The number of used mappings in the representation of this. */ private @NonNegative @LessThan("keys.length + 1") @IndexOrHigh({"keys", "values"}) int size = 0; // An alternate representation would also store the hash code of each key, for quicker querying. /** * The number of times this map's size has been modified by adding or removing an element * (changing the value associated with a key does not count as a change). This field is used to * make view iterators fail-fast. */ transient int sizeModificationCount = 0; // Constructors /** * Constructs an empty {@code ArrayMap} with the specified initial capacity. * * @param initialCapacity the initial capacity * @throws IllegalArgumentException if the initial capacity is negative */ @SuppressWarnings({ "unchecked", // generic array cast "samelen:assignment", // initialization }) @SideEffectFree public ArrayMap(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity == 0) { this.keys = null; this.values = null; } else { this.keys = (K[]) new Object[initialCapacity]; this.values = (V[]) new Object[initialCapacity]; } } /** Constructs an empty {@code ArrayMap} with the default initial capacity. */ @SideEffectFree public ArrayMap() { this(4); } /** * Private constructor. Installs the given objects in this as its representation, without making * defensive copies. * * @param keys the keys * @param values the values * @param size the number of used items in the arrays; may be less than their lengths */ @SuppressWarnings("samelen:assignment") // initialization @SideEffectFree private ArrayMap( K @SameLen("values") [] keys, V @SameLen("keys") [] values, @LTEqLengthOf({"keys", "values"}) int size) { this.keys = keys; this.values = values; this.size = size; } /** * Constructs a new {@code ArrayMap} with the same mappings as the given {@code Map}. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the given map is null */ @SuppressWarnings({ "allcheckers:purity", // initializes `this` "lock:method.guarantee.violated", // initializes `this` "nullness:method.invocation", // inference failure; // https://github.com/typetools/checker-framework/issues/979 ? }) @SideEffectFree public ArrayMap(Map m) { this(m.size()); putAll(m); } // Factory (constructor) methods /** * Returns a new ArrayMap or HashMap with the given capacity. Uses an ArrayMap if the capacity is * small, and a HashMap otherwise. * * @param the type of the keys * @param the type of the values * @param capacity the expected maximum number of elements in the set * @return a new ArrayMap or HashMap with the given capacity */ public static Map newArrayMapOrHashMap(int capacity) { if (capacity <= 4) { return new ArrayMap<>(capacity); } else { return new HashMap<>(CollectionsPlume.mapCapacity(capacity)); } } /** * Returns a new ArrayMap or HashMap with the given elements. Uses an ArrayMap if the capacity is * small, and a HashMap otherwise. * * @param the type of the keys * @param the type of the values * @param m the elements to put in the returned set * @return a new ArrayMap or HashMap with the given elements */ public static Map newArrayMapOrHashMap(Map m) { if (m.size() <= 4) { return new ArrayMap<>(m); } else { return new HashMap<>(m); } } /** * Returns a new ArrayMap or LinkedHashMap with the given capacity. Uses an ArrayMap if the * capacity is small, and a LinkedHashMap otherwise. * * @param the type of the keys * @param the type of the values * @param capacity the expected maximum number of elements in the set * @return a new ArrayMap or LinkedHashMap with the given capacity */ public static Map newArrayMapOrLinkedHashMap(int capacity) { if (capacity <= 4) { return new ArrayMap<>(capacity); } else { return new LinkedHashMap<>(CollectionsPlume.mapCapacity(capacity)); } } /** * Returns a new ArrayMap or LinkedHashMap with the given elements. Uses an ArrayMap if the * capacity is small, and a LinkedHashMap otherwise. * * @param the type of the keys * @param the type of the values * @param m the elements to put in the returned set * @return a new ArrayMap or LinkedHashMap with the given elements */ public static Map newArrayMapOrLinkedHashMap(Map m) { if (m.size() <= 4) { return new ArrayMap<>(m); } else { return new LinkedHashMap<>(m); } } // Private helper functions /** * Adds the (key, value) mapping to this. * * @param index the index of {@code key} in {@code keys}. If -1, add a new mapping. Otherwise, * replace the mapping at {@code index}. * @param key the key * @param value the value */ @SuppressWarnings({ "InvalidParam", // Error Prone stupidly warns about field `keys` "keyfor:contracts.postcondition" // insertion in keys array suffices }) @EnsuresKeyFor(value = "#2", map = "this") private void put(@GTENegativeOne int index, K key, V value) { if (index == -1) { // Add a new mapping. if ((size == 0 && keys == null) || (size == keys.length)) { grow(); } keys[size] = key; values[size] = value; size++; sizeModificationCount++; } else { // Replace an existing mapping. values[index] = value; } } /** Increases the capacity of the arrays. */ @SuppressWarnings({"unchecked"}) // generic array cast private void grow() { if (keys == null) { this.keys = (K[]) new Object[4]; this.values = (V[]) new Object[4]; } else { int newCapacity = 2 * keys.length; keys = Arrays.copyOf(keys, newCapacity); values = Arrays.copyOf(values, newCapacity); } } /** * Remove the mapping at the given index. Does nothing if index is -1. * * @param index the index of the mapping to remove * @return true if this map was modified */ private boolean removeIndex(@GTENegativeOne int index) { if (index == -1) { return false; } System.arraycopy(keys, index + 1, keys, index, size - index - 1); System.arraycopy(values, index + 1, values, index, size - index - 1); size--; sizeModificationCount++; return true; } // Query Operations @Pure @Override public @NonNegative int size() { return size; } @Pure @Override public boolean isEmpty() { return size == 0; } /** * Returns the index of the given key, or -1 if it does not appear. Uses {@code Objects.equals} * for comparison. * * @param key a key to find * @return the index of the given key, or -1 if it does not appear */ @Pure private int indexOfKey(@GuardSatisfied @Nullable @UnknownSignedness Object key) { if (keys == null) { return -1; } for (int i = 0; i < size; i++) { if (Objects.equals(key, keys[i])) { return i; } } return -1; } /** * Returns the index of the given value, or -1 if it does not appear. Uses {@code Objects.equals} * for comparison. * * @param value a value to find * @return the index of the given value, or -1 if it does not appear */ @Pure private int indexOfValue(@GuardSatisfied @Nullable @UnknownSignedness Object value) { if (keys == null) { return -1; } for (int i = 0; i < size; i++) { if (Objects.equals(value, values[i])) { return i; } } return -1; } @Pure @Override @SuppressWarnings("keyfor:contracts.conditional.postcondition") // delegate test to `keys` field public boolean containsKey(@GuardSatisfied @Nullable @UnknownSignedness Object key) { return indexOfKey(key) != -1; } @Pure @Override public boolean containsValue(@GuardSatisfied @Nullable @UnknownSignedness Object value) { return indexOfValue(value) != -1; } /** * Returns true if this map contains the given mapping. * * @param key the key * @param value the value * @return true if this map contains the given mapping */ @Pure private boolean containsEntry( @GuardSatisfied @Nullable @UnknownSignedness Object key, @GuardSatisfied @Nullable @UnknownSignedness Object value) { int index = indexOfKey(key); return index != -1 && Objects.equals(value, values[index]); } @Pure @Override public @Nullable V get(@GuardSatisfied @Nullable @UnknownSignedness Object key) { int index = indexOfKey(key); return getOrNull(index); } /** * Returns the value at the given index, or null if the index is -1. * * @param index the index * @return the value at the given index, or null if the index is -1 */ @Pure private @Nullable V getOrNull(@GTENegativeOne int index) { return (index == -1) ? null : values[index]; } // Modification Operations @Override public @Nullable V put(K key, V value) { int index = indexOfKey(key); V currentValue = getOrNull(index); put(index, key, value); return currentValue; } @Override public @Nullable V remove(@GuardSatisfied @Nullable @UnknownSignedness Object key) { int index = indexOfKey(key); // cannot use removeIndex because it has the wrong return type if (index == -1) { return null; } V currentValue = values[index]; removeIndex(index); return currentValue; } // Bulk Operations @Override public void putAll(Map m) { if (m.isEmpty()) { return; } for (Map.Entry entry : m.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public void clear() { if (size != 0) { size = 0; sizeModificationCount++; } } // Views /** A view of the keys. */ @MonotonicNonNull Set<@KeyFor("this") K> keySet = null; // Behavior is undefined if the map is changed while the sets are being iterated through, so these // implementations can assume there are no concurrent side effects. @Pure @SuppressWarnings("allcheckers:purity") // update cache @Override public Set<@KeyFor("this") K> keySet() { if (keySet == null) { keySet = new KeySet(); } return keySet; } /** Represents a view of the keys. */ final class KeySet extends AbstractSet<@KeyFor("this") K> { /** Creates a new KeySet. */ public KeySet() {} @Pure @Override public final @NonNegative int size() { return ArrayMap.this.size(); } @Override public final void clear() { ArrayMap.this.clear(); } @Override public final Iterator<@KeyFor("this") K> iterator() { return new KeyIterator(); } @Pure @Override public final boolean contains(@GuardSatisfied @Nullable @UnknownSignedness Object o) { return containsKey(o); } @Override public final boolean remove(@GuardSatisfied @Nullable @UnknownSignedness Object o) { int index = indexOfKey(o); return removeIndex(index); } @SuppressWarnings({"nullness:return"}) // array isn't padded with null, before index `size` @SideEffectFree @Override public @PolySigned Object[] toArray() { // toArray must return a new array because clients are permitted to modify it. return (@PolySigned Object[]) Arrays.copyOf(keys, size); } @SuppressWarnings({ "unchecked", // generic array cast "nullness" // Nullness Checker special-cases toArray }) @SideEffectFree @Override public @Nullable T[] toArray(@PolyNull T[] a) { T[] result; if (a.length >= size) { result = a; } else { result = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); } System.arraycopy(keys, 0, result, 0, size); return result; } @Override public final void forEach(Consumer action) { if (keys == null) { return; } int oldSizeModificationCount = sizeModificationCount; for (int i = 0; i < size; i++) { K key = keys[i]; action.accept(key); } if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } } } /** The view of the values. */ @MonotonicNonNull Collection valuesCollection = null; @Pure @SuppressWarnings("allcheckers:purity") @Override public Collection values() { if (valuesCollection == null) { valuesCollection = new Values(); } return valuesCollection; } /** Represents a view of the values. */ final class Values extends AbstractCollection { /** Creates a new Values. */ public Values() {} @Pure @Override public final @NonNegative int size() { return ArrayMap.this.size(); } @Override public final void clear() { ArrayMap.this.clear(); } @Override public final Iterator iterator() { return new ValueIterator(); } @Pure @Override public final boolean contains(@GuardSatisfied @Nullable @UnknownSignedness Object o) { return containsValue(o); } @SuppressWarnings({"nullness:override.return"}) // polymorphism problem @SideEffectFree @Override public @Nullable @PolySigned Object[] toArray() { // toArray must return a new array because clients are permitted to modify it. return (@Nullable @PolySigned Object[]) Arrays.copyOf(values, size); } @SuppressWarnings({ "unchecked", // generic array cast "nullness" // Nullness Checker special-cases toArray }) @SideEffectFree @Override public @Nullable T[] toArray(@PolyNull T[] a) { T[] result; if (a.length >= size) { result = a; } else { result = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); } System.arraycopy(values, 0, result, 0, size); return result; } @Override public final void forEach(Consumer action) { if (keys == null) { return; } int oldSizeModificationCount = sizeModificationCount; for (int i = 0; i < size; i++) { action.accept(values[i]); } if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } } } /** The view of the entries. */ @MonotonicNonNull Set> entrySet = null; @SuppressWarnings("allcheckers:purity") @Pure @Override public Set> entrySet() { if (entrySet == null) { entrySet = new EntrySet(); } return entrySet; } /** Represents a view of the entries. */ final class EntrySet extends AbstractSet> { /** Creates a new EntrySet. */ public EntrySet() {} @Pure @Override public final @NonNegative int size() { return ArrayMap.this.size(); } @Override public final void clear() { ArrayMap.this.clear(); } @Override public final Iterator> iterator() { return new EntryIterator(); } @Pure @Override public final boolean contains(@GuardSatisfied @Nullable @UnknownSignedness Object o) { if (!(o instanceof Map.Entry)) { return false; } Map.Entry e = (Map.Entry) o; Object key = e.getKey(); Object value = e.getValue(); return containsEntry(key, value); } @Override public final boolean remove(@GuardSatisfied @Nullable @UnknownSignedness Object o) { if (o instanceof Map.Entry) { Map.Entry e = (Map.Entry) o; Object key = e.getKey(); Object value = e.getValue(); return ArrayMap.this.remove(key, value); } return false; } // toArray() and toArray(T[] a) are inherited. @SuppressWarnings({ "interning:argument", // TODO: investigate later "signature:argument", // TODO: investigate later }) @Override public final void forEach(Consumer> action) { int oldSizeModificationCount = sizeModificationCount; for (int index = 0; index < size(); index++) { action.accept(new Entry(index)); } if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } } } /////////////////////////////////////////////////////////////////////////// // iterators /** An iterator over the ArrayMap. */ abstract class ArrayMapIterator { /** The first unread index; the index of the next value to return. */ @NonNegative int index; /** True if remove() has been called since the last call to next(). */ boolean removed; /** The modification count when the iterator is created, for fail-fast. */ int initialSizeModificationCount; /** Creates a new ArrayMapIterator. */ @SideEffectFree ArrayMapIterator() { index = 0; removed = true; // can't remove until next() has been called initialSizeModificationCount = sizeModificationCount; } /** * Returns true if this has another element. * * @return true if this has another element */ @Pure public final boolean hasNext() { return index < size(); } /** Removes the previously-returned element. */ public final void remove() { if (removed) { throw new IllegalStateException( "Called remove() on ArrayMapIterator without calling next() first."); } if (initialSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } // Remove the previously returned element, so use index-1. @SuppressWarnings("lowerbound:assignment") // removed==false, so index>0. @NonNegative int newIndex = index - 1; index = newIndex; ArrayMap.this.removeIndex(index); initialSizeModificationCount = sizeModificationCount; removed = true; } } /** An iterator over the keys. */ final class KeyIterator extends ArrayMapIterator implements Iterator<@KeyFor("this") K> { /** Creates a new KeyIterator. */ @SideEffectFree KeyIterator() {} @Override public final @KeyFor("ArrayMap.this") K next() { if (!hasNext()) { throw new NoSuchElementException(); } removed = false; return keys[index++]; } } /** An iterator over the values. */ final class ValueIterator extends ArrayMapIterator implements Iterator { /** Creates a new ValueIterator. */ @SideEffectFree ValueIterator() {} @Override public final V next() { if (!hasNext()) { throw new NoSuchElementException(); } removed = false; return values[index++]; } } /** An iterator over the entries. */ final class EntryIterator extends ArrayMapIterator implements Iterator> { /** Creates a new EntryIterator. */ @SideEffectFree EntryIterator() {} @Override public final Map.Entry next() { if (!hasNext()) { throw new NoSuchElementException(); } removed = false; return new Entry(index++); } } // An alternate representation would be a triple of index, key, and value. // * That would make Entry objects a bit larger (more allocation would be necessary, though the // same *number* of objects), and would take a tiny bit more computation to create. // * That would make calling getKey and getValue slightly cheaper if they are called multiple // times (a local lookup instead of calling an ArrayList method). // * That would provide less surprising results for some illegal client code. Removing from the // entrySet iterator and then calling any entry method (getKey, getValue, setValue) has // undefined behavior, but clients might try to do it. This could issue // ConcurrentModificationException in that case, by checking that the values in the array still // match those stored in the entry. // Per the specification of Map.Entry, a map entry is meaningful only during the execution of the // iteration over the entry set, and only if the backing map has not been modified except through // calling {@code setValue} on the map entry. /** An entrySet() entry. Tracks the containing list and the index. */ final class Entry implements Map.Entry { /** The index. */ @NonNegative int index; /** * Creates a new map entry. * * @param index the index */ @SuppressWarnings("allcheckers:purity") // initializes `this` @Pure public Entry(@NonNegative int index) { this.index = index; } @Pure @Override public K getKey() { return keys[index]; } @Pure @Override public V getValue() { return values[index]; } @Override public V setValue(V value) { // Do not increment sizeModificationCount. return values[index] = value; } /** * Returns the ArrayMap associated with this entry. * * @return the ArrayMap associated with this entry */ @Pure private ArrayMap theArrayMap() { return ArrayMap.this; } // Per the specification of Map.Entry, this does not compare the underlying list and index. @Pure @Override public boolean equals(@GuardSatisfied @Nullable @UnknownSignedness Object o) { if (this == o) { return true; } if (o instanceof ArrayMap.Entry) { @SuppressWarnings("unchecked") Entry otherEntry = (Entry) o; @SuppressWarnings({"interning:not.interned", "ReferenceEquality"}) // fast special case test boolean result = this.index == otherEntry.index && this.theArrayMap() == otherEntry.theArrayMap(); if (result) { return true; } // else fall through } if (o instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry otherEntry = (Map.Entry) o; return Objects.equals(this.getKey(), otherEntry.getKey()) && Objects.equals(this.getValue(), otherEntry.getValue()); } return false; } @Pure @Override public int hashCode(ArrayMap.Entry this) { return Objects.hash(getKey(), getValue()); } } /////////////////////////////////////////////////////////////////////////// // Comparison and hashing: equals and hashCode are inherited from AbstractSet. // Defaultable methods @SideEffectFree @Override public V getOrDefault(@GuardSatisfied @Nullable @UnknownSignedness Object key, V defaultValue) { int index = indexOfKey(key); if (index != -1) { return values[index]; } else { return defaultValue; } } @Override public void forEach(BiConsumer action) { Objects.requireNonNull(action); if (keys == null) { return; } int oldSizeModificationCount = sizeModificationCount; for (int index = 0; index < size; index++) { K k; V v; try { k = keys[index]; v = values[index]; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(e); } action.accept(k, v); } if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } } @Override public void replaceAll(BiFunction function) { Objects.requireNonNull(function); if (keys == null) { return; } int oldSizeModificationCount = sizeModificationCount; int size = size(); for (int index = 0; index < size; index++) { K k; V v; try { k = keys[index]; v = values[index]; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(e); } v = function.apply(k, v); try { values[index] = v; // Do not increment sizeModificationCount. } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(e); } } if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } } @Override public @Nullable V putIfAbsent(K key, V value) { int index = indexOfKey(key); if (index == -1 || values[index] == null) { put(index, key, value); return null; } else { return values[index]; } } @Override public boolean remove( @GuardSatisfied @Nullable @UnknownSignedness Object key, @GuardSatisfied @Nullable @UnknownSignedness Object value) { int index = indexOfKey(key); if (index == -1) { return false; } Object curValue = values[index]; if (!Objects.equals(curValue, value)) { return false; } removeIndex(index); return true; } @Override public boolean replace(K key, V oldValue, V newValue) { int index = indexOfKey(key); if (index == -1) { return false; } Object curValue = values[index]; if (!Objects.equals(curValue, oldValue)) { return false; } values[index] = newValue; // Do not increment sizeModificationCount. return true; } @Override public @Nullable V replace(K key, V value) { int index = indexOfKey(key); if (index == -1) { return null; } V currentValue = values[index]; values[index] = value; // Do not increment sizeModificationCount. return currentValue; } @Override public @PolyNull V computeIfAbsent( K key, Function mappingFunction) { Objects.requireNonNull(mappingFunction); int index = indexOfKey(key); if (index != -1) { V currentValue = values[index]; if (currentValue != null) { return currentValue; } } // either index == -1, or values[index]==null. int oldSizeModificationCount = sizeModificationCount; V newValue = mappingFunction.apply(key); if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } if (newValue != null) { put(index, key, newValue); } return newValue; } @Override public @PolyNull V computeIfPresent( K key, BiFunction remappingFunction) { Objects.requireNonNull(remappingFunction); int index = indexOfKey(key); if (index == -1) { @SuppressWarnings("nullness:assignment") @PolyNull V result = null; return result; } V oldValue = values[index]; if (oldValue == null) { @SuppressWarnings("nullness:assignment") @PolyNull V result = null; return result; } // index != -1 and values[index] != null. int oldSizeModificationCount = sizeModificationCount; V newValue = remappingFunction.apply(key, oldValue); if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } if (newValue != null) { values[index] = newValue; // Do not increment sizeModificationCount. return newValue; } else { removeIndex(index); return null; } } @Override public @PolyNull V compute( K key, BiFunction remappingFunction) { Objects.requireNonNull(remappingFunction); int index = indexOfKey(key); V oldValue = getOrNull(index); int oldSizeModificationCount = sizeModificationCount; V newValue = remappingFunction.apply(key, oldValue); if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } if (newValue == null) { removeIndex(index); return null; } else { put(index, key, newValue); return newValue; } } @Override public @PolyNull V merge( K key, @NonNull V value, BiFunction remappingFunction) { Objects.requireNonNull(remappingFunction); Objects.requireNonNull(value); int index = indexOfKey(key); V oldValue = getOrNull(index); int oldSizeModificationCount = sizeModificationCount; @PolyNull V newValue; if (oldValue == null) { newValue = value; } else { newValue = remappingFunction.apply(oldValue, value); } if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } if (newValue == null) { removeIndex(index); } else { put(index, key, newValue); } return newValue; } /** * Returns a copy of this. * * @return a copy of this */ @SuppressWarnings("unchecked") @SideEffectFree @Override public ArrayMap clone() { return new ArrayMap(Arrays.copyOf(keys, size), Arrays.copyOf(values, size), size); } /** * Returns the internal representation, printed. * * @return the internal representation, printed */ @SideEffectFree /* package-private */ String repr() { return String.format( "size=%d capacity=%d %s %s", size, (keys == null ? 0 : keys.length), Arrays.toString(keys), Arrays.toString(values)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy