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

ca.odell.glazedlists.impl.FunctionListMap Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl;

import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FunctionList;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.DisposableMap;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;

import java.util.*;

/**
 * This map implementation sits atop an {@link EventList} and makes it
 * accessible via the convenient {@link Map} interface. It is constructed with
 * a {@link FunctionList.Function} which is used to create the keys of the map.
 * The values of the map are the lists of values from the {@link EventList}.
 *
 * 

For example, an {@link EventList} containing * *

 * {Cherry, Orange, Apple, Pineapple, Banana}
 * 
* * paired with a Function that returns the first letter of the fruit name * produces the map: * *
 * "C" -> "Cherry"
 * "O" -> "Orange"
 * "A" -> "Apple"
 * "P" -> "Pinapple"
 * "B" -> "Banana"
 * 
* * Note: all values MUST map to unique keys in order to use * this class. If that constraint is violated at any time, an * {@link IllegalStateException} will be thrown to indicate the violation to * the programmer. * * @author James Lemieux */ public class FunctionListMap implements DisposableMap, ListEventListener { /** The keys of this Map (used to remove entries from the {@link #delegate}) */ private List keyList; /** The keyList of this Map made to look like a Set (it is build lazily in {@link #keySet()}) */ private Set keySet; /** The values of this Map in an {@link EventList}. */ private final EventList valueList; /** The set of Map.Entry objects in this Map (it is build lazily in {@link #entrySet()}) */ private Set> entrySet; /** The function which produces keyList for this multimap. */ private final FunctionList.Function keyFunction; /** The delegate Map which is kept in synch with changes. */ private final Map delegate; /** * Construct a map which maps the keys produced by the * keyFunction, to corresponding entries from the * source. * * @param source the raw data which has not yet been grouped * @param keyFunction the function capable of producing the key of this * {@link Map} for each value */ public FunctionListMap(EventList source, FunctionList.Function keyFunction) { if (keyFunction == null) throw new IllegalArgumentException("keyFunction may not be null"); // the source is the list of values this.valueList = source; this.valueList.addListEventListener(this); this.keyFunction = keyFunction; // it is important that the keyList is a BasicEventList since we use its ListIterator, which remains // consistent with changes to its underlying data (any other Iterator would throw a ConcurrentModificationException) this.keyList = new BasicEventList(source.size()); this.delegate = new HashMap(source.size()); // populate the keyList and the delegate Map for (int i = 0, n = source.size(); i < n; i++) elementAdded(i); } /** @inheritDoc */ public void dispose() { valueList.removeListEventListener(this); keySet = null; entrySet = null; keyList.clear(); delegate.clear(); } /** @inheritDoc */ public int size() { return delegate.size(); } /** @inheritDoc */ public boolean isEmpty() { return delegate.isEmpty(); } /** @inheritDoc */ public boolean containsKey(Object key) { return delegate.containsKey(key); } /** @inheritDoc */ public boolean containsValue(Object value) { return delegate.containsValue(value); } /** @inheritDoc */ public V get(Object key) { return delegate.get(key); } /** @inheritDoc */ public V put(K key, V value) { checkKeyValueAgreement(key, value); // if no prior value exists for this key, simply add it if (!containsKey(key)) { valueList.add(value); return null; } // otherwise try to replace the old value in place final V toReplace = get(key); // try to find the old value by identity in the valueList and replace it for (ListIterator i = valueList.listIterator(); i.hasNext();) { if (i.next() == toReplace) { i.set(value); return toReplace; } } // something terrible has happened if a value exists in the delegate Map but not in the valueList throw new IllegalStateException("Found key: " + key + " in delegate map but could not find corresponding value in valueList: " + toReplace); } /** @inheritDoc */ public void putAll(Map m) { // verify the contents of the given Map and ensure all key/value pairs agree with the keyFunction for (Iterator> i = m.entrySet().iterator(); i.hasNext();) { final Entry entry = i.next(); checkKeyValueAgreement(entry.getKey(), entry.getValue()); } // add each of the elements from m into this Map for (Iterator> i = m.entrySet().iterator(); i.hasNext();) { final Entry entry = i.next(); put(entry.getKey(), entry.getValue()); } } /** * This convenience method ensures that the key matches the * key value produced for the value object. If a * mismatch is found, an {@link IllegalArgumentException} is thrown. * * @param key the expected key value of each value object * @param value the value object which should produce the given key when * run through the key function */ private void checkKeyValueAgreement(K key, V value) { final K k = key(value); if (!GlazedListsImpl.equal(key, k)) throw new IllegalArgumentException("The calculated key for the given value (" + k + ") does not match the given key (" + key + ")"); } /** @inheritDoc */ public void clear() { valueList.clear(); } /** @inheritDoc */ public V remove(Object key) { if (!containsKey(key)) return null; final V value = get(key); GlazedListsImpl.identityRemove(valueList, value); return value; } /** @inheritDoc */ public Collection values() { return valueList; } /** @inheritDoc */ public Set keySet() { if (this.keySet == null) this.keySet = new KeySet(); return this.keySet; } /** @inheritDoc */ public Set> entrySet() { if (this.entrySet == null) this.entrySet = new EntrySet(); return this.entrySet; } /** @inheritDoc */ @Override public boolean equals(Object o) { return delegate.equals(o); } /** @inheritDoc */ @Override public int hashCode() { return delegate.hashCode(); } /** * Updates this Map datastructure to reflect changes in the underlying * {@link EventList}. Specifically, new entries are added and existing * entries are updated in this Map by calculating a key using the key * function of this Map. * * The algorithm in this method operates in 2 passes. The reason for this * is that {@link #putInDelegate} contains a sanity check that ensures we * never enter an illegal state for the map (where a single key maps to two * distinct values). But, complex but valid listChanges may * temporarily break that invariant only to rectify the state at a later * index. (e.g. insert a duplicate value at i and then delete the original * value at i+1) * * By performing all remove operations first in pass 1, we preserve the * ability to check the invariant in pass 2 when additions are processed. * Thus, FunctionListMap remains proactive in locating values which break * the invariant. * * @param listChanges an event describing the changes in the FunctionList */ public void listChanged(ListEvent listChanges) { int offset = 0; // pass 1: do all remove work (this includes deletes and the front half of updates) while (listChanges.next()) { switch (listChanges.getType()) { case ListEvent.DELETE: elementRemoved(listChanges.getIndex() + offset); break; case ListEvent.UPDATE: elementRemoved(listChanges.getIndex() + offset); offset--; break; case ListEvent.INSERT: offset--; break; } } listChanges.reset(); // pass 2: do all add work (this includes inserts and the back half of updates) while (listChanges.next()) { switch (listChanges.getType()) { case ListEvent.UPDATE: elementAdded(listChanges.getIndex()); break; case ListEvent.INSERT: elementAdded(listChanges.getIndex()); break; } } } /** * Updates the internal data structures to reflect the addition of a new * element at the given index. */ private void elementAdded(int index) { final V value = valueList.get(index); final K key = key(value); keyList.add(index, key); putInDelegate(key, value); } /** * Updates the internal data structures to reflect the removal of an * element at the given index. */ private void elementRemoved(int index) { final K key = keyList.remove(index); delegate.remove(key); } /** * This method puts an entry into the delegate Map after first verifying * that the delegate Map does not contain an entry for the given * key. */ private void putInDelegate(K key, V value) { if (delegate.containsKey(key)) throw new IllegalStateException("Detected duplicate key->value mapping: attempted to put '" + key + "' -> '" + value + "' in the map, but found '" + key + "' -> '" + delegate.get(key) + "' already existed."); delegate.put(key, value); } /** * Uses the key function to return the key for a given value. * * @param value a single value from the valueList list * @return the key which maps to the given value */ private K key(V value) { return keyFunction.evaluate(value); } /** * This private {@link Set} implementation represents the {@link Map.Entry} * objects within this Map. All mutating methods are implemented to * "write through" to the backing {@link EventList} which ensures that both * the {@link EventList} and this Map always remain in sync. */ private class EntrySet extends AbstractSet> { /** {@inheritDoc} */ @Override public int size() { return keyList.size(); } /** {@inheritDoc} */ @Override public Iterator> iterator() { return new EntrySetIterator(keyList.listIterator()); } /** {@inheritDoc} */ @Override public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; final Entry e = (Entry) o; final K key = e.getKey(); final V value = e.getValue(); final V mapValue = FunctionListMap.this.get(key); return GlazedListsImpl.equal(value, mapValue); } /** {@inheritDoc} */ @Override public boolean remove(Object o) { if (!contains(o)) return false; FunctionListMap.this.remove(((Map.Entry) o).getKey()); return true; } /** {@inheritDoc} */ @Override public void clear() { FunctionListMap.this.clear(); } } /** * This private {@link Iterator} implementation iterates the {@link Set} of * {@link Map.Entry} objects within this Map. All mutating methods are * implemented to "write through" to the backing {@link EventList} which * ensures that both the {@link EventList} and this Map always remain * in sync. * *

Note: This implementation returns a new * {@link Map.Entry} object each time {@link #next} is called. Identity is * not preserved. */ private class EntrySetIterator implements Iterator> { /** The delegate Iterator walks a List of keys for the Map. */ private final ListIterator keyIter; /** * Construct a new EntrySetIterator using a delegate Iterator that * walks the keys of the MultMap. * * @param keyIter a {@link ListIterator} that walks the keys of the Map */ EntrySetIterator(ListIterator keyIter) { this.keyIter = keyIter; } /** {@inheritDoc} */ public boolean hasNext() { return keyIter.hasNext(); } /** * Returns a new {@link Map.Entry} each time this method is called. */ public Entry next() { final K key = keyIter.next(); return new MapEntry(key, get(key)); } /** {@inheritDoc} */ public void remove() { final int index = keyIter.previousIndex(); if (index == -1) throw new IllegalStateException("Cannot remove() without a prior call to next()"); valueList.remove(index); } } /** * This is an implementation of the {@link Map.Entry} interface that is * appropriate for this Map. All mutating methods are implemented to * "write through" to the backing {@link EventList} which ensures that * both the {@link EventList} and this Map always remain in sync. */ private class MapEntry implements Map.Entry { /** The Map key for this Entry object. */ private final K key; /** The Map value for this Entry object. */ private V value; /** * Constructs a new MapEntry with the given key and * initial value. */ MapEntry(K key, V value) { if (value == null) throw new IllegalArgumentException("value cannot be null"); this.value = value; this.key = key; } /** {@inheritDoc} */ public K getKey() { return key; } /** {@inheritDoc} */ public V getValue() { return value; } /** {@inheritDoc} */ public V setValue(V newValue) { // ensure the newValue element agrees with the key of this Entry checkKeyValueAgreement(key, newValue); this.value = newValue; return FunctionListMap.this.put(key, newValue); } /** * Two MapEntry entry objects are equal iff their keys and values * are equal. */ @Override public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry) o; final boolean keysEqual = GlazedListsImpl.equal(getKey(), e.getKey()); return keysEqual && GlazedListsImpl.equal(getValue(), e.getValue()); } /** {@inheritDoc} */ @Override public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ value.hashCode(); } /** {@inheritDoc} */ @Override public String toString() { return getKey() + "=" + getValue(); } } /** * This private {@link Set} implementation represents the keyList within this * Map. All mutating methods are implemented to "write through" to the * backing {@link EventList} which ensures that both the {@link EventList} * and this Map always remain in sync. */ private class KeySet extends AbstractSet { /** {@inheritDoc} */ @Override public int size() { return keyList.size(); } /** {@inheritDoc} */ @Override public Iterator iterator() { return new KeySetIterator(keyList.listIterator()); } /** {@inheritDoc} */ @Override public boolean contains(Object o) { return FunctionListMap.this.containsKey(o); } /** {@inheritDoc} */ @Override public boolean remove(Object o) { return FunctionListMap.this.remove(o) != null; } /** {@inheritDoc} */ @Override public void clear() { FunctionListMap.this.clear(); } } /** * This private {@link Iterator} implementation iterates the {@link Set} of * keyList within this Map. All mutating methods are implemented to * "write through" to the backing {@link EventList} which ensures that both * the {@link EventList} and this Map always remain in sync. */ private class KeySetIterator implements Iterator { /** The delegate Iterator walks a List of keyList for the Map. */ private final ListIterator keyIter; /** * Construct a new KeySetIterator using a delegate Iterator that walks * the list of unique keyList of the MultMap. * * @param keyIter a {@link ListIterator} that walks the keyList of the Map */ KeySetIterator(ListIterator keyIter) { this.keyIter = keyIter; } /** {@inheritDoc} */ public boolean hasNext() { return keyIter.hasNext(); } /** {@inheritDoc} */ public K next() { return keyIter.next(); } /** {@inheritDoc} */ public void remove() { final int index = keyIter.previousIndex(); if (index == -1) throw new IllegalStateException("Cannot remove() without a prior call to next()"); valueList.remove(index); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy