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

ca.odell.glazedlists.impl.GroupingListMultiMap 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.*;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.event.ListEvent;

import java.util.*;

/**
 * This multimap 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}
 * which all map to a common key.
 *
 * 

For example, a list containing * *

 * {Cherry, Plum, Cranberry, Pineapple, Banana, Prune}
 * 
* * paired with a Function that returns the first letter of the fruit name * produces the multi map: * *
 * "B" -> {Banana}
 * "C" -> {Cherry, Cranberry}
 * "P" -> {Plum, Pineapple, Prune}
 * 
* * @author James Lemieux */ public class GroupingListMultiMap implements Map, List>, ListEventListener> { /** The raw values of this Map in an {@link EventList}. */ private final GroupingList groupingList; /** The polished values of this Map in an {@link EventList}. */ private final FunctionList, List> valueList; /** The keys of this Map (used to remove entries from the {@link #delegate}) */ private final List> keyList; /** The keys of this Map made to look like a Set (it is build lazily in {@link #keySet()}) */ private Set> keySet; /** The function which produces keys for this multimap. */ private final FunctionList.Function> keyFunction; /** The delegate Map which is kept in synch with {@link #groupingList} changes. */ private final Map, List> delegate; /** The set of Map.Entry objects in this Map (it is build lazily in {@link #entrySet()}) */ private Set, List>> entrySet; /** * Construct a multimap which maps the keys produced by the * keyFunction, to groups of values from source * that agree on their keys. * * @param source the raw data which has not yet been grouped * @param keyFunction the function capable of producing the keys of this * {@link Map}; the keys themselves are {@link Comparable} and thus * also determine the content of the {@link List}s which are the * values of this {@link Map}. */ public GroupingListMultiMap(EventList source, FunctionList.Function> keyFunction) { this.keyFunction = keyFunction; // construct a GroupingList which groups together the source elements for common keys this.groupingList = new GroupingList(source, new FunctionComparator(keyFunction)); // wrap each List in the GroupingList in a layer that enforces the keyFunction constraints for writes this.valueList = new FunctionList, List>(this.groupingList, new ValueListFunction()); this.valueList.addListEventListener(this); // 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>(this.groupingList.size()); this.delegate = new HashMap,List>(this.groupingList.size()); // initialize both the keyList and the delegate Map for (Iterator> i = this.valueList.iterator(); i.hasNext();) { final List value = i.next(); final Comparable key = key(value); this.keyList.add(key); this.delegate.put(key, value); } } /** {@inheritDoc} */ public int size() { return this.delegate.size(); } /** {@inheritDoc} */ public boolean isEmpty() { return this.delegate.isEmpty(); } /** {@inheritDoc} */ public boolean containsKey(Object key) { return this.delegate.containsKey(key); } /** {@inheritDoc} */ public boolean containsValue(Object value) { return this.delegate.containsValue(value); } /** {@inheritDoc} */ public List get(Object key) { return this.delegate.get(key); } /** {@inheritDoc} */ public List put(Comparable key, List value) { this.checkKeyValueAgreement(key, value); final List removed = (List) this.remove(key); this.groupingList.add(value); return removed; } /** {@inheritDoc} */ public void putAll(Map, ? extends List> m) { // verify the contents of the given Map and ensure all key/value pairs agree with the keyFunction for (Iterator, ? extends List>> i = m.entrySet().iterator(); i.hasNext();) { final Entry, ? extends List> entry = i.next(); final Comparable key = entry.getKey(); final List value = entry.getValue(); this.checkKeyValueAgreement(key, value); } // remove all values currently associated with the keys for (Iterator> i = m.keySet().iterator(); i.hasNext();) this.remove(i.next()); // add all new values into this Map this.groupingList.addAll(m.values()); } /** * This convenience method ensures that the key matches the * key values produced by each of the value objects. If a * mismatch is found, an {@link IllegalArgumentException} is thrown. * * @param key the expected key value of each value object * @param value the value objects which should produce the given key when * run through the key function */ private void checkKeyValueAgreement(Comparable key, Collection value) { for (Iterator i = value.iterator(); i.hasNext();) checkKeyValueAgreement(key, i.next()); } /** * 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(Comparable key, V value) { final Comparable 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() { this.groupingList.clear(); } /** {@inheritDoc} */ public List remove(Object key) { final int index = this.keyList.indexOf(key); return index == -1 ? null : this.groupingList.remove(index); } /** {@inheritDoc} */ public Collection> values() { return this.groupingList; } /** {@inheritDoc} */ public Set> keySet() { if (this.keySet == null) this.keySet = new KeySet(); return this.keySet; } /** {@inheritDoc} */ public Set, List>> entrySet() { if (this.entrySet == null) this.entrySet = new EntrySet(); return this.entrySet; } /** * Updates this MultiMap datastructure to reflect changes in the underlying * {@link GroupingList}. Specifically, new entries are added to this * MultiMap by calculating a key using key function this MultiMap was * constructed with. * * @param listChanges an event describing the changes in the GroupingList */ public void listChanged(ListEvent> listChanges) { while (listChanges.next()) { final int changeIndex = listChanges.getIndex(); final int changeType = listChanges.getType(); if (changeType == ListEvent.INSERT) { final List inserted = (List) listChanges.getSourceList().get(changeIndex); final Comparable key = key(inserted); this.keyList.add(changeIndex, key); this.delegate.put(key, inserted); } else if (changeType == ListEvent.DELETE) { final Comparable deleted = keyList.remove(changeIndex); this.delegate.remove(deleted); } } } /** * Uses the key function to return the key for a given list of values. * * @param values a non-empty list of values from the source * {@link GroupingList} which share the same key value * @return the shared key which maps to each of the given values */ private Comparable key(List values) { return key(values.get(0)); } /** * Uses the key function to return the key for a given value. * * @param value a single value from the source list * @return the key which maps to the given value */ private Comparable key(V value) { return this.keyFunction.evaluate(value); } /** * This private {@link Set} implementation represents the {@link Map.Entry} * objects within this MultiMap. All mutating methods are implemented to * "write through" to the backing {@link EventList} which ensures that both * the {@link EventList} and this MultiMap always remain in sync. */ private class EntrySet extends AbstractSet, List>> { /** {@inheritDoc} */ public int size() { return keyList.size(); } /** {@inheritDoc} */ public Iterator, List>> iterator() { return new EntrySetIterator(keyList.listIterator()); } /** {@inheritDoc} */ public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; final Entry, List> e = (Entry, List>) o; final Comparable key = e.getKey(); final List value = e.getValue(); final List mapValue = (List) GroupingListMultiMap.this.get(key); return GlazedListsImpl.equal(value, mapValue); } /** {@inheritDoc} */ public boolean remove(Object o) { if (!contains(o)) return false; GroupingListMultiMap.this.remove(((Map.Entry) o).getKey()); return true; } /** {@inheritDoc} */ public void clear() { GroupingListMultiMap.this.clear(); } } /** * This private {@link Iterator} implementation iterates the {@link Set} of * {@link Map.Entry} objects within this MultiMap. All mutating methods are * implemented to "write through" to the backing {@link EventList} which * ensures that both the {@link EventList} and this MultiMap 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, List>> { /** The delegate Iterator walks a List of keys for the MultiMap. */ 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 MultiMap */ 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, List> next() { final Comparable key = keyIter.next(); return new MultiMapEntry(key, (List) 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()"); groupingList.remove(index); } } /** * This is an implementation of the {@link Map.Entry} interface that is * appropriate for this MultiMap. All mutating methods are implemented to * "write through" to the backing {@link EventList} which ensures that * both the {@link EventList} and this MultiMap always remain in sync. */ private class MultiMapEntry implements Map.Entry, List> { /** The MultiMap key for this Entry object. */ private final Comparable key; /** The MultiMap value for this Entry object. */ private List value; /** * Constructs a new MultiMapEntry with the given key and * initial value. */ MultiMapEntry(Comparable key, List value) { if (value == null) throw new IllegalArgumentException("value cannot be null"); this.value = value; this.key = key; } /** {@inheritDoc} */ public Comparable getKey() { return key; } /** {@inheritDoc} */ public List getValue() { return value; } /** * Since {@link GroupingList} is particular about the identity of the * Lists it contains, and this MultiMap uses those same * Lists as its values, this method is implemented to simply * replace the contents of the List with the contents * of the given newValue. So, the data is changed, but the * identity of the List in the MultiMap and {@link GroupingList} is not. * * @param newValue the new values use as elements of the value List * @return the old value List of this Entry */ public List setValue(List newValue) { // ensure all of the newValue elements agree with the key of this Entry checkKeyValueAgreement((Comparable) getKey(), newValue); // record the old value List elements (to return) final List oldValue = new ArrayList(value); // replace all elements within the List // // (GroupingList actually removes Lists the moment they become *empty* // so we first insert the new values rather than removing the old values // to avoid the temporary existence of an empty List) value.addAll(newValue); value.removeAll(oldValue); return oldValue; } /** * Two MultiMapEntry entry objects are equal iff their keys and values * are equal. */ 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} */ public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ value.hashCode(); } /** {@inheritDoc} */ public String toString() { return getKey() + "=" + getValue(); } } /** * This private {@link Set} implementation represents the keys within this * MultiMap. All mutating methods are implemented to "write through" to the * backing {@link EventList} which ensures that both the {@link EventList} * and this MultiMap always remain in sync. */ private class KeySet extends AbstractSet> { /** {@inheritDoc} */ public int size() { return keyList.size(); } /** {@inheritDoc} */ public Iterator> iterator() { return new KeySetIterator(keyList.listIterator()); } /** {@inheritDoc} */ public boolean contains(Object o) { return GroupingListMultiMap.this.containsKey(o); } /** {@inheritDoc} */ public boolean remove(Object o) { return GroupingListMultiMap.this.remove(o) != null; } /** {@inheritDoc} */ public void clear() { GroupingListMultiMap.this.clear(); } } /** * This private {@link Iterator} implementation iterates the {@link Set} of * keys within this MultiMap. All mutating methods are implemented to * "write through" to the backing {@link EventList} which ensures that both * the {@link EventList} and this MultiMap always remain in sync. */ private class KeySetIterator implements Iterator> { /** The delegate Iterator walks a List of keys for the MultiMap. */ private final ListIterator> keyIter; /** * Construct a new KeySetIterator using a delegate Iterator that walks * the list of unique keys of the MultMap. * * @param keyIter a {@link ListIterator} that walks the keys of the MultiMap */ KeySetIterator(ListIterator> keyIter) { this.keyIter = keyIter; } /** {@inheritDoc} */ public boolean hasNext() { return keyIter.hasNext(); } /** {@inheritDoc} */ public Comparable 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()"); groupingList.remove(index); } } /** * This Comparator first runs each value through a * {@link FunctionList.Function} to produce {@link Comparable} objects * which are then compared to determine a relative ordering. */ private final class FunctionComparator implements Comparator { /** A Comparator that orders {@link Comparable} objects. */ private final Comparator delegate = GlazedLists.comparableComparator(); /** A function that extracts {@link Comparable} values from given objects. */ private final FunctionList.Function> function; /** * Construct a new FunctionComparator that uses the given * function to extract {@link Comparable} values from * given objects. */ FunctionComparator(FunctionList.Function> function) { if (function == null) throw new IllegalArgumentException("function may not be null"); this.function = function; } /** {@inheritDoc} */ public int compare(V o1, V o2) { final Comparable c1 = function.evaluate(o1); final Comparable c2 = function.evaluate(o2); return delegate.compare(c1, c2); } } /** * This Function wraps each List produced by the GroupingList with a layer * that ensures that mutations to it don't violate the keyFunction * constraints required by this MultiMap. */ private final class ValueListFunction implements FunctionList.Function, List> { public List evaluate(List sourceValue) { return new ValueList(sourceValue); } } /** * This class wraps each element of the GroupingList with a layer of * checking to ensure that mutations to it don't violate the keyFunction * constraints required by this MultiMap. */ private final class ValueList implements List { /** The List that actually implements the List operations */ private final List delegate; /** The key that all values in this List must share. */ private final Comparable key; public ValueList(List delegate) { this.delegate = delegate; this.key = key(delegate.get(0)); } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public boolean contains(Object o) { return delegate.contains(o); } public Iterator iterator() { return delegate.iterator(); } public Object[] toArray() { return delegate.toArray(); } public T[] toArray(T[] a) { return delegate.toArray(a); } public boolean add(V o) { checkKeyValueAgreement(this.key, o); return delegate.add(o); } public boolean addAll(Collection c) { checkKeyValueAgreement(this.key, c); return delegate.addAll(c); } public boolean addAll(int index, Collection c) { checkKeyValueAgreement(this.key, c); return delegate.addAll(index, c); } public void add(int index, V element) { checkKeyValueAgreement(this.key, element); delegate.add(index, element); } public V set(int index, V element) { checkKeyValueAgreement(this.key, element); return delegate.set(index, element); } public List subList(int fromIndex, int toIndex) { return new ValueList(delegate.subList(fromIndex, toIndex)); } public ListIterator listIterator() { return new ValueListIterator(delegate.listIterator()); } public ListIterator listIterator(int index) { return new ValueListIterator(delegate.listIterator(index)); } public boolean remove(Object o) { return delegate.remove(o); } public boolean containsAll(Collection c) { return delegate.containsAll(c); } public boolean removeAll(Collection c) { return delegate.removeAll(c); } public boolean retainAll(Collection c) { return delegate.retainAll(c); } public void clear() { delegate.clear(); } public boolean equals(Object o) { return delegate.equals(o); } public int hashCode() { return delegate.hashCode(); } public V get(int index) { return delegate.get(index); } public V remove(int index) { return delegate.remove(index); } public int indexOf(Object o) { return delegate.indexOf(o); } public int lastIndexOf(Object o) { return delegate.lastIndexOf(o); } public String toString() { return delegate.toString(); } /** * This class wraps the normal ListIterator returned by the GroupingList * elements with extra checking to ensure mutations to it don't violate * the keyFunction constraints required by this MultiMap. */ private final class ValueListIterator implements ListIterator { private final ListIterator delegate; public ValueListIterator(ListIterator delegate) { this.delegate = delegate; } public void set(V o) { checkKeyValueAgreement(key, o); delegate.set(o); } public void add(V o) { checkKeyValueAgreement(key, o); delegate.add(o); } public boolean hasNext() { return delegate.hasNext(); } public V next() { return delegate.next(); } public boolean hasPrevious() { return delegate.hasPrevious(); } public V previous() { return delegate.previous(); } public int nextIndex() { return delegate.nextIndex(); } public void remove() { delegate.remove(); } public int previousIndex() { return delegate.previousIndex(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy