/* ListModelList.java
{{IS_NOTE
Purpose:
Description:
History:
Thu Nov 23 17:43:13 2006, Created by Henri Chen
}}IS_NOTE
Copyright (C) 2006 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
}}IS_RIGHT
*/
package org.zkoss.zul;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zul.event.ListDataEvent;
import org.zkoss.zul.ext.Sortable;
/**
* <p>This is the {@link ListModel} as a {@link java.util.List} to be used with {@link Listbox}.
* Add or remove the contents of this model as a List would cause the associated Listbox to change accordingly.</p>
*
* <p>For more information, please refer to
* <a href="http://books.zkoss.org/wiki/ZK_Developer%27s_Reference/MVC/Model/List_Model">ZK Developer's Reference: List Model</a>
*
* @author Henri Chen
* @see ListModel
* @see ListModelList
* @see ListModelMap
*/
public class ListModelList<E> extends AbstractListModel<E> implements Sortable<E>, List<E>, java.io.Serializable {
private static final long serialVersionUID = 20120206122641L;
protected List<E> _list;
private Comparator<E> _sorting;
private boolean _sortDir;
/**
* Constructor
*
* @param list the list to represent
* @param live whether to have a 'live' {@link ListModel} on top of
* the specified list.
* If false, the content of the specified list is copied.
* If true, this object is a 'facade' of the specified list,
* i.e., when you add or remove items from this ListModelList,
* the inner "live" list would be changed accordingly.
*
* However, it is not a good idea to modify <code>list</code>
* if it is passed to this method with live is true,
* since {@link Listbox} is not smart enough to handle it.
* Instead, modify it thru this object.
* @since 2.4.0
*/
public ListModelList(List<E> list, boolean live) {
_list = live ? list : new ArrayList<E>(list);
}
/**
* Constructor.
*/
public ListModelList() {
_list = new ArrayList<E>();
}
/**
* Constructor.
* It makes a copy of the specified collection (i.e., not live).
*
* <p>Notice that if the data is static or not shared, it is better to
* use <code>ListModelList(c, true)</code> instead, since
* making a copy is slower.
*/
public ListModelList(Collection<? extends E> c) {
_list = new ArrayList<E>(c);
}
/**
* Constructor.
* It makes a copy of the specified array (i.e., not live).
* @since 2.4.1
*/
public ListModelList(E[] array) {
_list = new ArrayList<E>(array.length);
for (int j = 0; j < array.length; ++j)
_list.add(array[j]);
}
/**
* Constructor.
* @param initialCapacity the initial capacity for this ListModelList.
*/
public ListModelList(int initialCapacity) {
_list = new ArrayList<E>(initialCapacity);
}
/**
* Remove from fromIndex(inclusive) to toIndex(exclusive). If fromIndex equals toIndex,
* this methods do nothing.
* @param fromIndex the begin index (inclusive) to be removed.
* @param toIndex the end index (exclusive) to be removed.
*/
public void removeRange(int fromIndex, int toIndex) {
if (fromIndex > toIndex) {
throw new UiException(
"fromIndex must less than toIndex: fromIndex: " + fromIndex + ", toIndex: " + toIndex);
}
if (fromIndex == toIndex) {
return;
}
int sz = _list.size();
if (sz == fromIndex) {
return;
}
int index = fromIndex;
//B70-ZK-2447 : IndexOutOfBound exception occurs when using ListModelList.removeRange(0, size) with all items selected
Set<E> removedObjs = new HashSet<E>();
for (final Iterator<E> it = _list.listIterator(fromIndex); it.hasNext() && index < toIndex; ++index) {
final E obj = it.next();
removedObjs.add(obj);
it.remove();
}
fireEvent(ListDataEvent.INTERVAL_REMOVED, fromIndex, index - 1);
for (E obj : removedObjs)
removeFromSelection(obj);
}
/**
* Get the inner real List.
*/
public List<E> getInnerList() {
return _list;
}
//-- ListModel --//
public int getSize() {
return _list.size();
}
public E getElementAt(int j) {
return _list.get(j);
}
//-- List --//
public void add(int index, E element) {
_list.add(index, element);
fireEvent(ListDataEvent.INTERVAL_ADDED, index, index);
}
public boolean add(E o) {
int i1 = _list.size();
boolean ret = _list.add(o);
fireEvent(ListDataEvent.INTERVAL_ADDED, i1, i1);
return ret;
}
/**
* Notifies a change of the same element to trigger an event of {@link ListDataEvent#CONTENTS_CHANGED}.
* @param element
* @return true if the element exists
* @since 8.0.0
*/
public boolean notifyChange(E element) {
int i = _list.indexOf(element);
if (i >= 0) {
fireEvent(ListDataEvent.CONTENTS_CHANGED, i, i);
return true;
}
return false;
}
public boolean addAll(Collection<? extends E> c) {
int sz = c.size();
if (sz <= 0) {
return false;
}
int i1 = _list.size();
int i2 = i1 + sz - 1;
boolean ret = _list.addAll(c);
fireEvent(ListDataEvent.INTERVAL_ADDED, i1, i2);
return ret;
}
public boolean addAll(int index, Collection<? extends E> c) {
int sz = c.size();
if (sz <= 0) {
return false;
}
int i2 = index + sz - 1;
boolean ret = _list.addAll(index, c);
fireEvent(ListDataEvent.INTERVAL_ADDED, index, i2);
return ret;
}
public void clear() {
int i2 = _list.size() - 1;
if (i2 < 0) {
return;
}
clearSelection();
_list.clear();
fireEvent(ListDataEvent.INTERVAL_REMOVED, 0, i2);
}
public boolean contains(Object elem) {
boolean ret = _list.contains(elem);
return ret;
}
public boolean containsAll(Collection<?> c) {
return _list.containsAll(c);
}
public boolean equals(Object o) {
if (this == o)
return true;
return _list.equals(o instanceof ListModelList<?> ? ((ListModelList<?>) o)._list : o);
}
public E get(int index) {
return _list.get(index);
}
public int hashCode() {
return _list.hashCode();
}
public String toString() {
return _list.toString();
}
public int indexOf(Object elem) {
return _list.indexOf(elem);
}
public boolean isEmpty() {
return _list.isEmpty();
}
public Iterator<E> iterator() {
return new Iterator<E>() {
private Iterator<E> _it = _list.iterator();
private E _current = null;
private int _nextIndex;
public boolean hasNext() {
return _it.hasNext();
}
public E next() {
_current = _it.next();
++_nextIndex;
return _current;
}
public void remove() {
removeFromSelection(_current);
_it.remove();
--_nextIndex;
fireEvent(ListDataEvent.INTERVAL_REMOVED, _nextIndex, _nextIndex);
}
};
}
public int lastIndexOf(Object elem) {
return _list.lastIndexOf(elem);
}
public ListIterator<E> listIterator() {
return listIterator(0);
}
public ListIterator<E> listIterator(final int index) {
return new ListIterator<E>() {
private ListIterator<E> _it = _list.listIterator(index);
private E _current = null;
public boolean hasNext() {
return _it.hasNext();
}
public E next() {
_current = _it.next();
return _current;
}
public void remove() {
removeFromSelection(_current);
_it.remove();
final int index = _it.nextIndex();
fireEvent(ListDataEvent.INTERVAL_REMOVED, index, index);
}
public void add(E arg0) {
final int index = _it.nextIndex();
_it.add(arg0);
fireEvent(ListDataEvent.INTERVAL_ADDED, index, index);
}
public boolean hasPrevious() {
return _it.hasPrevious();
}
public int nextIndex() {
return _it.nextIndex();
}
public E previous() {
_current = _it.previous();
return _current;
}
public int previousIndex() {
return _it.previousIndex();
}
public void set(E arg0) {
_it.set(arg0);
final int index = _it.nextIndex() - 1;
fireEvent(ListDataEvent.CONTENTS_CHANGED, index, index);
}
};
}
public E remove(int index) {
E ret = _list.get(index);
removeFromSelection(ret);
//Bug ZK-1428: should remove the object after removeFromSelection
//since Listbox.doSelectionChanged will try to get the object again
_list.remove(index);
fireEvent(ListDataEvent.INTERVAL_REMOVED, index, index);
return ret;
}
public boolean remove(Object o) {
int index = indexOf(o);
if (index >= 0) {
remove(index);
return true;
}
return false;
}
public boolean removeAll(Collection<?> c) {
if (_list == c || this == c) { // special case
clearSelection();
clear();
return true;
}
return removePartial(c, true);
}
public boolean retainAll(Collection<?> c) {
if (_list == c || this == c) { //special case
return false;
}
return removePartial(c, false);
}
private boolean removePartial(Collection<?> c, boolean exclude) {
boolean removed = false;
int index = 0;
int begin = -1;
// B60-ZK-1126.zul
// Remember the selections to be cleared
List<E> selected = new ArrayList<E>();
for (final Iterator<E> it = _list.iterator(); it.hasNext(); ++index) {
E item = it.next();
if (c.contains(item) == exclude) {
if (begin < 0) {
begin = index;
}
removed = true;
it.remove();
// B60-ZK-1126.zul
// Removed item has been selected; remember and remove the selection later
if (_selection.contains(item)) {
selected.add(item);
}
} else {
if (begin >= 0) {
fireEvent(ListDataEvent.INTERVAL_REMOVED, begin, index - 1);
index = begin; //this range removed, the index is reset to begin
begin = -1;
}
}
}
// B60-ZK-1126.zul
// Clear the selected items that were removed
if (!selected.isEmpty()) {
removeAllSelection(selected);
}
if (begin >= 0) {
fireEvent(ListDataEvent.INTERVAL_REMOVED, begin, index - 1);
}
return removed;
}
public E set(int index, E element) {
E ret = _list.set(index, element);
fireEvent(ListDataEvent.CONTENTS_CHANGED, index, index);
return ret;
}
public int size() {
return _list.size();
}
public List<E> subList(int fromIndex, int toIndex) {
List<E> list = _list.subList(fromIndex, toIndex);
//bug 2448987: sublist must be live
return new ListModelList<E>(list, true);
}
public Object[] toArray() {
return _list.toArray();
}
public <T> T[] toArray(T[] a) {
return _list.toArray(a);
}
//-- Sortable --//
/** Sorts the data.
*
* @param cmpr the comparator.
* @param ascending whether to sort in the ascending order.
* It is ignored since this implementation uses cmprt to compare.
*/
public void sort(Comparator<E> cmpr, final boolean ascending) {
_sorting = cmpr;
_sortDir = ascending;
Collections.sort(_list, cmpr);
fireEvent(ListDataEvent.STRUCTURE_CHANGED, -1, -1);
}
public String getSortDirection(Comparator<E> cmpr) {
if (Objects.equals(_sorting, cmpr))
return _sortDir ? "ascending" : "descending";
return "natural";
}
@SuppressWarnings("unchecked")
public Object clone() {
ListModelList<E> clone = (ListModelList<E>) super.clone();
if (_list != null)
clone._list = new ArrayList<E>(_list);
return clone;
}
protected void fireSelectionEvent(E e) {
fireEvent(ListDataEvent.SELECTION_CHANGED, indexOf(e), -1);
}
//For Backward Compatibility//
/** @deprecated As of release 6.0.0, replaced with {@link #addToSelection}.
*/
public void addSelection(E obj) {
addToSelection(obj);
}
/** @deprecated As of release 6.0.0, replaced with {@link #removeFromSelection}.
*/
public void removeSelection(Object obj) {
removeFromSelection(obj);
}
}