/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
// TODO javadoc - remove this comment only when the class and all non-public
// methods and fields are documented
package org.beanfabrics.model;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.ResourceBundle;
import java.util.Set;
import org.beanfabrics.Path;
import org.beanfabrics.context.ContextOwner;
import org.beanfabrics.event.BnPropertyChangeEvent;
import org.beanfabrics.event.ElementChangedEvent;
import org.beanfabrics.event.ElementsAddedEvent;
import org.beanfabrics.event.ElementsDeselectedEvent;
import org.beanfabrics.event.ElementsRemovedEvent;
import org.beanfabrics.event.ElementsReplacedEvent;
import org.beanfabrics.event.ElementsSelectedEvent;
import org.beanfabrics.event.ListListener;
import org.beanfabrics.event.ListSupport;
import org.beanfabrics.util.Interval;
import org.beanfabrics.util.OrderPreservingMap;
import org.beanfabrics.util.ResourceBundleFactory;
import org.beanfabrics.validation.ValidationRule;
import org.beanfabrics.validation.ValidationState;
/**
* The MapPM is a map of presentation models. Basically it provides methods for
* adding, removing, accessing and iterating elements and informs listeners
* about changes. It also maintains a {@link Selection}.
*
* @author Michael Karneim
*/
public class MapPM<K, V extends PresentationModel> extends AbstractPM implements IMapPM<K, V> {
protected static final String KEY_MESSAGE_INVALID_ELEMENTS = "message.invalidElements";
private final ResourceBundle resourceBundle = ResourceBundleFactory.getBundle(ListPM.class);
private static final int NONE = -1;
private final OrderPreservingMap<K, V> entries = new OrderPreservingMap<K, V>();
private final SelectedKeysImpl selectedKeys = new SelectedKeysImpl();
private final Selection<V> selection = new SelectionImpl();
private final ListSupport support = new ListSupport(this);
private final PropertyChangeListener elementsPcl = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
onElementChanged(evt);
}
};
private final ListListener selfListener = new ListListener() {
public void elementsSelected(ElementsSelectedEvent evt) {
onEntriesChanged(evt);
}
public void elementsReplaced(ElementsReplacedEvent evt) {
onEntriesChanged(evt);
setSortKeys(null);
}
public void elementsRemoved(ElementsRemovedEvent evt) {
onEntriesChanged(evt);
}
public void elementsDeselected(ElementsDeselectedEvent evt) {
onEntriesChanged(evt);
}
public void elementsAdded(ElementsAddedEvent evt) {
onEntriesChanged(evt);
setSortKeys(null);
}
public void elementChanged(ElementChangedEvent evt) {
onEntriesChanged(evt);
setSortKeys(null);
}
};
private boolean revalidateElementsOnChangeEnabled = false;
/**
* This sorter implements the sorting algorithm used by
* {@link #sortBy(SortKey...)}.
*/
private Sorter sorter = new DefaultSorter();
/**
* The sort keys reflect the sorting state of this list.
*/
private Collection<SortKey> sortKeys = Collections.emptyList();
public MapPM() {
this.support.addListListener(this.selfListener);
// Please note: to disable default validation rules just call getValidator().clear();
getValidator().add(new ListElementsValidationRule());
}
/**
* Returns the {@link Sorter}. The sorter defines the sorting alorithm used
* {@link #sortBy(SortKey...)}.
*
* @return the {@link Sorter}
*/
public Sorter getSorter() {
return sorter;
}
/**
* Sets the {@link Sorter}.
* <p>
* See {@link #getSorter()} for details.
*
* @param sorter
*/
public void setSorter(Sorter sorter) {
if (sorter == null) {
throw new IllegalArgumentException("sorter==null");
}
this.sorter = sorter;
}
public boolean isRevalidateElementsOnChangeEnabled() {
return revalidateElementsOnChangeEnabled;
}
// TODO (mk) write TEST
public void setRevalidateElementsOnChangeEnabled(boolean enabled) {
this.revalidateElementsOnChangeEnabled = enabled;
// TODO (mk) fire pce
}
protected void onEntriesChanged(EventObject evt) {
if (revalidateElementsOnChangeEnabled) {
revalidateAllExcept(null);
}
revalidateProperties();
BnPropertyChangeEvent newEvent = new BnPropertyChangeEvent(this, null, null, null, evt);
getPropertyChangeSupport().firePropertyChange(newEvent);
}
/**
* Revalidates all elements of this map.
*/
// TODO (mk) write TEST
public void revalidateElements() {
revalidateAllExcept(null);
}
protected void revalidateAllExcept(V element) {
for (V e : entries.valuesReference()) {
if (element == e) {
continue;
}
//PropertySupport.get(e).revalidateProperties();
e.revalidate();
}
}
protected ListSupport getListPropertySupport() {
return this.support;
}
private void checkElementsType(Class aType) {
for (PresentationModel element : entries.values()) {
if (aType.isInstance(element) == false) {
throw new IllegalArgumentException("map is not empty, so new element type must be superclass of " + element.getClass().getName());
}
}
}
public boolean isEmpty() {
return entries == null || entries.isEmpty();
}
public void clear() {
selectedKeys.clear();
int oldLen = this.size();
if (oldLen > 0) {
Collection<PresentationModel> oldEds = (Collection<PresentationModel>)this.entries.toCollection();
for (PresentationModel element : oldEds) {
onRemove((V)element);
}
entries.clear();
this.support.fireElementsRemoved(0, oldLen, oldEds);
}
}
public V put(K key, V newElement) {
if (key == null) {
throw new IllegalArgumentException("key must not be null");
}
if (newElement == null) {
throw new IllegalArgumentException("element must not be null");
}
V result = entries.put(key, newElement);
if (result == newElement) {
return result;
}
onAdd(newElement);
int index = indexOfKey(key);
if (result != null) {
// an old element with equal key has been replaced
onRemove(result);
support.fireElementsReplaced(index, result);
} else {
// a new element was added
support.fireElementsAdded(index, 1);
}
return result;
}
private void onRemove(V element) {
element.removePropertyChangeListener(this.elementsPcl);
if (element instanceof ContextOwner) {
((ContextOwner)element).getContext().removeParent(getContext());
}
}
private void onAdd(V element) {
element.addPropertyChangeListener(this.elementsPcl);
if (element instanceof ContextOwner) {
((ContextOwner)element).getContext().addParent(getContext());
}
}
// TODO (mk) TEST
public V put(K key, V newElement, int toIndex) {
if (toIndex < 0 || toIndex > entries.size()) {
throw new IndexOutOfBoundsException("toIndex=" + toIndex);
}
V oldElement = entries.get(key);
boolean selected = selectedKeys.contains(key);
if (oldElement == null) {
// this is a new key
entries.put(key, newElement, toIndex);
onAdd(newElement);
support.fireElementsAdded(toIndex, 1);
return null;
} else {
// this was an old key
int oldIndex = entries.indexOfKey(key);
if (oldIndex == toIndex) {
// don't move, just replace
entries.put(key, newElement);
onAdd(newElement);
onRemove(oldElement);
support.fireElementsReplaced(oldIndex, oldElement);
return oldElement;
} else {
// first remove old one
if (selected) {
selection.remove(oldIndex);
}
entries.remove(oldIndex);
onRemove(oldElement);
// then insert new one
support.fireElementsRemoved(oldIndex, oldElement);
entries.put(key, newElement, toIndex);
onAdd(newElement);
support.fireElementsAdded(toIndex, 1);
if (selected) {
selection.addInterval(toIndex, toIndex);
}
return oldElement;
}
}
}
// TODO (mk) TEST
public void swap(int indexA, int indexB) {
if (indexA < 0 || indexA > entries.size()) {
throw new IndexOutOfBoundsException("indexA=" + indexA);
}
if (indexB < 0 || indexB > entries.size()) {
throw new IndexOutOfBoundsException("indexB=" + indexB);
}
if (indexA == indexB) {
return;
}
int index1 = Math.min(indexA, indexB);
int index2 = Math.max(indexA, indexB);
boolean isSelected1 = this.selection.contains(index1);
boolean isSelected2 = this.selection.contains(index2);
K key2 = getKey(index2);
V element2 = removeAt(index2);
K key1 = getKey(index1);
V element1 = removeAt(index1);
this.put(key2, element2, index1);
this.put(key1, element1, index2);
if (isSelected1) {
this.selectedKeys.add(key1);
} else if (selectedKeys.contains(key1)) {
this.selectedKeys.remove(key1);
}
if (isSelected2) {
this.selectedKeys.add(key2);
} else if (selectedKeys.contains(key2)) {
this.selectedKeys.remove(key2);
}
}
/**
* Swaps the position of these elements.
*/
public void swap(V elemA, V elemB) {
int indexA = indexOf(elemA);
int indexB = indexOf(elemB);
swap(indexA, indexB);
}
/**
* Reverses the order of the elements.
*/
public void reverse() {
OrderPreservingMap<K, V> map = new OrderPreservingMap<K, V>(entries);
List keys = new ArrayList(map.orderedKeysReference());
Collections.reverse(keys);
map.reorder(keys);
Set<K> oldSelection = new HashSet<K>(this.selectedKeys);
this.clear();
this.putAll(map);
this.getSelectedKeys().addAll(oldSelection);
}
/** {@inheritDoc} */
public void sortBy(boolean ascending, Path... paths) {
List<SortKey> newSortKeys = new ArrayList<SortKey>();
for (Path path : paths) {
if (path != null) {
newSortKeys.add(new SortKey(ascending, path));
}
}
sortBy(newSortKeys);
}
/** {@inheritDoc} */
public void sortBy(Collection<SortKey> newSortKeys) {
sortBy(newSortKeys.toArray(new SortKey[newSortKeys.size()]));
}
/** {@inheritDoc} */
public void sortBy(SortKey... newSortKeys) {
if (newSortKeys != null && newSortKeys.length == 1 && getSortKeys().size() == 1) {
SortKey invertedSortKey = getSortKeys().iterator().next().invert();
if (invertedSortKey.equals(newSortKeys[0])) {
// the new sort key is the inversion of the old sort key
// -> just reverse the order
reverse();
setSortKeys(newSortKeys);
return;
}
}
OrderPreservingMap<K, V> map = new OrderPreservingMap<K, V>(entries);
getSorter().sortBy(map, newSortKeys);
Set<K> oldSelection = new HashSet<K>(this.selectedKeys);
this.clear();
this.putAll(map);
this.getSelectedKeys().addAll(oldSelection);
setSortKeys(newSortKeys);
}
private void setSortKeys(SortKey[] newSortKeys) {
Collection<SortKey> oldValue = this.sortKeys;
if (newSortKeys == null) {
this.sortKeys = Collections.emptyList();
} else {
this.sortKeys = Collections.unmodifiableCollection(Arrays.asList(newSortKeys));
}
getPropertyChangeSupport().firePropertyChange("sortKeys", oldValue, this.sortKeys);
}
/**
* Returns the (immutable) collection of {@link SortKey} objects that
* reflect the current sorting state of this list.
*/
public Collection<SortKey> getSortKeys() {
return sortKeys;
}
public int size() {
return entries.size();
}
public Collection<V> getAll(int startIndex, int endIndex) {
Collection<V> result = new LinkedList<V>();
for (int i = startIndex; i <= endIndex; ++i) {
result.add(entries.get(i));
}
return result;
}
public Collection<V> getAll(int[] indices) {
Collection<V> result = new LinkedList<V>();
for (int index : indices) {
result.add(entries.get(index));
}
return result;
}
public V getAt(int index) {
return entries.get(index);
}
public V get(K key) {
return entries.get(key);
}
public V removeAt(int index) {
K key = entries.getKey(index);
selectedKeys.remove(key);
V result = entries.remove(index);
onRemove(result);
if (result != null) {
// the element has been removed
Collection<PresentationModel> oldEds = new ArrayList<PresentationModel>();
oldEds.add(result);
support.fireElementsRemoved(index, 1, oldEds);
}
return result;
}
public V removeKey(K key) {
selectedKeys.remove(key);
int index = indexOfKey(key);
if (index >= 0) {
// element found. remove it
V result = entries.remove(key);
assert (result != null);
onRemove(result);
Collection<PresentationModel> oldEds = new ArrayList<PresentationModel>();
oldEds.add(result);
support.fireElementsRemoved(index, 1, oldEds);
return result;
} else {
// element not found
return null;
}
}
public boolean remove(V elem) {
int index = entries.indexOf(elem);
if (index == -1) {
return false;
} else {
selection.remove(elem);
entries.remove(index);
onRemove(elem);
Collection<PresentationModel> removedCol = new ArrayList<PresentationModel>();
removedCol.add(elem);
support.fireElementsRemoved(index, 1, removedCol);
return true;
}
}
// TODO (mk) UNIT TEST
public boolean removeAll(Collection<? extends V> col) {
int[] indexes = indicesOf(col);
if (indexes.length == 0) {
return false;
} else {
return removeAllIndices(indexes).size() > 0;
}
}
public Collection<V> toCollection() {
return entries.toCollection();
}
public Map<K, V> toMap() {
return new OrderPreservingMap<K, V>(entries);
}
public Object[] toArray() {
return entries.toArray();
}
public Set<K> keySet() {
return entries.keySet();
}
public boolean containsKey(K key) {
return entries.containsKey(key);
}
public boolean contains(V element) {
return entries.containsValue(element);
}
public int indexOfKey(K key) {
return entries.indexOfKey(key);
}
public int indexOf(V element) {
return entries.indexOf(element);
}
// TODO (mk) spelling: rename to "indexes"
/**
* Returns an sorted array of all indices of the elements with the given
* keys starting with the smallest index.
*/
public int[] indicesOfKeys(Collection<K> keys) {
final int len = keys.size();
int[] result = new int[len];
int i = 0;
for (K key : keys) {
result[i] = indexOfKey(key);
++i;
}
if (len != i) {
int[] newResult = new int[i];
System.arraycopy(result, 0, newResult, 0, i);
result = newResult;
}
Arrays.sort(result);
return result;
}
/**
* Returns an sorted array of all indices of the given elements starting
* with the smallest index.
*/
public int[] indicesOf(Collection elements) {
final int len = elements.size();
int[] result = new int[len];
int i = 0;
for (Object elem : elements) {
result[i] = indexOf((V)elem);
++i;
}
if (len != i) {
int[] newResult = new int[i];
System.arraycopy(result, 0, newResult, 0, i);
result = newResult;
}
Arrays.sort(result);
return result;
}
public K getKey(V element) {
int index = entries.indexOf(element);
if (index == -1) {
throw new NoSuchElementException("No such element");
}
K result = entries.getKey(index);
return result;
}
public K getKey(int index) {
return this.entries.getKey(index);
}
public List<K> getKeys(int beginIndex, int endIndex) {
List<K> result = new ArrayList<K>();
for (int index = beginIndex; index <= endIndex; ++index) {
K key = getKey(index);
result.add(key);
}
return result;
}
public Set<K> getKeys(int[] indices) {
Set<K> result = new HashSet<K>();
for (int index : indices) {
K key = getKey(index);
result.add(key);
}
return result;
}
/**
* Returns a set of all keys of the elements in the given collection. For
* elements that are not in this map no key is inserted into the result.
*
* @param col
* @return a set of all keys of the elements in the given collection
*/
public Set<K> getKeys(Collection<?> col) {
HashSet<K> result = new HashSet<K>();
for (Object elem : col) {
if (elem instanceof PresentationModel) {
int index = indexOf((V)elem);
if (index != -1) {
K key = getKey(index);
if (key != null) {
result.add(key);
}
}
}
}
return result;
}
private void checkContainsKey(K key) {
if (!entries.containsKey(key)) {
throw new NoSuchElementException("No element with key='" + key + "' in this map");
}
}
public SelectedKeys<K> getSelectedKeys() {
return this.selectedKeys;
}
public Selection<V> getSelection() {
return this.selection;
}
private class SelectionImpl implements Selection<V> {
/** {@inheritDoc} */
public boolean addInterval(int beginIndex, int endIndex) {
if (beginIndex > endIndex) {
throw new IllegalArgumentException("beginIndex > endIndex");
}
ListIterator<K> it = entries.keyListIterator(beginIndex);
HashSet<K> keys = new HashSet<K>();
for (int i = beginIndex; it.hasNext() && i <= endIndex; i++) {
keys.add(it.next());
}
return selectedKeys.addAll(keys);
}
/** {@inheritDoc} */
public boolean setInterval(int beginIndex, int endIndex) {
if (beginIndex > endIndex) {
throw new IllegalArgumentException("beginIndex > endIndex");
}
ListIterator<K> it = entries.keyListIterator(beginIndex);
HashSet<K> keys = new HashSet<K>();
for (int i = beginIndex; it.hasNext() && i <= endIndex; i++) {
keys.add(it.next());
}
return selectedKeys.setAll(keys);
}
/** {@inheritDoc} */
// TODO (mk) UNIT TEST
public boolean setIndexes(int[] selIndices) {
Set<K> newSelection = getKeys(selIndices);
return selectedKeys.setAll(newSelection);
}
/** {@inheritDoc} */
public void clear() {
selectedKeys.clear();
}
/** {@inheritDoc} */
public boolean contains(int index) {
if (index == NONE) {
return false;
}
K key = getKey(index);
return selectedKeys.contains(key);
}
/** {@inheritDoc} */
// TODO (mk) TEST
public int[] getIndexes() {
int[] result = indicesOfKeys(selectedKeys.elements);
return result;
}
/** {@inheritDoc} */
public int[] getIndexes(int beginIndex, int endIndex) {
if (beginIndex > endIndex) {
throw new IllegalArgumentException("beginIndex > endIndex");
}
List<K> keys = getKeys(beginIndex, endIndex);
keys.retainAll(selectedKeys.elements);
int[] result = indicesOfKeys(keys);
return result;
}
/** {@inheritDoc} */
public int getMinIndex() {
return selectedKeys.getMinSelIndex();
}
/** {@inheritDoc} */
// TODO (mk) UNIT TEST
public int getMaxIndex() {
return selectedKeys.getMaxSelIndex();
}
/** {@inheritDoc} */
public V getFirst() {
int firstIndex = this.getMinIndex();
if (firstIndex == -1) {
return null;
} else {
return entries.get(firstIndex);
}
}
/** {@inheritDoc} */
public boolean removeInterval(int beginIndex, int endIndex) {
if (beginIndex > endIndex) {
throw new IllegalArgumentException("beginIndex > endIndex");
}
ListIterator<K> it = entries.keyListIterator(beginIndex);
HashSet<K> keys = new HashSet<K>();
for (int i = beginIndex; it.hasNext() && i <= endIndex; i++) {
keys.add(it.next());
}
return selectedKeys.removeAll(keys);
}
/** {@inheritDoc} */
public boolean add(V o) {
K key = getKey(o);
return selectedKeys.add(key);
}
/** {@inheritDoc} */
public boolean addAll(Collection<? extends V> c) {
Set<K> keys = getKeys(c);
return selectedKeys.addAll(keys);
}
/** {@inheritDoc} */
public boolean contains(Object o) {
if (o instanceof PresentationModel) {
K key = getKey((V)o);
return selectedKeys.contains(key);
} else {
return false;
}
}
/** {@inheritDoc} */
public boolean containsAll(Collection<?> c) {
Set<K> keys = getKeys(c);
if (keys.size() != c.size()) {
return false;
}
return selectedKeys.containsAll(keys);
}
/** {@inheritDoc} */
public boolean isEmpty() {
return selectedKeys.isEmpty();
}
/** {@inheritDoc} */
public Iterator<V> iterator() {
return new Iterator<V>() {
Iterator<K> keyIterator = selectedKeys.iterator();
public boolean hasNext() {
return keyIterator.hasNext();
}
public V next() {
K key = keyIterator.next();
return entries.get(key);
}
public void remove() {
keyIterator.remove();
}
};
}
/** {@inheritDoc} */
public boolean remove(Object o) {
if (o instanceof PresentationModel) {
K key = getKey((V)o);
return selectedKeys.remove(key);
} else {
// nothing to do
return false;
}
}
/** {@inheritDoc} */
public boolean removeAll(Collection<?> c) {
Set<K> keys = getKeys(c);
return selectedKeys.removeAll(keys);
}
/** {@inheritDoc} */
public boolean retainAll(Collection<?> c) {
Set<K> keys = getKeys(c);
return selectedKeys.retainAll(keys);
}
/** {@inheritDoc} */
public void addAll() {
selectedKeys.addAll(entries.orderedKeysReference());
}
/** {@inheritDoc} */
public int size() {
return selectedKeys.size();
}
/** {@inheritDoc} */
public Object[] toArray() {
Set<K> keys = selectedKeys.elements;
return entries.getAll(keys).toArray();
}
/** {@inheritDoc} */
public <T> T[] toArray(T[] a) {
Set<K> keys = selectedKeys.elements;
return entries.getAll(keys).toArray(a);
}
/**
* Returns a new Collection with all selected elements. Modification on
* this collection will not influence the original selection.
*
* @return a new Collection with all selected elements.
*/
public Collection<V> toCollection() {
Set<K> keys = selectedKeys.elements;
return entries.getAll(keys);
}
}
private class SelectedKeysImpl implements SelectedKeys<K> {
/**
* Keys of all selected elements. Iteration ordering is the order in
* which elements were inserted into the set.
*/
Set<K> elements = new LinkedHashSet<K>();
Integer minSelIndex;
Integer maxSelIndex;
public Integer getMinSelIndex() {
if (minSelIndex == null) {
if (isEmpty()) {
minSelIndex = NONE;
} else {
K firstSelectedKey = min();
int index = indexOfKey(firstSelectedKey);
minSelIndex = index;
}
}
Integer result = minSelIndex;
minSelIndex = null; // TODO (mk) enable caching for minSelIndex
return result;
}
public Integer getMaxSelIndex() {
if (maxSelIndex == null) {
if (isEmpty()) {
maxSelIndex = NONE;
} else {
K lastSelectedKey = max();
int index = indexOfKey(lastSelectedKey);
maxSelIndex = index;
}
}
Integer result = maxSelIndex;
maxSelIndex = null; // TODO (mk) enable caching for maxSelIndex
return result;
}
/**
* {@inheritDoc}
*/
public K getFirst() {
return min();
}
public K min() {
if (elements.isEmpty()) {
return null;
} else if (elements.size() == 1) {
return elements.iterator().next();
} else {
ListIterator<K> it = entries.keyListIterator(0);
while (it.hasNext()) {
K key = it.next();
if (elements.contains(key)) {
return key;
}
}
}
return null;
}
public K max() {
if (elements.isEmpty()) {
return null;
} else if (elements.size() == 1) {
return elements.iterator().next();
} else {
ListIterator<K> it = entries.keyListIterator(entries.size());
while (it.hasPrevious()) {
K key = it.previous();
if (elements.contains(key)) {
return key;
}
}
}
return null;
}
/**
* {@inheritDoc}
*/
public boolean add(K key) {
// check if key is valid
int index = indexOfKey(key);
// index <0 means it doesn't exists in the outer map.
if (index < 0) {
throw new NoSuchElementException("Can't select unknown element: key=" + key);
}
boolean result = elements.add(key);
if (result) {
support.fireElementsSelected(index, 1);
}
return result;
}
/**
* {@inheritDoc}
*/
public boolean addAll(Collection<? extends K> c) {
Collection<K> diff = new LinkedHashSet<K>(c);
diff.removeAll(elements);
if (!diff.isEmpty()) {
// check for existence of all elements.
int[] indices = indicesOfKeys(diff);
for (int index : indices) {
if (index < 0) {
// index <0 means it doesn't exists in the outer map.
// TODO (mk) we should include the invalid key into the
// exception message
throw new IllegalArgumentException("key is no element of this map model");
}
}
elements.addAll(diff);
support.fireElementsSelected(indices);
return true;
} else {
// nothing to do
return false;
}
}
/**
* {@inheritDoc}
*/
public void clear() {
int[] indices = indicesOfKeys(this);
elements.clear();
support.fireElementsDeselected(indices);
}
/**
* {@inheritDoc}
*/
public boolean contains(Object o) {
return elements.contains(o);
}
/**
* {@inheritDoc}
*/
public boolean containsAll(Collection<?> c) {
return elements.containsAll(c);
}
/**
* {@inheritDoc}
*/
public boolean isEmpty() {
return elements.isEmpty();
}
/**
* {@inheritDoc}
*/
public Iterator<K> iterator() {
return new Iterator<K>() {
private final Iterator<K> impl = elements.iterator();
private K last;
public boolean hasNext() {
return impl.hasNext();
}
public K next() {
last = impl.next();
return last;
}
public void remove() {
int index = indexOfKey(last);
impl.remove();
support.fireElementsDeselected(new int[] { index });
}
};
}
/**
* {@inheritDoc}
*/
public boolean remove(Object o) {
boolean result = elements.remove(o);
// we won't throw any exception for invalid elements
if (result) {
int index = indexOfKey((K)o);
assert (index >= 0);
support.fireElementsDeselected(new int[] { index });
}
return result;
}
/**
* {@inheritDoc}
*/
public boolean removeAll(Collection<?> c) {
Collection diff = new LinkedHashSet(c);
// we won't throw any exception for invalid elements
// -> get rid of all elements in c that are not element of this
// selectedKeys
diff.retainAll(elements);
if (!diff.isEmpty()) {
boolean changed = elements.removeAll(diff);
if (changed) {
int[] indices = indicesOfKeys(diff);
support.fireElementsDeselected(indices);
}
return changed;
} else {
// nothing to do
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean retainAll(Collection<?> c) {
Collection diff = new LinkedHashSet(elements);
diff.removeAll(c);
if (!diff.isEmpty()) {
return this.removeAll(diff);
} else {
// nothing to do
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean setAll(K... keys) {
return setAll(Arrays.asList(keys));
}
/**
* {@inheritDoc}
*/
public boolean setAll(Collection<?> c) {
Collection toRemove = new LinkedHashSet(elements);
toRemove.removeAll(c);
Collection toAdd = new LinkedHashSet(c);
toAdd.removeAll(elements);
boolean changed = false;
if (!toRemove.isEmpty()) {
boolean removed = elements.removeAll(toRemove);
if (removed) {
int[] indices = indicesOfKeys(toRemove);
support.fireElementsDeselected(indices);
}
changed = removed;
}
if (!toAdd.isEmpty()) {
int[] indices = indicesOfKeys(toAdd);
for (int index : indices) {
if (index < 0) {
// index <0 means it doesn't exists in the outer map.
// TODO (mk) we should include the invalid key into the
// exception message
throw new IllegalArgumentException("key is no element of this map model");
}
}
boolean added = elements.addAll(toAdd);
if (added) {
support.fireElementsSelected(indices);
}
changed = changed || added;
}
return changed;
}
/**
* {@inheritDoc}
*/
public int size() {
return elements.size();
}
/**
* {@inheritDoc}
*/
public Object[] toArray() {
return elements.toArray();
}
/**
* {@inheritDoc}
*/
public <T> T[] toArray(T[] a) {
return elements.toArray(a);
}
/**
* {@inheritDoc}
*/
public Collection<K> toCollection() {
return new ArrayList<K>(elements);
}
}
public void addListListener(ListListener l) {
this.support.addListListener(l);
}
public void removeListListener(ListListener l) {
this.support.removeListListener(l);
}
private void onElementChanged(PropertyChangeEvent evt) {
if ("modified".equals(evt.getPropertyName()) == false) {
V element = (V)evt.getSource();
this.onElementChanged(element, evt);
}
}
private void onElementChanged(V element, EventObject cause) {
int index = indexOf(element);
// fire a property change event to inform all listeners up to the
// root(s)
this.support.fireElementChanged(index, cause);
}
public Iterator<V> iterator() {
return new Iterator<V>() {
int next = 0;
int last = -1;
public boolean hasNext() {
return next < entries.size();
}
public V next() {
V result = entries.get(next);
last = next;
next++;
return result;
}
public void remove() {
removeAt(last);
next = last;
last = -1;
}
};
}
// TODO (mk) TEST
public ListIterator<V> listIterator(final int index) {
return new ListIterator<V>() {
int next = index;
int last = -1;
public void add(V o) {
throw new UnsupportedOperationException("'add' ist not supported by this ListIterator");
}
public boolean hasNext() {
return next < entries.size();
}
public boolean hasPrevious() {
return next - 1 >= 0;
}
public V next() {
V result = entries.get(next);
last = next;
next++;
return result;
}
public int nextIndex() {
return next;
}
public V previous() {
int previous = next - 1;
V result = entries.get(previous);
last = previous;
next--;
return result;
}
public int previousIndex() {
return next - 1;
}
public void remove() {
removeAt(last);
next = last;
last = -1;
}
public void set(V o) {
throw new UnsupportedOperationException("'set' ist not supported by this ListIterator");
}
};
}
public Iterator<K> keyiterator() {
return new Iterator<K>() {
int next = 0;
int last = -1;
public boolean hasNext() {
return next < entries.size();
}
public K next() {
K result = entries.getKey(next);
last = next;
next++;
return result;
}
public void remove() {
removeAt(last);
next = last;
last = -1;
}
};
}
public Collection<V> removeAllKeys(Set<K> keySet) {
int[] indices = indicesOfKeys(keySet);
Collection<V> result = this.removeAllIndices(indices);
return result;
}
public Collection<V> retainAllKeys(Set<K> keySet) {
Set<K> toRemove = entries.keySet();
toRemove.removeAll(keySet);
return removeAllKeys(toRemove);
}
public Collection<V> removeAllIndices(int[] indices) {
Interval[] aintv = Interval.createIntervals(indices);
LinkedList<V> result = new LinkedList<V>();
for (int iv = aintv.length - 1; iv >= 0; --iv) {
Interval intv = aintv[iv];
selection.removeInterval(intv.startIndex, intv.endIndex);
LinkedList<V> removed = new LinkedList<V>();
for (int i = intv.endIndex; i >= intv.startIndex; --i) {
V element = entries.remove(i);
onRemove(element);
removed.add(element);
}
support.fireElementsRemoved(intv.startIndex, intv.endIndex - intv.startIndex + 1, (Collection<PresentationModel>)removed);
result.addAll(removed);
}
return result;
}
public void putAll(Map<K, V> aMap) {
HashSet<K> duplicateKeys = new HashSet<K>(aMap.keySet());
duplicateKeys.retainAll(this.entries.keySetReference());
// first: replace all elements with duplicate keys
int[] indices = indicesOfKeys(duplicateKeys);
Interval[] aintv = Interval.createIntervals(indices);
for (Interval intv : aintv) {
LinkedList<V> replaced = new LinkedList<V>();
for (int index = intv.startIndex; index <= intv.endIndex; index++) {
K key = getKey(index);
V newValue = aMap.get(key);
V old = this.entries.put(key, newValue);
onRemove(old);
replaced.add(old);
onAdd(newValue);
}
support.fireElementsReplaced(intv.startIndex, intv.endIndex - intv.startIndex + 1, (Collection<PresentationModel>)replaced);
}
// second: add all elements with new keys
Map<K, V> newMap = new HashMap<K, V>(aMap);
newMap.keySet().removeAll(duplicateKeys);
if (newMap.size() > 0) {
int beginIndex = entries.size();
entries.putAll(aMap);
for (V element : newMap.values()) {
onAdd(element);
}
support.fireElementsAdded(beginIndex, newMap.size());
}
}
public void putAll(Collection<Entry<K, V>> aEntries) {
Map<? extends K, ? extends V> aMap = toMap(aEntries);
HashSet<K> duplicateKeys = new HashSet<K>(aMap.keySet());
duplicateKeys.retainAll(this.entries.keySetReference());
// first: replace all elements with duplicate keys
int[] indices = indicesOfKeys(duplicateKeys);
Interval[] aintv = Interval.createIntervals(indices);
for (Interval intv : aintv) {
LinkedList<V> replaced = new LinkedList<V>();
for (int index = intv.startIndex; index <= intv.endIndex; index++) {
K key = getKey(index);
V newValue = aMap.get(key);
V old = this.entries.put(key, newValue);
onRemove(old);
replaced.add(old);
onAdd(newValue);
}
support.fireElementsReplaced(intv.startIndex, intv.endIndex - intv.startIndex + 1, (Collection<PresentationModel>)replaced);
}
// second: add all elements with new keys
Collection<Entry<K, V>> newEntries = removeAllKeys(aEntries, duplicateKeys);
if (newEntries.size() > 0) {
int beginIndex = entries.size();
entries.putAll(newEntries);
for (Entry<K, V> entry : newEntries) {
onAdd(entry.getValue());
}
support.fireElementsAdded(beginIndex, newEntries.size());
}
}
private Collection<Entry<K, V>> removeAllKeys(Collection<Entry<K, V>> aEntries, HashSet<K> keys) {
Collection<Entry<K, V>> result = new ArrayList<Entry<K, V>>();
for (Entry<K, V> entry : aEntries) {
if (!keys.contains(entry.getKey())) {
result.add(entry);
}
}
return result;
}
private Map<? extends K, ? extends V> toMap(Collection<Entry<K, V>> aEntries) {
Map<K, V> result = new HashMap<K, V>();
for (Entry<K, V> entry : aEntries) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
public void setAll(Map<K, V> newMap) {
retainAllKeys(newMap.keySet());
putAll(newMap);
}
/**
* This rule evaluates to invalid if at least one of the list elements is
* invalid.
*
* @author Michael Karneim
*/
public class ListElementsValidationRule implements ValidationRule {
/** {@inheritDoc} */
public ValidationState validate() {
if (isEmpty()) {
return null;
}
for (V element : MapPM.this) {
if (element.isValid() == false) {
String message = resourceBundle.getString(KEY_MESSAGE_INVALID_ELEMENTS);
return new ValidationState(message);
}
}
return null;
}
}
}