/* * Copyright 2013 Cameron Beccario * * Licensed 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 net.nullschool.collect; import java.util.*; /** * 2013-02-11<p/> * * An implementation of {@link IterableMap} that implements most operations on top of a {@link MapIterator} * (rather than {@link Map#entrySet} as {@link AbstractMap} does).<p/> * * By default, maps that extend this implementation are unmodifiable if the supplied {@link MapIterator} * implementation does not support {@link MapIterator#remove} and the entries returned by the iterator do * not support {@link Entry#setValue}. To make a mutable map, the programmer must implement these methods * and override the {@link #put} method on this class.<p/> * * Simple implementations of the {@link #keySet}, {@link #values}, and {@link #entrySet} views are provided, * with these views delegating most of their operations to {@link AbstractIterableMap} itself and its * accompanying {@link MapIterator}. Programmers should extend these view classes to override their behavior * when necessary, but doing so requires overriding the associated view's retrieval method.<p/> * * For efficiency, most programmers will override the {@link #get}, {@link #containsKey}, and {@link #containsEntry} * methods, and for mutable maps, the {@link #remove}, {@link #removeKey}, and {@link #removeEntry} methods. * * @param <K> the key type * @param <V> the value type * * @author Cameron Beccario */ public abstract class AbstractIterableMap<K, V> implements IterableMap<K, V> { /** * {@inheritDoc} */ @Override public abstract int size(); /** * {@inheritDoc}<p/> * * This implementation compares the {@link #size} to 0. */ @Override public boolean isEmpty() { return size() == 0; } /** * {@inheritDoc} */ @Override public abstract MapIterator<K, V> iterator(); /** * {@inheritDoc}<p/> * * This implementation does a linear search over the keys returned by {@link MapIterator#next}. Most * programmers will override this method for maps whose implementations can provide sub-linear performance. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public boolean containsKey(Object key) { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { if (Objects.equals(key, iter.next())) { return true; } } return false; } /** * {@inheritDoc}<p/> * * This implementation does a linear search over the values returned by {@link MapIterator#value}. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public boolean containsValue(Object value) { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { iter.next(); if (Objects.equals(value, iter.value())) { return true; } } return false; } /** * Returns true if this map contains an association E where {@code Objects.equals(E.key, key) && * Objects.equals(E.value, value)}.<p/> * * The default implementation does a linear search over the items returned by {@link MapIterator#next} * and {@link MapIterator#value}. This method is used primarily for the implementation of the * {@link #entrySet} view. * * @throws ClassCastException if the key or value is of an inappropriate type for this map * @throws NullPointerException if the specified key or value is null and this map does not permit nulls */ protected boolean containsEntry(Object key, Object value) { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { if (Objects.equals(key, iter.next()) && Objects.equals(value, iter.value())) { return true; } } return false; } /** * {@inheritDoc}<p/> * * This implementation does a linear search over the keys returned by {@link MapIterator#next} and * returns the result of {@link MapIterator#value} if a match is found. Most programmers will override * this method for maps whose implementations can provide better-than-linear performance. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public V get(Object key) { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { if (Objects.equals(key, iter.next())) { return iter.value(); } } return null; } /** * {@inheritDoc}<p/> * * This implementation always throws {@link UnsupportedOperationException}. Mutable map implementations * must override this method. * * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ @Override public V put(K key, V value) { throw new UnsupportedOperationException(); } /** * {@inheritDoc}<p/> * * This implementation iterates over the associations of the specified map (using a {@link MapIterator} * when possible), successively calling {@link #put} on each association. * * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ @Override public void putAll(Map<? extends K, ? extends V> map) { if (map instanceof IterableMap) { for (MapIterator<? extends K, ? extends V> iter = iteratorFor(map); iter.hasNext();) { put(iter.next(), iter.value()); } } else { for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { put(entry.getKey(), entry.getValue()); } } } /** * Removes the association E where {@code Objects.equals(E.key, key)}, returning true if such an * association is found.<p/> * * The default implementation does a linear search over the keys returned by {@link MapIterator#next}, * and calls {@link MapIterator#remove} if a match is found. This method is used primarily for the * implementation of the {@link #keySet} view. * * @param key the key to remove. * @return if this map contained the specified key. * @throws UnsupportedOperationException if the remove operation is not supported by this map * @throws ClassCastException if the key is of an inappropriate type for this map * @throws NullPointerException if the specified key is null and this map does not permit nulls */ protected boolean removeKey(Object key) { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { if (Objects.equals(key, iter.next())) { iter.remove(); return true; } } return false; } /** * Removes the first association E where {@code Objects.equals(E.value, value)}, returning true if * such an association is found.<p/> * * The default implementation does a linear search over the values returned by {@link MapIterator#value}, * and calls {@link MapIterator#remove} if a match is found. This method is used primarily for the * implementation of the {@link #values} view. * * @param value the value to remove. * @return if this map contained the specified value. * @throws UnsupportedOperationException if the remove operation is not supported by this map * @throws ClassCastException if the value is of an inappropriate type for this map * @throws NullPointerException if the specified value is null and this map does not permit nulls */ protected boolean removeValue(Object value) { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { iter.next(); if (Objects.equals(value, iter.value())) { iter.remove(); return true; } } return false; } /** * Removes the association E where {@code Objects.equals(E.key, key) && Objects.equals(E.value, value)}, * returning {@code true} if such an association is found.<p/> * * The default implementation does a linear search over the items returned by {@link MapIterator#next} * and {@link MapIterator#value}, and calls {@link MapIterator#remove} if a match is found. This method * is used primarily for the implementation of the {@link #entrySet} view. * * @param key the key to remove. * @param value the associated value to remove. * @return if this map contained the specified entry. * @throws UnsupportedOperationException if the remove operation is not supported by this map * @throws ClassCastException if the key or value is of an inappropriate type for this map * @throws NullPointerException if the specified key or value is null and this map does not permit nulls */ protected boolean removeEntry(Object key, Object value) { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { if (Objects.equals(key, iter.next()) && Objects.equals(value, iter.value())) { iter.remove(); return true; } } return false; } /** * {@inheritDoc}<p/> * * This implementation does a linear search over the keys returned by {@link MapIterator#next} and * calls {@link MapIterator#remove} if a match is found. Note that this method will throw * {@link UnsupportedOperationException} if the iterator does not support the remove operation. Before * the remove, the {@link MapIterator#value} is retrieved and returned as the result of this method. * Most programmers will override this method for mutable maps whose implementations can provide * sub-linear performance. * * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public V remove(Object key) { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { if (Objects.equals(key, iter.next())) { V value = iter.value(); iter.remove(); return value; } } return null; } /** * {@inheritDoc}<p/> * * This implementation successively calls {@link MapIterator#remove} on each item returned by this * map's iterator. * * @throws UnsupportedOperationException {@inheritDoc} */ @Override public void clear() { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { iter.next(); iter.remove(); } } /** * Tests for map equality according to the rules required by {@link Map#equals}. * * @param that the map to test for equality. * @return true if the specified map is equal to this map. */ private boolean equals(Map<?, ?> that) { if (this.size() != that.size()) { return false; } try { for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { K thisKey = iter.next(); V thisValue = iter.value(); Object thatValue = that.get(thisKey); if (thatValue == null) { if (thisValue != null || !that.containsKey(thisKey)) { return false; } } else if (!thisValue.equals(thatValue)) { return false; } } } catch (ClassCastException | NullPointerException ignored) { } return true; } /** * {@inheritDoc}<p/> * * This implementation checks if the specified object is a {@link Map}, and if so, iterates * over the results returned by {@link MapIterator#next} and {@link MapIterator#value}, checking * if each association is contained within the specified map. */ @Override public boolean equals(Object that) { return this == that || that instanceof Map && equals((Map<?, ?>)that); } /** * {@inheritDoc}<p/> * * This implementation iterates over the results returned by {@link MapIterator#next} and * {@link MapIterator#value} to calculate the hash code. */ @Override public int hashCode() { int result = 0; for (MapIterator<K, V> iter = iterator(); iter.hasNext();) { result += AbstractEntry.hashCode(iter.next(), iter.value()); } return result; } /** * Append a String representation of this map's entries to the specified StringBuilder.<p/> * * The default implementation of this method iterates over the results returned by {@link MapIterator#next} * and {@link MapIterator#value} and appends using the format: {@code "k1=v1, k2=v2, ..., kn=vn"} * * @param sb the string builder to append to */ protected void printEntriesTo(StringBuilder sb) { MapIterator<K, V> iter = iterator(); if (iter.hasNext()) { do { K key = iter.next(); V value = iter.value(); sb.append(key == this ? "(this Map)" : key); sb.append('='); sb.append(value == this ? "(this Map)" : value); if (!iter.hasNext()) { break; } sb.append(',').append(' '); } while (true); } } /** * Returns a String representation of this map. This representation encases the results of calling * {@link #printEntriesTo} within curly braces '{' and '}'. */ @Override public String toString() { int size = size(); if (size == 0) { return "{}"; } StringBuilder sb = new StringBuilder(8 + size * 8).append('{'); printEntriesTo(sb); return sb.append('}').toString(); } // ================================================================================================================= // KeySet View /** * A simple view of this map's keys. This view delegates any operation to the owning map if the map * has an analogous operation, otherwise it uses the implementation inherited from {@link AbstractSet}. * For example, {@link KeysView#contains} simply delegates to {@link AbstractIterableMap#containsKey}. * All mutation operations, except {@link KeysView#add} and {@link KeysView#addAll}, are supported if * the underlying map supports them. */ protected class KeysView extends AbstractSet<K> { /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#size}. */ @Override public int size() { return AbstractIterableMap.this.size(); } /** * {@inheritDoc}<p/> * * This implementation returns an iterator over the underlying map's keys by wrapping an * instance of {@link MapIterator} and returning the results of {@link MapIterator#next}. */ @Override public Iterator<K> iterator() { final MapIterator<K, V> inner = AbstractIterableMap.this.iterator(); return new Iterator<K>() { @Override public boolean hasNext() { return inner.hasNext(); } @Override public K next() { return inner.next(); } @Override public void remove() { inner.remove(); } }; } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#containsKey}. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public boolean contains(Object o) { return AbstractIterableMap.this.containsKey(o); } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#removeKey}. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws UnsupportedOperationException {@inheritDoc} */ @Override public boolean remove(Object o) { return AbstractIterableMap.this.removeKey(o); } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#clear}. * * @throws UnsupportedOperationException {@inheritDoc} */ @Override public void clear() { AbstractIterableMap.this.clear(); } } /** * {@inheritDoc}<p/> * * This implementation returns a new view on each invocation. */ @Override public Set<K> keySet() { return new KeysView(); } // ================================================================================================================= // Values View /** * A simple view of this map's values. This view delegates any operation to the owning map if the map * has an analogous operation, otherwise it uses the implementation inherited from {@link AbstractCollection}. * For example, {@link ValuesView#contains} simply delegates to {@link AbstractIterableMap#containsValue}. * All mutation operations, except {@link ValuesView#add} and {@link ValuesView#addAll}, are supported if * the underlying map supports them. */ protected class ValuesView extends AbstractCollection<V> { /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#size}. */ @Override public int size() { return AbstractIterableMap.this.size(); } /** * {@inheritDoc}<p/> * * This implementation returns an iterator over the underlying map's values by wrapping an * instance of {@link MapIterator} and returning the results of {@link MapIterator#value}. */ @Override public Iterator<V> iterator() { final MapIterator<K, V> inner = AbstractIterableMap.this.iterator(); return new Iterator<V>() { @Override public boolean hasNext() { return inner.hasNext(); } @Override public V next() { inner.next(); return inner.value(); } @Override public void remove() { inner.remove(); } }; } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#containsValue}. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public boolean contains(Object o) { return AbstractIterableMap.this.containsValue(o); } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#removeValue}. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws UnsupportedOperationException {@inheritDoc} */ @Override public boolean remove(Object o) { return AbstractIterableMap.this.removeValue(o); } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#clear}. * * @throws UnsupportedOperationException {@inheritDoc} */ @Override public void clear() { AbstractIterableMap.this.clear(); } } /** * {@inheritDoc}<p/> * * This implementation returns a new view on each invocation. */ @Override public Collection<V> values() { return new ValuesView(); } // ================================================================================================================= // EntrySet View /** * A simple view of this map's entries. This view delegates any operation to the owning map if the map * has an analogous operation, otherwise it uses the implementation inherited from {@link AbstractSet}. * For example, {@link EntriesView#contains} simply delegates to {@link AbstractIterableMap#containsEntry}. * All mutation operations, except {@link EntriesView#add} and {@link EntriesView#addAll}, are supported if * the underlying map supports them. */ protected class EntriesView extends AbstractSet<Entry<K, V>> { /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#size}. */ @Override public int size() { return AbstractIterableMap.this.size(); } /** * {@inheritDoc}<p/> * * This implementation returns an iterator over the underlying map's entries by wrapping an * instance of {@link MapIterator} and returning the results of {@link MapIterator#entry}. */ @Override public Iterator<Entry<K, V>> iterator() { final MapIterator<K, V> inner = AbstractIterableMap.this.iterator(); return new Iterator<Entry<K, V>>() { @Override public boolean hasNext() { return inner.hasNext(); } @Override public Entry<K, V> next() { inner.next(); return inner.entry(); } @Override public void remove() { inner.remove(); } }; } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#containsEntry}. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public boolean contains(Object o) { return o instanceof Entry && AbstractIterableMap.this.containsEntry(((Entry<?, ?>)o).getKey(), ((Entry<?, ?>)o).getValue()); } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#removeEntry}. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws UnsupportedOperationException {@inheritDoc} */ @Override public boolean remove(Object o) { return o instanceof Entry && AbstractIterableMap.this.removeEntry(((Entry<?, ?>)o).getKey(), ((Entry<?, ?>)o).getValue()); } /** * {@inheritDoc}<p/> * * This implementation delegates to {@link AbstractIterableMap#clear}. * * @throws UnsupportedOperationException {@inheritDoc} */ @Override public void clear() { AbstractIterableMap.this.clear(); } /** * {@inheritDoc}<p/> * * Interestingly, by contract, {@code map.entrySet().hashCode() == map.hashCode()}, so this * implementation delegates to {@link AbstractIterableMap#hashCode}. */ @Override public int hashCode() { return AbstractIterableMap.this.hashCode(); } /** * Returns a String representation of this set of entries. This representation encases the results * of calling {@link #printEntriesTo} within square brackets '[' and ']'. */ @Override public String toString() { int size = size(); if (size == 0) { return "[]"; } StringBuilder sb = new StringBuilder(8 + size * 8).append('['); printEntriesTo(sb); return sb.append(']').toString(); } } /** * {@inheritDoc}<p/> * * This implementation returns a new view on each invocation. */ @Override public Set<Entry<K, V>> entrySet() { return new EntriesView(); } /** * Utility function that returns a MapIterator for the specified map. Hides wildcard noise. */ private static <K, V> MapIterator<K, V> iteratorFor(Map<K, V> map) { return ((IterableMap<K, V>)map).iterator(); } }