001: /* ListModelSet.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Fri Dec 01 14:38:43 2006, Created by Henri Chen
010: }}IS_NOTE
011:
012: Copyright (C) 2006 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: }}IS_RIGHT
016: */
017: package org.zkoss.zul;
018:
019: import org.zkoss.zul.event.ListDataEvent;
020: import org.zkoss.zk.ui.UiException;
021: import org.zkoss.lang.Objects;
022:
023: import java.util.Set;
024: import java.util.List;
025: import java.util.SortedSet;
026: import java.util.LinkedHashSet;
027: import java.util.Iterator;
028: import java.util.ArrayList;
029: import java.util.Comparator;
030: import java.util.Collection;
031: import java.util.Collections;
032:
033: /**
034: * <p>This is the {@link ListModel} as a {@link java.util.Set} to be used with {@link Listbox}.
035: * Add or remove the contents of this model as a Set would cause the associated Listbox to change accordingly.</p>
036: *
037: * @author Henri Chen
038: * @see ListModel
039: * @see ListModelList
040: * @see ListModelMap
041: */
042: public class ListModelSet extends AbstractListModel implements
043: ListModelExt, Set, java.io.Serializable {
044: protected Set _set;
045:
046: /**
047: * Creates an instance which accepts a "live" Set as its inner Set.
048: * @param set the inner Set storage.
049: * @deprecated As of release 2.4.0, replaced by {@link #ListModelSet(Set,boolean)}
050: */
051: public static ListModelSet instance(Set set) {
052: return new ListModelSet(set, true);
053: }
054:
055: /**
056: * Constructor
057: *
058: * @param set the set to represent
059: * @param live whether to have a 'live' {@link ListModel} on top of
060: * the specified set.
061: * If false, the content of the specified set is copied.
062: * If true, this object is a 'facade' of the specified set,
063: * i.e., when you add or remove items from this {@link ListModelSet},
064: * the inner "live" set would be changed accordingly.
065: *
066: * However, it is not a good idea to modify <code>set</code>
067: * if it is passed to this method with live is true,
068: * since {@link Listbox} is not smart enough to hanle it.
069: * Instead, modify it thru this object.
070: * @since 2.4.0
071: */
072: public ListModelSet(Set set, boolean live) {
073: _set = live ? set : new LinkedHashSet(set);
074: }
075:
076: /**
077: * Constructor.
078: */
079: public ListModelSet() {
080: _set = new LinkedHashSet();
081: }
082:
083: /**
084: * Constructor.
085: * It mades a copy of the specified collection (i.e., not live).
086: */
087: public ListModelSet(Collection c) {
088: _set = new LinkedHashSet(c);
089: }
090:
091: /**
092: * Constructor.
093: * It mades a copy of the specified array (i.e., not live).
094: * @since 2.4.1
095: */
096: public ListModelSet(Object[] array) {
097: _set = new LinkedHashSet(array.length);
098: for (int j = 0; j < array.length; ++j)
099: _set.add(array[j]);
100: }
101:
102: /**
103: * Constructor.
104: * @param initialCapacity the initial capacity for this ListModelSet.
105: */
106: public ListModelSet(int initialCapacity) {
107: _set = new LinkedHashSet(initialCapacity);
108: }
109:
110: /**
111: * Constructor.
112: * @param initialCapacity the initial capacity for this ListModelMap.
113: * @param loadFactor the loadFactor to increase capacity of this ListModelMap.
114: */
115: public ListModelSet(int initialCapacity, float loadFactor) {
116: _set = new LinkedHashSet(initialCapacity, loadFactor);
117: }
118:
119: /**
120: * Get the inner real set.
121: */
122: public Set getInnerSet() {
123: return _set;
124: }
125:
126: //-- ListModel --//
127: public int getSize() {
128: return _set.size();
129: }
130:
131: public Object getElementAt(int j) {
132: if (j < 0 || j >= _set.size())
133: throw new IndexOutOfBoundsException("" + j);
134:
135: for (Iterator it = _set.iterator();;) {
136: final Object o = it.next();
137: if (--j < 0)
138: return o;
139: }
140: }
141:
142: //-- Set --//
143: /**
144: * This implementation optimized on the LinkedHashSet(which guaratee the sequence of the added item).
145: * Other implementation needs one more linier search.
146: */
147: public boolean add(Object o) {
148: if (!_set.contains(o)) {
149: boolean ret = _set.add(o);
150: //After add, the position can change if not LinkedHashSet
151: //bug #1819318 Problem while using SortedSet with Databinding
152: //bug #1839634 Problem while using HashSet with Databinding
153: if (_set instanceof LinkedHashSet) {
154: //bug 1870996 Exception when use ListModelSet with SimpleListModelSharer
155: //java.lang.IndexOutOfBoundsException: 1, interval added index should be _set.size() - 1
156: final int i1 = _set.size() - 1;
157: fireEvent(ListDataEvent.INTERVAL_ADDED, i1, i1);
158: } else if (_set instanceof SortedSet) {
159: final int i1 = indexOf(o);
160: fireEvent(ListDataEvent.INTERVAL_ADDED, i1, i1);
161: } else {//bug #1839634, HashSet, not sure the iteration sequence
162: //of the HashSet, must resync
163: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
164: }
165: return ret;
166: }
167: return false;
168: }
169:
170: /**
171: * This implementation optimized on the LinkedHashSet(which
172: * guaratee the sequence of the added item).
173: * Other implementation needs one more linier search.
174: */
175: public boolean addAll(Collection c) {
176: if (_set instanceof LinkedHashSet) {
177: int begin = _set.size();
178: int added = 0;
179: for (final Iterator it = c.iterator(); it.hasNext();) {
180: Object o = it.next();
181: if (_set.contains(o)) {
182: continue;
183: }
184: _set.add(o);
185: ++added;
186: }
187: if (added > 0) {
188: fireEvent(ListDataEvent.INTERVAL_ADDED, begin, begin
189: + added - 1);
190: return true;
191: }
192: return false;
193: } else {//bug #1819318 Problem while using SortedSet with Databinding
194: //bug #1839634 Problem while using HashSet with Databinding
195: final boolean ret = _set.addAll(c);
196: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
197: return ret;
198: }
199: }
200:
201: public void clear() {
202: int i2 = _set.size() - 1;
203: if (i2 < 0) {
204: return;
205: }
206: _set.clear();
207: fireEvent(ListDataEvent.INTERVAL_REMOVED, 0, i2);
208: }
209:
210: public boolean contains(Object elem) {
211: return _set.contains(elem);
212: }
213:
214: public boolean containsAll(Collection c) {
215: return _set.containsAll(c);
216: }
217:
218: public boolean equals(Object o) {
219: return _set
220: .equals(o instanceof ListModelSet ? ((ListModelSet) o)._set
221: : o);
222: }
223:
224: public int hashCode() {
225: return _set.hashCode();
226: }
227:
228: public boolean isEmpty() {
229: return _set.isEmpty();
230: }
231:
232: public String toString() {
233: return _set.toString();
234: }
235:
236: public Iterator iterator() {
237: return new Iterator() {
238: private Iterator _it = _set.iterator();
239: private Object _current = null;
240:
241: public boolean hasNext() {
242: return _it.hasNext();
243: }
244:
245: public Object next() {
246: _current = _it.next();
247: return _current;
248: }
249:
250: public void remove() {
251: //bug #1819318 Problem while using SortedSet with Databinding
252: if (_set instanceof LinkedHashSet
253: || _set instanceof SortedSet) {
254: final int index = indexOf(_current);
255: _it.remove();
256: fireEvent(ListDataEvent.INTERVAL_REMOVED, index,
257: index);
258: } else { //bug #1839634 Problem while using HashSet with Databinding
259: _it.remove();
260: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
261: }
262: }
263: };
264: }
265:
266: public boolean remove(Object o) {
267: boolean ret = false;
268: if (_set.contains(o)) {
269: //bug #1819318 Problem while using SortedSet with Databinding
270: if (_set instanceof LinkedHashSet
271: || _set instanceof SortedSet) {
272: final int index = indexOf(o);
273: ret = _set.remove(o);
274: fireEvent(ListDataEvent.INTERVAL_REMOVED, index, index);
275: } else { //bug #1839634 Problem while using HashSet with Databinding
276: ret = _set.remove(o);
277: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
278: }
279: }
280: return ret;
281: }
282:
283: /** Returns the index of the specified object, or -1 if not found.
284: */
285: public int indexOf(Object o) {
286: int j = 0;
287: for (Iterator it = _set.iterator(); it.hasNext(); ++j) {
288: if (Objects.equals(o, it.next()))
289: return j;
290: }
291: return -1;
292: }
293:
294: public boolean removeAll(Collection c) {
295: if (_set == c || this == c) { //special case
296: clear();
297: return true;
298: }
299: //bug #1819318 Problem while using SortedSet with Databinding
300: if (_set instanceof LinkedHashSet || _set instanceof SortedSet) {
301: return removePartial(c, true);
302: } else { //bug #1839634 Problem while using HashSet with Databinding
303: final boolean ret = _set.removeAll(c);
304: if (ret) {
305: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
306: }
307: return ret;
308: }
309: }
310:
311: public boolean retainAll(Collection c) {
312: if (_set == c || this == c) { //special case
313: return false;
314: }
315: //bug #1819318 Problem while using SortedSet with Databinding
316: if (_set instanceof LinkedHashSet || _set instanceof SortedSet) {
317: return removePartial(c, false);
318: } else { //bug #1839634 Problem while using HashSet with Databinding
319: final boolean ret = _set.retainAll(c);
320: if (ret) {
321: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
322: }
323: return ret;
324: }
325: }
326:
327: private boolean removePartial(Collection c, boolean isRemove) {
328: int sz = c.size();
329: int removed = 0;
330: int retained = 0;
331: int index = 0;
332: int begin = -1;
333: for (final Iterator it = _set.iterator(); it.hasNext()
334: && (!isRemove || removed < sz)
335: && (isRemove || retained < sz); ++index) {
336: Object item = it.next();
337: if (c.contains(item) == isRemove) {
338: if (begin < 0) {
339: begin = index;
340: }
341: ++removed;
342: it.remove();
343: } else {
344: ++retained;
345: if (begin >= 0) {
346: fireEvent(ListDataEvent.INTERVAL_REMOVED, begin,
347: index - 1);
348: index = begin; //this range removed, the index is reset to begin
349: begin = -1;
350: }
351: }
352: }
353: if (begin >= 0) {
354: fireEvent(ListDataEvent.INTERVAL_REMOVED, begin, index - 1);
355: }
356:
357: return removed > 0;
358: }
359:
360: public int size() {
361: return _set.size();
362: }
363:
364: public Object[] toArray() {
365: return _set.toArray();
366: }
367:
368: public Object[] toArray(Object[] a) {
369: return _set.toArray(a);
370: }
371:
372: //-- ListModelExt --//
373: /** Sorts the data.
374: *
375: * @param cmpr the comparator.
376: * @param ascending whether to sort in the ascending order.
377: * It is ignored since this implementation uses cmprt to compare.
378: */
379: public void sort(Comparator cmpr, final boolean ascending) {
380: final List copy = new ArrayList(_set);
381: Collections.sort(copy, cmpr);
382: _set.clear();
383: _set.addAll(copy);
384: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
385: }
386: }
|