AbstractLinkedMap.java
- /*
- * 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.commons.collections4.map;
- import java.util.ConcurrentModificationException;
- import java.util.Iterator;
- import java.util.Map;
- import java.util.NoSuchElementException;
- import java.util.Objects;
- import org.apache.commons.collections4.OrderedIterator;
- import org.apache.commons.collections4.OrderedMap;
- import org.apache.commons.collections4.OrderedMapIterator;
- import org.apache.commons.collections4.ResettableIterator;
- import org.apache.commons.collections4.iterators.EmptyOrderedIterator;
- import org.apache.commons.collections4.iterators.EmptyOrderedMapIterator;
- /**
- * An abstract implementation of a hash-based map that links entries to create an
- * ordered map and which provides numerous points for subclasses to override.
- * <p>
- * This class implements all the features necessary for a subclass linked
- * hash-based map. Key-value entries are stored in instances of the
- * {@code LinkEntry} class which can be overridden and replaced.
- * The iterators can similarly be replaced, without the need to replace the KeySet,
- * EntrySet and Values view classes.
- * </p>
- * <p>
- * Overridable methods are provided to change the default hashing behavior, and
- * to change how entries are added to and removed from the map. Hopefully, all you
- * need for unusual subclasses is here.
- * </p>
- * <p>
- * This implementation maintains order by original insertion, but subclasses
- * may work differently. The {@code OrderedMap} interface is implemented
- * to provide access to bidirectional iteration and extra convenience methods.
- * </p>
- * <p>
- * The {@code orderedMapIterator()} method provides direct access to a
- * bidirectional iterator. The iterators from the other views can also be cast
- * to {@code OrderedIterator} if required.
- * </p>
- * <p>
- * All the available iterators can be reset back to the start by casting to
- * {@code ResettableIterator} and calling {@code reset()}.
- * </p>
- * <p>
- * The implementation is also designed to be subclassed, with lots of useful
- * methods exposed.
- * </p>
- *
- * @param <K> the type of the keys in this map
- * @param <V> the type of the values in this map
- * @since 3.0
- */
- public abstract class AbstractLinkedMap<K, V> extends AbstractHashedMap<K, V> implements OrderedMap<K, V> {
- /**
- * EntrySet iterator.
- *
- * @param <K> the key type.
- * @param <V> the value type.
- */
- protected static class EntrySetIterator<K, V> extends LinkIterator<K, V> implements
- OrderedIterator<Map.Entry<K, V>>, ResettableIterator<Map.Entry<K, V>> {
- /**
- * Constructs a new instance.
- *
- * @param parent The parent AbstractLinkedMap.
- */
- protected EntrySetIterator(final AbstractLinkedMap<K, V> parent) {
- super(parent);
- }
- @Override
- public Map.Entry<K, V> next() {
- return super.nextEntry();
- }
- @Override
- public Map.Entry<K, V> previous() {
- return super.previousEntry();
- }
- }
- /**
- * KeySet iterator.
- *
- * @param <K> the key type.
- */
- protected static class KeySetIterator<K> extends LinkIterator<K, Object> implements
- OrderedIterator<K>, ResettableIterator<K> {
- /**
- * Constructs a new instance.
- *
- * @param parent The parent AbstractLinkedMap.
- */
- @SuppressWarnings("unchecked")
- protected KeySetIterator(final AbstractLinkedMap<K, ?> parent) {
- super((AbstractLinkedMap<K, Object>) parent);
- }
- @Override
- public K next() {
- return super.nextEntry().getKey();
- }
- @Override
- public K previous() {
- return super.previousEntry().getKey();
- }
- }
- /**
- * LinkEntry that stores the data.
- * <p>
- * If you subclass {@code AbstractLinkedMap} but not {@code LinkEntry}
- * then you will not be able to access the protected fields.
- * The {@code entryXxx()} methods on {@code AbstractLinkedMap} exist
- * to provide the necessary access.
- * </p>
- *
- * @param <K> the key type.
- * @param <V> the value type.
- */
- protected static class LinkEntry<K, V> extends HashEntry<K, V> {
- /** The entry before this one in the order */
- protected LinkEntry<K, V> before;
- /** The entry after this one in the order */
- protected LinkEntry<K, V> after;
- /**
- * Constructs a new entry.
- *
- * @param next the next entry in the hash bucket sequence
- * @param hashCode the hash code
- * @param key the key
- * @param value the value
- */
- protected LinkEntry(final HashEntry<K, V> next, final int hashCode, final Object key, final V value) {
- super(next, hashCode, key, value);
- }
- }
- /**
- * Base Iterator that iterates in link order.
- *
- * @param <K> the key type.
- * @param <V> the value type.
- */
- protected abstract static class LinkIterator<K, V> {
- /** The parent map */
- protected final AbstractLinkedMap<K, V> parent;
- /** The current (last returned) entry */
- protected LinkEntry<K, V> last;
- /** The next entry */
- protected LinkEntry<K, V> next;
- /** The modification count expected */
- protected int expectedModCount;
- /**
- * Constructs a new instance.
- *
- * @param parent The parent AbstractLinkedMap.
- */
- protected LinkIterator(final AbstractLinkedMap<K, V> parent) {
- this.parent = Objects.requireNonNull(parent);
- this.next = parent.header.after;
- this.expectedModCount = parent.modCount;
- }
- /**
- * Gets the current entry.
- *
- * @return the current entry.
- */
- protected LinkEntry<K, V> currentEntry() {
- return last;
- }
- /**
- * Tests whether there is another entry.
- *
- * @return whether there is another entry.
- */
- public boolean hasNext() {
- return next != parent.header;
- }
- /**
- * Tests whether there is a previous entry.
- *
- * @return whether there is a previous entry.
- */
- public boolean hasPrevious() {
- return next.before != parent.header;
- }
- /**
- * Gets the next entry.
- *
- * @return the next entry.
- */
- protected LinkEntry<K, V> nextEntry() {
- if (parent.modCount != expectedModCount) {
- throw new ConcurrentModificationException();
- }
- if (next == parent.header) {
- throw new NoSuchElementException(NO_NEXT_ENTRY);
- }
- last = next;
- next = next.after;
- return last;
- }
- /**
- * Gets the previous entry.
- *
- * @return the previous entry.
- */
- protected LinkEntry<K, V> previousEntry() {
- if (parent.modCount != expectedModCount) {
- throw new ConcurrentModificationException();
- }
- final LinkEntry<K, V> previous = next.before;
- if (previous == parent.header) {
- throw new NoSuchElementException(NO_PREVIOUS_ENTRY);
- }
- next = previous;
- last = previous;
- return last;
- }
- /**
- * Removes the current entry.
- */
- public void remove() {
- if (last == null) {
- throw new IllegalStateException(REMOVE_INVALID);
- }
- if (parent.modCount != expectedModCount) {
- throw new ConcurrentModificationException();
- }
- parent.remove(last.getKey());
- last = null;
- expectedModCount = parent.modCount;
- }
- /**
- * Resets the state to the end.
- */
- public void reset() {
- last = null;
- next = parent.header.after;
- }
- @Override
- public String toString() {
- if (last != null) {
- return "Iterator[" + last.getKey() + "=" + last.getValue() + "]";
- }
- return "Iterator[]";
- }
- }
- /**
- * MapIterator implementation.
- *
- * @param <K> the key type.
- * @param <V> the value type.
- */
- protected static class LinkMapIterator<K, V> extends LinkIterator<K, V> implements
- OrderedMapIterator<K, V>, ResettableIterator<K> {
- /**
- * Constructs a new instance.
- *
- * @param parent The parent AbstractLinkedMap.
- */
- protected LinkMapIterator(final AbstractLinkedMap<K, V> parent) {
- super(parent);
- }
- @Override
- public K getKey() {
- final LinkEntry<K, V> current = currentEntry();
- if (current == null) {
- throw new IllegalStateException(GETKEY_INVALID);
- }
- return current.getKey();
- }
- @Override
- public V getValue() {
- final LinkEntry<K, V> current = currentEntry();
- if (current == null) {
- throw new IllegalStateException(GETVALUE_INVALID);
- }
- return current.getValue();
- }
- @Override
- public K next() {
- return super.nextEntry().getKey();
- }
- @Override
- public K previous() {
- return super.previousEntry().getKey();
- }
- @Override
- public V setValue(final V value) {
- final LinkEntry<K, V> current = currentEntry();
- if (current == null) {
- throw new IllegalStateException(SETVALUE_INVALID);
- }
- return current.setValue(value);
- }
- }
- /**
- * Values iterator.
- *
- * @param <V> the value type.
- */
- protected static class ValuesIterator<V> extends LinkIterator<Object, V> implements
- OrderedIterator<V>, ResettableIterator<V> {
- /**
- * Constructs a new instance.
- *
- * @param parent The parent AbstractLinkedMap.
- */
- @SuppressWarnings("unchecked")
- protected ValuesIterator(final AbstractLinkedMap<?, V> parent) {
- super((AbstractLinkedMap<Object, V>) parent);
- }
- @Override
- public V next() {
- return super.nextEntry().getValue();
- }
- @Override
- public V previous() {
- return super.previousEntry().getValue();
- }
- }
- /** Header in the linked list */
- transient LinkEntry<K, V> header;
- /**
- * Constructor only used in deserialization, do not use otherwise.
- */
- protected AbstractLinkedMap() {
- }
- /**
- * Constructs a new, empty map with the specified initial capacity.
- *
- * @param initialCapacity the initial capacity
- * @throws IllegalArgumentException if the initial capacity is negative
- */
- protected AbstractLinkedMap(final int initialCapacity) {
- super(initialCapacity);
- }
- /**
- * Constructs a new, empty map with the specified initial capacity and
- * load factor.
- *
- * @param initialCapacity the initial capacity
- * @param loadFactor the load factor
- * @throws IllegalArgumentException if the initial capacity is negative
- * @throws IllegalArgumentException if the load factor is less than zero
- */
- protected AbstractLinkedMap(final int initialCapacity, final float loadFactor) {
- super(initialCapacity, loadFactor);
- }
- /**
- * Constructor which performs no validation on the passed in parameters.
- *
- * @param initialCapacity the initial capacity, must be a power of two
- * @param loadFactor the load factor, must be > 0.0f and generally < 1.0f
- * @param threshold the threshold, must be sensible
- */
- protected AbstractLinkedMap(final int initialCapacity, final float loadFactor, final int threshold) {
- super(initialCapacity, loadFactor, threshold);
- }
- /**
- * Constructor copying elements from another map.
- *
- * @param map the map to copy
- * @throws NullPointerException if the map is null
- */
- protected AbstractLinkedMap(final Map<? extends K, ? extends V> map) {
- super(map);
- }
- /**
- * Adds an entry into this map, maintaining insertion order.
- * <p>
- * This implementation adds the entry to the data storage table and
- * to the end of the linked list.
- * </p>
- *
- * @param entry the entry to add
- * @param hashIndex the index into the data array to store at
- */
- @Override
- protected void addEntry(final HashEntry<K, V> entry, final int hashIndex) {
- final LinkEntry<K, V> link = (LinkEntry<K, V>) entry;
- link.after = header;
- link.before = header.before;
- header.before.after = link;
- header.before = link;
- data[hashIndex] = link;
- }
- /**
- * Clears the map, resetting the size to zero and nullifying references
- * to avoid garbage collection issues.
- */
- @Override
- public void clear() {
- // override to reset the linked list
- super.clear();
- header.before = header.after = header;
- }
- /**
- * Checks whether the map contains the specified value.
- *
- * @param value the value to search for
- * @return true if the map contains the value
- */
- @Override
- public boolean containsValue(final Object value) {
- // override uses faster iterator
- if (value == null) {
- for (LinkEntry<K, V> entry = header.after; entry != header; entry = entry.after) {
- if (entry.getValue() == null) {
- return true;
- }
- }
- } else {
- for (LinkEntry<K, V> entry = header.after; entry != header; entry = entry.after) {
- if (isEqualValue(value, entry.getValue())) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * Creates an entry to store the data.
- * <p>
- * This implementation creates a new LinkEntry instance.
- * </p>
- *
- * @param next the next entry in sequence
- * @param hashCode the hash code to use
- * @param key the key to store
- * @param value the value to store
- * @return the newly created entry
- */
- @Override
- protected LinkEntry<K, V> createEntry(final HashEntry<K, V> next, final int hashCode, final K key, final V value) {
- return new LinkEntry<>(next, hashCode, convertKey(key), value);
- }
- /**
- * Creates an entry set iterator.
- * Subclasses can override this to return iterators with different properties.
- *
- * @return the entrySet iterator
- */
- @Override
- protected Iterator<Map.Entry<K, V>> createEntrySetIterator() {
- if (isEmpty()) {
- return EmptyOrderedIterator.<Map.Entry<K, V>>emptyOrderedIterator();
- }
- return new EntrySetIterator<>(this);
- }
- /**
- * Creates a key set iterator.
- * Subclasses can override this to return iterators with different properties.
- *
- * @return the keySet iterator
- */
- @Override
- protected Iterator<K> createKeySetIterator() {
- if (isEmpty()) {
- return EmptyOrderedIterator.<K>emptyOrderedIterator();
- }
- return new KeySetIterator<>(this);
- }
- /**
- * Creates a values iterator.
- * Subclasses can override this to return iterators with different properties.
- *
- * @return the values iterator
- */
- @Override
- protected Iterator<V> createValuesIterator() {
- if (isEmpty()) {
- return EmptyOrderedIterator.<V>emptyOrderedIterator();
- }
- return new ValuesIterator<>(this);
- }
- /**
- * Gets the {@code after} field from a {@code LinkEntry}.
- * Used in subclasses that have no visibility of the field.
- *
- * @param entry the entry to query, must not be null
- * @return the {@code after} field of the entry
- * @throws NullPointerException if the entry is null
- * @since 3.1
- */
- protected LinkEntry<K, V> entryAfter(final LinkEntry<K, V> entry) {
- return entry.after;
- }
- /**
- * Gets the {@code before} field from a {@code LinkEntry}.
- * Used in subclasses that have no visibility of the field.
- *
- * @param entry the entry to query, must not be null
- * @return the {@code before} field of the entry
- * @throws NullPointerException if the entry is null
- * @since 3.1
- */
- protected LinkEntry<K, V> entryBefore(final LinkEntry<K, V> entry) {
- return entry.before;
- }
- /**
- * Gets the first key in the map, which is the first inserted.
- *
- * @return the eldest key
- */
- @Override
- public K firstKey() {
- if (size == 0) {
- throw new NoSuchElementException("Map is empty");
- }
- return header.after.getKey();
- }
- /**
- * Gets the key at the specified index.
- *
- * @param index the index to retrieve
- * @return the key at the specified index
- * @throws IndexOutOfBoundsException if the index is invalid
- */
- protected LinkEntry<K, V> getEntry(final int index) {
- if (index < 0) {
- throw new IndexOutOfBoundsException("Index " + index + " is less than zero");
- }
- if (index >= size) {
- throw new IndexOutOfBoundsException("Index " + index + " is invalid for size " + size);
- }
- LinkEntry<K, V> entry;
- if (index < size / 2) {
- // Search forwards
- entry = header.after;
- for (int currentIndex = 0; currentIndex < index; currentIndex++) {
- entry = entry.after;
- }
- } else {
- // Search backwards
- entry = header;
- for (int currentIndex = size; currentIndex > index; currentIndex--) {
- entry = entry.before;
- }
- }
- return entry;
- }
- @Override
- protected LinkEntry<K, V> getEntry(final Object key) {
- return (LinkEntry<K, V>) super.getEntry(key);
- }
- /**
- * Initialize this subclass during construction.
- * <p>
- * Note: As from v3.2 this method calls
- * {@link #createEntry(HashEntry, int, Object, Object)} to create
- * the map entry object.
- * </p>
- */
- @Override
- protected void init() {
- header = createEntry(null, -1, null, null);
- header.before = header.after = header;
- }
- /**
- * Gets the last key in the map, which is the most recently inserted.
- *
- * @return the most recently inserted key
- */
- @Override
- public K lastKey() {
- if (size == 0) {
- throw new NoSuchElementException("Map is empty");
- }
- return header.before.getKey();
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public OrderedMapIterator<K, V> mapIterator() {
- if (size == 0) {
- return EmptyOrderedMapIterator.<K, V>emptyOrderedMapIterator();
- }
- return new LinkMapIterator<>(this);
- }
- /**
- * Gets the next key in sequence.
- *
- * @param key the key to get after
- * @return the next key
- */
- @Override
- public K nextKey(final Object key) {
- final LinkEntry<K, V> entry = getEntry(key);
- return entry == null || entry.after == header ? null : entry.after.getKey();
- }
- /**
- * Gets the previous key in sequence.
- *
- * @param key the key to get before
- * @return the previous key
- */
- @Override
- public K previousKey(final Object key) {
- final LinkEntry<K, V> entry = getEntry(key);
- return entry == null || entry.before == header ? null : entry.before.getKey();
- }
- /**
- * Removes an entry from the map and the linked list.
- * <p>
- * This implementation removes the entry from the linked list chain, then
- * calls the superclass implementation.
- * </p>
- *
- * @param entry the entry to remove
- * @param hashIndex the index into the data structure
- * @param previous the previous entry in the chain
- */
- @Override
- protected void removeEntry(final HashEntry<K, V> entry, final int hashIndex, final HashEntry<K, V> previous) {
- final LinkEntry<K, V> link = (LinkEntry<K, V>) entry;
- link.before.after = link.after;
- link.after.before = link.before;
- link.after = null;
- link.before = null;
- super.removeEntry(entry, hashIndex, previous);
- }
- }