/*
* 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.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
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.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.ResourceBundleFactory;
import org.beanfabrics.validation.ValidationRule;
import org.beanfabrics.validation.ValidationState;
/**
* The ListPM is a list 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 ListPM<T extends PresentationModel> extends AbstractPM implements IListPM<T> {
protected static final String KEY_MESSAGE_INVALID_ELEMENTS = "message.invalidElements";
private final ResourceBundle resourceBundle = ResourceBundleFactory.getBundle(ListPM.class);
private static final Integer UNKNOWN = null;
private static final int NONE = -1;
private final ListSupport support = new ListSupport(this);
private final SelectionImpl selection = new SelectionImpl();
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);
}
};
protected boolean revalidateElementsOnChangeEnabled = false;
/**
* This list holds all elements of this <code>ListPM</code>. All elements
* are wrapped into {@link Entry} objects that hold a
* {@link Entry#isSelected} flag. This list is an {@link ArrayList} to
* ensure quick index-base access.
*/
private final List<Entry> entries;
/**
* 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();
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ListPM() {
this(10);
}
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list.
*/
public ListPM(int initialCapacity) {
this(new ArrayList<Entry>(initialCapacity));
}
/**
* Constructs a <code>ListPM</code> with the specified list of entries.
*
* @param list the initial list of entries
*/
public ListPM(ArrayList<Entry> list) {
this((List<Entry>)list);
}
/**
* Constructs a <code>ListPM</code> with the specified list of entries.
*
* @param list the initial list of entries
*/
protected ListPM(List<Entry> list) {
entries = list;
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
}
public void addListListener(ListListener l) {
support.addListListener(l);
}
public void removeListListener(ListListener l) {
support.removeListListener(l);
}
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 <code>ListPM</code>.
*/
// TODO (mk) TEST
public void revalidateElements() {
revalidateAllExcept(null);
}
protected void revalidateAllExcept(T element) {
for (Entry entry : entries) {
if (element == entry.element) {
continue;
}
//PropertySupport.get(entry.element).revalidateProperties();
entry.element.revalidate();
}
}
// TODO (mk) UNIT TEST
public void replace(T oldElement, T newElement) {
int index = indexOf(oldElement);
replace(index, newElement);
}
// TODO (mk) UNIT TEST
public void replace(int index, T newElement) {
Entry newEntry = new Entry(newElement);
Entry oldEntry = entries.remove(index);
newEntry.isSelected = oldEntry.isSelected;
entries.add(index, newEntry);
onRemove(oldEntry.element);
onAdd(newElement);
support.fireElementsReplaced(index, oldEntry.element);
}
public void add(T element) {
int nextIndex = entries.size();
Entry entry = new Entry(element);
entries.add(entry);
onAdd(element);
support.fireElementsAdded(nextIndex, 1);
}
private void onAdd(T element) {
element.addPropertyChangeListener(this.elementsPcl);
if (element instanceof ContextOwner) {
((ContextOwner)element).getContext().addParent(getContext());
}
}
private void onRemove(T element) {
element.removePropertyChangeListener(this.elementsPcl);
if (element instanceof ContextOwner) {
((ContextOwner)element).getContext().removeParent(getContext());
}
}
public void add(int index, T element) {
if (index < 0 || index > entries.size()) {
throw new IndexOutOfBoundsException("index=" + index);
}
Entry entry = new Entry(element);
entries.add(index, entry);
// update min and max
// if (selection.selectionSize>0) {
// if (selection.minSelIndex!=UNKNOWN && selection.minSelIndex>=index) {
// selection.minSelIndex++;
// }
// if (selection.maxSelIndex!=UNKNOWN && selection.maxSelIndex<=index) {
// selection.maxSelIndex++;
// }
// }
onAdd(element);
support.fireElementsAdded(index, 1);
}
// TODO (mk) TEST
public void addAll(Collection<T> col) {
int startIndex = size();
for (T elem : col) {
Entry entry = new Entry(elem);
entries.add(entry);
onAdd(elem);
}
support.fireElementsAdded(startIndex, col.size());
}
public boolean contains(T element) {
for (Entry e : entries) {
if (e.element == element) {
return true;
}
}
return false;
}
public boolean containsAll(Collection<T> col) {
if (col.isEmpty()) {
return true;
} else if (col.size() == 1) {
return contains(col.iterator().next());
} else {
}
Set<T> set = new HashSet<T>();
for (Entry e : entries) {
set.add(e.element);
}
return set.containsAll(col);
}
public void clear() {
if (entries.isEmpty()) {
return; // nothing to do
} else {
this.selection.clear();
int len = entries.size();
Collection<PresentationModel> removed = (Collection<PresentationModel>)this.toCollection();
for (Entry e : entries) {
onRemove(e.element);
}
this.entries.clear();
support.fireElementsRemoved(0, len, removed);
}
}
// TODO (mk) write TEST
public boolean removeAll(Collection<? extends T> col) {
int[] indexes = indicesOf(col);
Interval[] intv = Interval.createIntervals(indexes);
// we process the intervals backwards
for (int i = intv.length - 1; i >= 0; --i) {
Interval iv = intv[i];
this.selection.removeInterval(iv.startIndex, iv.endIndex);
List<PresentationModel> removed = new LinkedList<PresentationModel>();
for (int index = iv.endIndex; index >= iv.startIndex; --index) {
Entry e = entries.remove(index);
onRemove(e.element);
removed.add(e.element);
}
support.fireElementsRemoved(iv.startIndex, iv.endIndex - iv.startIndex + 1, removed);
}
return indexes.length > 0;
}
public boolean remove(T element) {
int index = indexOf(element);
if (index == NONE) {
return false; // nothing found, nothing changed.
}
removeAt(index);
return true;
}
public T removeAt(int index) {
if (index < 0 || index >= entries.size()) {
throw new IndexOutOfBoundsException("index=" + index);
}
Entry e = entries.get(index);
assert (e != null);
if (e.isSelected) {
e.isSelected = false;
selection.selectionSize--;
// update min and max
// if (selection.selectionSize>0) {
// if (selection.minSelIndex != UNKNOWN && selection.minSelIndex >= index) {
// selection.minSelIndex = UNKNOWN;
// }
// if (selection.maxSelIndex != UNKNOWN && selection.maxSelIndex >= index) {
// selection.maxSelIndex = UNKNOWN;
// }
// } else {
// selection.minSelIndex = NONE;
// selection.maxSelIndex = NONE;
// }
support.fireElementsDeselected(index, 1);
}
onRemove(e.element);
entries.remove(index);
support.fireElementsRemoved(index, e.element);
return e.element;
}
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);
Entry e1 = entries.get(index1);
assert (e1 != null);
T elem1 = e1.element;
boolean e1Selected = e1.isSelected;
Entry e2 = entries.get(index2);
assert (e2 != null);
T elem2 = e2.element;
boolean e2Selected = e2.isSelected;
this.removeAt(index2);
this.removeAt(index1);
this.add(index1, elem2);
this.add(index2, elem1);
if (e1Selected) {
selection.add(elem1);
} else if (selection.contains(index2)) {
selection.remove(elem1);
}
if (e2Selected) {
selection.add(elem2);
} else if (selection.contains(index1)) {
selection.remove(elem2);
}
}
public void swap(T elemA, T elemB) {
int indexA = indexOf(elemA);
int indexB = indexOf(elemB);
swap(indexA, indexB);
}
/**
* Reverses the order of the elements.
*/
public void reverse() {
List<T> list = new ArrayList<T>(this.toCollection());
Collections.reverse(list);
Collection<T> oldSelection = new ArrayList<T>(this.selection);
this.clear();
this.addAll(list);
this.selection.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;
}
}
ArrayList<T> list = new ArrayList<T>();
for (Entry entry : entries) {
list.add(entry.element);
}
getSorter().sortBy(list, newSortKeys);
Collection<T> oldSelection = new ArrayList<T>(this.selection);
this.clear();
this.addAll(list);
this.selection.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 T getAt(int index) {
return entries.get(index).element;
}
public Collection<T> toCollection() {
ArrayList<T> result = new ArrayList<T>();
for (Entry entry : entries) {
result.add(entry.element);
}
return result;
}
public Object[] toArray() {
Object[] result = new Object[size()];
int i = 0;
for (Entry entry : entries) {
result[i++] = entry.element;
}
return result;
}
public Selection<T> getSelection() {
return this.selection;
}
public int indexOf(T element) {
if (element == null) {
return NONE;
}
int i = 0;
for (Entry entry : entries) {
if (element.equals(entry.element)) {
return i;
}
++i;
}
return NONE;
}
/**
* Returns a sorted array of all indices of the given elements starting with
* the smallest index.
*
* @param col all elements to get the index from
* @return a sorted array of all indices of the given elements
*/
public int[] indicesOf(Collection<? extends T> col) {
if (col == null || col.size() == 0 || isEmpty()) {
return new int[0];
}
final int len = col.size();
int[] result = new int[len];
int count = 0;
for (T elem : col) {
int index = indexOf(elem);
if (index != -1) {
result[count] = index;
count++;
}
}
if (count != len) {
int[] newResult = new int[count];
System.arraycopy(result, 0, newResult, 0, count);
result = newResult;
}
Arrays.sort(result);
return result;
}
public int size() {
return entries.size();
}
public boolean isEmpty() {
return entries == null || entries.isEmpty();
}
private void onElementChanged(PropertyChangeEvent evt) {
if ("modified".equals(evt.getPropertyName()) == false) {
T element = (T)evt.getSource();
this.onElementChanged(element, evt);
}
}
private void onElementChanged(T 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);
}
// TODO (mk) write TEST
public Iterator<T> iterator() {
return new Iterator<T>() {
int next = 0;
int last = NONE;
public boolean hasNext() {
return next < entries.size();
}
public T next() {
T result = entries.get(next).element;
last = next;
next++;
return result;
}
public void remove() {
removeAt(last);
next = last;
last = NONE;
}
};
}
// TODO (mk) TEST
public ListIterator<T> listIterator(final int index) {
return new ListIterator<T>() {
int next = index;
int last = NONE;
public void add(T 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 T next() {
T result = entries.get(next).element;
last = next;
next++;
return result;
}
public int nextIndex() {
return next;
}
public T previous() {
int previous = next - 1;
T result = entries.get(previous).element;
last = previous;
next--;
return result;
}
public int previousIndex() {
return next - 1;
}
public void remove() {
removeAt(last);
next = last;
last = NONE;
}
public void set(T o) {
throw new UnsupportedOperationException("'set' ist not supported by this ListIterator");
}
};
}
private class SelectionImpl implements Selection<T> {
int selectionSize = 0;
// private Integer minSelIndex = NONE;
// private Integer maxSelIndex = NONE;
/** {@inheritDoc} */
public void addAll() {
List<Integer> indices = new LinkedList<Integer>();
int index = 0;
for (Entry entry : entries) {
if (entry.isSelected == false) {
entry.isSelected = true;
selectionSize++;
indices.add(index);
}
index++;
}
// // update min and max
// if (selectionSize>0) {
// minSelIndex = 0;
// maxSelIndex = entries.size()-1;
// } else {
// minSelIndex = NONE;
// maxSelIndex = NONE;
// }
// TODO (mk) hmm. I know that this event could be fired easily, by just
// providing startIndex=0, length=size(). But is that ok? Any drawbacks?
if (!indices.isEmpty()) {
support.fireElementsSelected(indices);
}
}
/** {@inheritDoc} */
public boolean setInterval(int beginIndex, int endIndex) {
if (beginIndex > endIndex) {
throw new IllegalArgumentException("beginIndex > endIndex");
}
List<Integer> addIndices = new LinkedList<Integer>();
List<Integer> removeIndices = new LinkedList<Integer>();
int index = 0;
for (Entry entry : entries) {
if (beginIndex <= index && index <= endIndex) {
if (entry.isSelected == false) {
entry.isSelected = true;
selectionSize++;
addIndices.add(index);
}
} else {
if (entry.isSelected == true) {
entry.isSelected = false;
selectionSize--;
removeIndices.add(index);
}
}
index++;
}
// update min and max
// if (selectionSize>0) {
// minSelIndex = beginIndex;
// maxSelIndex = endIndex;
// } else {
// minSelIndex = NONE;
// maxSelIndex = NONE;
// }
if (!removeIndices.isEmpty()) {
support.fireElementsDeselected(removeIndices);
}
if (!addIndices.isEmpty()) {
support.fireElementsSelected(addIndices);
}
return !removeIndices.isEmpty() || !addIndices.isEmpty();
}
/**
* {@inheritDoc} TODO (mk) UNIT TEST
*/
public boolean setIndexes(int[] selIndices) {
Set<Integer> indexSet = new HashSet<Integer>();
for (int index : selIndices) {
indexSet.add(index);
}
List<Integer> addIndices = new LinkedList<Integer>();
List<Integer> removeIndices = new LinkedList<Integer>();
int index = 0;
// minSelIndex = NONE;
// maxSelIndex = NONE;
for (Entry entry : entries) {
if (indexSet.contains(index)) {
if (entry.isSelected == false) {
entry.isSelected = true;
selectionSize++;
addIndices.add(index);
}
// if (minSelIndex == NONE || minSelIndex > index) {
// minSelIndex = index;
// }
// if (maxSelIndex == NONE || maxSelIndex < index) {
// maxSelIndex = index;
// }
} else {
if (entry.isSelected == true) {
entry.isSelected = false;
selectionSize--;
removeIndices.add(index);
}
}
index++;
}
if (!removeIndices.isEmpty()) {
support.fireElementsDeselected(removeIndices);
}
if (!addIndices.isEmpty()) {
support.fireElementsSelected(addIndices);
}
return !removeIndices.isEmpty() || !addIndices.isEmpty();
}
/** {@inheritDoc} */
// TODO (mk) UNIT TEST
public int[] getIndexes() {
int[] result = new int[selectionSize];
int entryIndex = 0;
int selPos = 0;
for (Entry entry : entries) {
if (entry.isSelected) {
result[selPos] = entryIndex;
selPos++;
}
entryIndex++;
}
return result;
}
/** {@inheritDoc} */
// TODO (mk) UNIT TEST
public int[] getIndexes(int beginIndex, int endIndex) {
if (beginIndex > endIndex) {
throw new IllegalArgumentException("beginIndex > endIndex");
}
int[] tmpresult = new int[selectionSize];
ListIterator<Entry> it = entries.listIterator(beginIndex);
int entryIndex = beginIndex;
int selPos = 0;
while (it.hasNext() && entryIndex <= endIndex) {
Entry entry = it.next();
if (entry.isSelected) {
tmpresult[selPos] = entryIndex;
selPos++;
}
entryIndex++;
}
int[] result = new int[selPos];
System.arraycopy(tmpresult, 0, result, 0, result.length);
return result;
}
/** {@inheritDoc} */
public boolean addInterval(int beginIndex, int endIndex) {
if (beginIndex > endIndex) {
throw new IllegalArgumentException("beginIndex > endIndex");
}
ListIterator<Entry> listIt = entries.listIterator(beginIndex);
int index = beginIndex;
List<Integer> indices = new LinkedList<Integer>();
while (listIt.hasNext() && index <= endIndex) {
Entry e = listIt.next();
if (e.isSelected == false) {
e.isSelected = true;
selectionSize++;
indices.add(index);
}
index++;
}
// update min and max
// if (!indices.isEmpty()) {
// if (selectionSize>0) {
// if (minSelIndex==UNKNOWN || minSelIndex==NONE || minSelIndex>beginIndex) {
// minSelIndex = beginIndex;
// }
// if (maxSelIndex==UNKNOWN || maxSelIndex==NONE || maxSelIndex<endIndex) {
// maxSelIndex = endIndex;
// }
// } else {
// minSelIndex = NONE;
// maxSelIndex = NONE;
// }
// } // else nothing changed -> no update needed
// TODO (mk) hmm. I know that this event could be fired easily, by just
// providing startIndex=beginIndex, length=endIndex-beginIndex. But is that ok? Any drawbacks?
if (!indices.isEmpty()) {
support.fireElementsSelected(indices);
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
public boolean contains(int index) {
if (index >= entries.size() || index < 0) {
return false;
}
Entry e = entries.get(index);
return e.isSelected;
}
/** {@inheritDoc} */
public T getFirst() {
int index = getMinIndex();
if (index == NONE) {
return null;
} else {
return entries.get(index).element;
}
}
/** {@inheritDoc} */
public int getMinIndex() {
// if (minSelIndex!=UNKNOWN) {
// return minSelIndex;
// }
int index = 0;
for (Entry entry : entries) {
if (entry.isSelected) {
// minSelIndex = index;
// return minSelIndex;
return index;
}
index++;
}
// minSelIndex = NONE;
// return minSelIndex;
return NONE;
}
/** {@inheritDoc} */
public int getMaxIndex() {
// if (maxSelIndex != UNKNOWN) {
// return maxSelIndex;
// }
ListIterator<Entry> it = entries.listIterator(entries.size());
for (int index = entries.size() - 1; it.hasPrevious(); index--) {
Entry entry = it.previous();
if (entry.isSelected) {
// maxSelIndex = index;
// return maxSelIndex;
return index;
}
}
// maxSelIndex = NONE;
// return maxSelIndex;
return NONE;
}
/**
* Returns the index of the next selected element AFTER the given index,
* or -1 if no element is found.
*
* @param index
* @return the index of the next selected element after the given index,
* or -1 if no element is found.
*/
public int getNextIndex(int index) {
if (index < 0 || index >= entries.size()) {
throw new IndexOutOfBoundsException("index=" + index);
}
if (selectionSize == 0 || index == entries.size() - 1) {
return NONE;
}
// if (maxSelIndex != UNKNOWN && maxSelIndex<=index) {
// return NONE;
// }
ListIterator<Entry> it = entries.listIterator(++index);
while (it.hasNext()) {
Entry entry = it.next();
if (entry.isSelected) {
return index;
}
index++;
}
return NONE;
}
/** {@inheritDoc} */
public boolean removeInterval(int beginIndex, int endIndex) {
if (beginIndex > endIndex) {
throw new IllegalArgumentException("beginIndex > endIndex");
}
ListIterator<Entry> listIt = entries.listIterator(beginIndex);
int index = beginIndex;
List<Integer> indices = new LinkedList<Integer>();
while (listIt.hasNext() && index <= endIndex) {
Entry e = listIt.next();
if (e.isSelected == true) {
e.isSelected = false;
selectionSize--;
indices.add(index);
}
index++;
}
// update min and max
// if (!indices.isEmpty()) {
// if (selectionSize>0) {
// assert(minSelIndex!=NONE);
// if (minSelIndex!=UNKNOWN && minSelIndex>=beginIndex) {
// minSelIndex = UNKNOWN;
// }
// assert(maxSelIndex!=NONE);
// if (maxSelIndex!=UNKNOWN && maxSelIndex<=endIndex) {
// maxSelIndex = UNKNOWN;
// }
// } else {
// minSelIndex = NONE;
// maxSelIndex = NONE;
// }
// } // else nothing changed -> no updated needed
if (!indices.isEmpty()) {
support.fireElementsDeselected(indices);
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
public boolean add(T o) {
int index = indexOf(o);
if (index == NONE) {
throw new NoSuchElementException("Can't select unknown element: o=" + o);
}
Entry e = entries.get(index);
if (e.isSelected == false) {
e.isSelected = true;
selectionSize++;
// // update min and max
// if (minSelIndex==UNKNOWN || minSelIndex==NONE || minSelIndex>index) {
// minSelIndex = index;
// }
// if (maxSelIndex==UNKNOWN || maxSelIndex==NONE || maxSelIndex<index) {
// maxSelIndex = index;
// }
support.fireElementsSelected(index, 1);
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
public boolean addAll(Collection<? extends T> c) {
Set argSet;
if (c instanceof Set) {
argSet = (Set)c;
} else {
argSet = new HashSet(c);
}
List<Integer> indices = new LinkedList<Integer>();
int index = 0;
for (Entry e : entries) {
if (argSet.remove(e.element)) {
if (e.isSelected == false) {
e.isSelected = true;
selectionSize++;
// update min and max
// if (minSelIndex==UNKNOWN || minSelIndex==NONE || minSelIndex>index) {
// minSelIndex = index;
// }
// if (maxSelIndex==UNKNOWN || maxSelIndex==NONE || maxSelIndex<index) {
// maxSelIndex = index;
// }
indices.add(index);
}
}
index++;
}
if (!indices.isEmpty()) {
support.fireElementsSelected(indices);
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
public void clear() {
boolean changed = false;
for (Entry e : entries) {
if (e.isSelected) {
e.isSelected = false;
changed = true;
selectionSize--;
}
}
assert (selectionSize == 0);
// update min and max
// minSelIndex = NONE;
// maxSelIndex = NONE;
if (changed) {
// TODO (mk) is this 'generic' event allowed here even if
// not all elements were selected?
support.fireElementsDeselected(0, entries.size());
}
}
/** {@inheritDoc} */
public boolean contains(Object o) {
for (Entry e : entries) {
if (e.element.equals(o)) {
return e.isSelected;
}
}
return false;
}
/** {@inheritDoc} */
public boolean containsAll(Collection<?> c) {
Set argSet;
if (c instanceof Set) {
argSet = (Set)c;
} else {
argSet = new HashSet(c);
}
for (Entry e : entries) {
if (argSet.remove(e.element)) {
if (e.isSelected == false) {
return false;
} else {
continue;
}
} else {
return false;
}
}
return true;
}
/** {@inheritDoc} */
public boolean isEmpty() {
return selectionSize == 0;
}
/** {@inheritDoc} */
public Iterator<T> iterator() {
return new Iterator<T>() {
int next = getMinIndex();
int last;
public boolean hasNext() {
return next != NONE;
}
public T next() {
Entry e = entries.get(next);
last = next;
next = getNextIndex(next);
return e.element;
}
public void remove() {
Entry e = entries.get(last);
e.isSelected = false;
selectionSize--;
// update min and max
// if (selectionSize==0) {
// minSelIndex = NONE;
// maxSelIndex = NONE;
// } else {
// if (minSelIndex != UNKNOWN && minSelIndex == last) {
// minSelIndex = UNKNOWN;
// }
// if (maxSelIndex != UNKNOWN && maxSelIndex == last) {
// maxSelIndex = UNKNOWN;
// }
// }
support.fireElementsDeselected(last, 1);
}
};
}
/** {@inheritDoc} */
public boolean remove(Object o) {
int index = 0;
for (Entry e : entries) {
if (e.element.equals(o)) {
if (e.isSelected == true) {
e.isSelected = false;
selectionSize--;
// update min and max
// if (selectionSize==0) {
// minSelIndex = NONE;
// maxSelIndex = NONE;
// } else {
// if (minSelIndex == null || minSelIndex==index) {
// minSelIndex = UNKNOWN;
// }
// if (maxSelIndex == null || maxSelIndex == index) {
// maxSelIndex = UNKNOWN;
// }
// }
support.fireElementsDeselected(index, 1);
return true;
} else {
return false;
}
}
index++;
}
return false;
}
/** {@inheritDoc} */
public boolean removeAll(Collection<?> c) {
Set argSet;
if (c instanceof Set) {
argSet = (Set)c;
} else {
argSet = new HashSet(c);
}
List<Integer> indices = new LinkedList<Integer>();
int index = 0;
for (Entry e : entries) {
if (argSet.remove(e.element)) {
if (e.isSelected) {
e.isSelected = false;
selectionSize--;
// update min and max
// if (selectionSize == 0) {
// minSelIndex = NONE;
// maxSelIndex = NONE;
// } else {
// if (minSelIndex != UNKNOWN && minSelIndex == index) {
// minSelIndex = UNKNOWN;
// }
// if (maxSelIndex != UNKNOWN && maxSelIndex == index) {
// maxSelIndex = UNKNOWN;
// }
// }
indices.add(index);
}
} // else ignore, since the result is not affected
index++;
}
if (!indices.isEmpty()) {
support.fireElementsDeselected(indices);
return true;
} else {
return false;
}
}
/** {@inheritDoc} */
public boolean retainAll(Collection<?> c) {
Collection diff = new LinkedHashSet(this);
diff.removeAll(c);
if (!diff.isEmpty()) {
return this.removeAll(diff);
} else {
// nothing to do
return false;
}
}
/** {@inheritDoc} */
public int size() {
return selectionSize;
}
/** {@inheritDoc} */
public Object[] toArray() {
Object[] result = new Object[selectionSize];
int index = 0;
for (Entry e : entries) {
if (e.isSelected) {
result[index] = e.element;
index++;
}
}
return result;
}
/** {@inheritDoc} */
public <T> T[] toArray(T[] a) {
if (a.length != selectionSize) {
a = (T[])Array.newInstance(a.getClass().getComponentType(), selectionSize);
}
int index = 0;
for (Entry e : entries) {
if (e.isSelected) {
a[index] = (T)e.element;
index++;
}
}
return 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<T> toCollection() {
return new ArrayList<T>(this);
}
}
private class Entry {
T element;
boolean isSelected;
Entry(T element) {
this.element = element;
}
@Override
public String toString() {
return element.toString();
}
}
/**
* 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 (T pModel1 : ListPM.this) {
if (pModel1.isValid() == false) {
String message = resourceBundle.getString(KEY_MESSAGE_INVALID_ELEMENTS);
return new ValidationState(message);
}
}
return null;
}
}
}