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

org.apache.commons.collections4.map.AbstractLinkedMap Maven / Gradle / Ivy

There is a newer version: 5.0.70
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.commons.collections4.map;

import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

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.
 * 

* This class implements all the features necessary for a subclass linked * hash-based map. Key-value entries are stored in instances of the * 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. *

* Overridable methods are provided to change the default hashing behaviour, and * to change how entries are added to and removed from the map. Hopefully, all you * need for unusual subclasses is here. *

* This implementation maintains order by original insertion, but subclasses * may work differently. The OrderedMap interface is implemented * to provide access to bidirectional iteration and extra convenience methods. *

* The orderedMapIterator() method provides direct access to a * bidirectional iterator. The iterators from the other views can also be cast * to OrderedIterator if required. *

* All the available iterators can be reset back to the start by casting to * ResettableIterator and calling reset(). *

* The implementation is also designed to be subclassed, with lots of useful * methods exposed. * * @param the type of the keys in this map * @param the type of the values in this map * @since 3.0 */ public abstract class AbstractLinkedMap extends AbstractHashedMap implements OrderedMap { /** Header in the linked list */ transient LinkEntry header; /** * Constructor only used in deserialization, do not use otherwise. */ protected AbstractLinkedMap() { super(); } /** * 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); } /** * 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 copying elements from another map. * * @param map the map to copy * @throws NullPointerException if the map is null */ protected AbstractLinkedMap(final Map map) { super(map); } /** * Initialise this subclass during construction. *

* NOTE: As from v3.2 this method calls * {@link #createEntry(HashEntry, int, Object, Object)} to create * the map entry object. */ @Override protected void init() { header = createEntry(null, -1, null, null); 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 entry = header.after; entry != header; entry = entry.after) { if (entry.getValue() == null) { return true; } } } else { for (LinkEntry entry = header.after; entry != header; entry = entry.after) { if (isEqualValue(value, entry.getValue())) { return true; } } } return false; } /** * 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; } //----------------------------------------------------------------------- /** * 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 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(); } /** * 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 entry = getEntry(key); return entry == null || entry.after == header ? null : entry.after.getKey(); } @Override protected LinkEntry getEntry(final Object key) { return (LinkEntry) super.getEntry(key); } /** * 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 entry = getEntry(key); return entry == null || entry.before == header ? null : entry.before.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 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 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; } /** * Adds an entry into this map, maintaining insertion order. *

* This implementation adds the entry to the data storage table and * to the end of the linked list. * * @param entry the entry to add * @param hashIndex the index into the data array to store at */ @Override protected void addEntry(final HashEntry entry, final int hashIndex) { final LinkEntry link = (LinkEntry) entry; link.after = header; link.before = header.before; header.before.after = link; header.before = link; data[hashIndex] = link; } /** * Creates an entry to store the data. *

* This implementation creates a new LinkEntry instance. * * @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 createEntry(final HashEntry next, final int hashCode, final K key, final V value) { return new LinkEntry<>(next, hashCode, convertKey(key), value); } /** * Removes an entry from the map and the linked list. *

* This implementation removes the entry from the linked list chain, then * calls the superclass implementation. * * @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 entry, final int hashIndex, final HashEntry previous) { final LinkEntry link = (LinkEntry) entry; link.before.after = link.after; link.after.before = link.before; link.after = null; link.before = null; super.removeEntry(entry, hashIndex, previous); } //----------------------------------------------------------------------- /** * Gets the before field from a LinkEntry. * Used in subclasses that have no visibility of the field. * * @param entry the entry to query, must not be null * @return the before field of the entry * @throws NullPointerException if the entry is null * @since 3.1 */ protected LinkEntry entryBefore(final LinkEntry entry) { return entry.before; } /** * Gets the after field from a LinkEntry. * Used in subclasses that have no visibility of the field. * * @param entry the entry to query, must not be null * @return the after field of the entry * @throws NullPointerException if the entry is null * @since 3.1 */ protected LinkEntry entryAfter(final LinkEntry entry) { return entry.after; } //----------------------------------------------------------------------- /** * {@inheritDoc} */ @Override public OrderedMapIterator mapIterator() { if (size == 0) { return EmptyOrderedMapIterator.emptyOrderedMapIterator(); } return new LinkMapIterator<>(this); } /** * MapIterator implementation. */ protected static class LinkMapIterator extends LinkIterator implements OrderedMapIterator, ResettableIterator { protected LinkMapIterator(final AbstractLinkedMap parent) { super(parent); } @Override public K next() { return super.nextEntry().getKey(); } @Override public K previous() { return super.previousEntry().getKey(); } @Override public K getKey() { final LinkEntry current = currentEntry(); if (current == null) { throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); } return current.getKey(); } @Override public V getValue() { final LinkEntry current = currentEntry(); if (current == null) { throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); } return current.getValue(); } @Override public V setValue(final V value) { final LinkEntry current = currentEntry(); if (current == null) { throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); } return current.setValue(value); } } //----------------------------------------------------------------------- /** * Creates an entry set iterator. * Subclasses can override this to return iterators with different properties. * * @return the entrySet iterator */ @Override protected Iterator> createEntrySetIterator() { if (size() == 0) { return EmptyOrderedIterator.>emptyOrderedIterator(); } return new EntrySetIterator<>(this); } /** * EntrySet iterator. */ protected static class EntrySetIterator extends LinkIterator implements OrderedIterator>, ResettableIterator> { protected EntrySetIterator(final AbstractLinkedMap parent) { super(parent); } @Override public Map.Entry next() { return super.nextEntry(); } @Override public Map.Entry previous() { return super.previousEntry(); } } //----------------------------------------------------------------------- /** * Creates a key set iterator. * Subclasses can override this to return iterators with different properties. * * @return the keySet iterator */ @Override protected Iterator createKeySetIterator() { if (size() == 0) { return EmptyOrderedIterator.emptyOrderedIterator(); } return new KeySetIterator<>(this); } /** * KeySet iterator. */ protected static class KeySetIterator extends LinkIterator implements OrderedIterator, ResettableIterator { @SuppressWarnings("unchecked") protected KeySetIterator(final AbstractLinkedMap parent) { super((AbstractLinkedMap) parent); } @Override public K next() { return super.nextEntry().getKey(); } @Override public K previous() { return super.previousEntry().getKey(); } } //----------------------------------------------------------------------- /** * Creates a values iterator. * Subclasses can override this to return iterators with different properties. * * @return the values iterator */ @Override protected Iterator createValuesIterator() { if (size() == 0) { return EmptyOrderedIterator.emptyOrderedIterator(); } return new ValuesIterator<>(this); } /** * Values iterator. */ protected static class ValuesIterator extends LinkIterator implements OrderedIterator, ResettableIterator { @SuppressWarnings("unchecked") protected ValuesIterator(final AbstractLinkedMap parent) { super((AbstractLinkedMap) parent); } @Override public V next() { return super.nextEntry().getValue(); } @Override public V previous() { return super.previousEntry().getValue(); } } //----------------------------------------------------------------------- /** * LinkEntry that stores the data. *

* If you subclass AbstractLinkedMap but not LinkEntry * then you will not be able to access the protected fields. * The entryXxx() methods on AbstractLinkedMap exist * to provide the necessary access. */ protected static class LinkEntry extends HashEntry { /** The entry before this one in the order */ protected LinkEntry before; /** The entry after this one in the order */ protected LinkEntry 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 next, final int hashCode, final Object key, final V value) { super(next, hashCode, key, value); } } /** * Base Iterator that iterates in link order. */ protected static abstract class LinkIterator { /** The parent map */ protected final AbstractLinkedMap parent; /** The current (last returned) entry */ protected LinkEntry last; /** The next entry */ protected LinkEntry next; /** The modification count expected */ protected int expectedModCount; protected LinkIterator(final AbstractLinkedMap parent) { super(); this.parent = parent; this.next = parent.header.after; this.expectedModCount = parent.modCount; } public boolean hasNext() { return next != parent.header; } public boolean hasPrevious() { return next.before != parent.header; } protected LinkEntry nextEntry() { if (parent.modCount != expectedModCount) { throw new ConcurrentModificationException(); } if (next == parent.header) { throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); } last = next; next = next.after; return last; } protected LinkEntry previousEntry() { if (parent.modCount != expectedModCount) { throw new ConcurrentModificationException(); } final LinkEntry previous = next.before; if (previous == parent.header) { throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY); } next = previous; last = previous; return last; } protected LinkEntry currentEntry() { return last; } public void remove() { if (last == null) { throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); } if (parent.modCount != expectedModCount) { throw new ConcurrentModificationException(); } parent.remove(last.getKey()); last = null; expectedModCount = parent.modCount; } public void reset() { last = null; next = parent.header.after; } @Override public String toString() { if (last != null) { return "Iterator[" + last.getKey() + "=" + last.getValue() + "]"; } return "Iterator[]"; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy