001: /* ListModelMap.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Fri Dec 01 11:15:23 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.Classes;
022: import org.zkoss.lang.Objects;
023:
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.List;
027: import java.util.SortedMap;
028: import java.util.ArrayList;
029: import java.util.Iterator;
030: import java.util.Comparator;
031: import java.util.Collection;
032: import java.util.Collections;
033: import java.util.LinkedHashMap;
034: import java.lang.reflect.Method;
035:
036: /**
037: * <p>This is the {@link ListModel} as a {@link java.util.Map} to be used with {@link Listbox}.
038: * Add or remove the contents of this model as a List would cause the associated Listbox to change accordingly.</p>
039: *
040: * @author Henri Chen
041: * @see ListModel
042: * @see ListModelList
043: * @see ListModelMap
044: */
045: public class ListModelMap extends AbstractListModel implements
046: ListModelExt, Map, java.io.Serializable {
047: protected Map _map; //(key, value)
048:
049: /**
050: * Creates an instance which accepts a "live" Map as its inner Map.
051: * @param map the inner Map storage.
052: * @deprecated As of release 2.4.0, replaced by {@link #ListModelMap(Map, boolean)}
053: */
054: public static ListModelMap instance(Map map) {
055: return new ListModelMap(map, true);
056: }
057:
058: /**
059: * Constructor.
060: *
061: * @param map the map to represent
062: * @param live whether to have a 'live' {@link ListModel} on top of
063: * the specified map.
064: * If false, the content of the specified map is copied.
065: * If true, this object is a 'facade' of the specified map,
066: * i.e., when you add or remove items from this {@link ListModelMap},
067: * the inner "live" map would be changed accordingly.
068: *
069: * However, it is not a good idea to modify <code>map</code>
070: * if it is passed to this method with live is true,
071: * since {@link Listbox} is not smart enough to hanle it.
072: * Instead, modify it thru this object.
073: * @since 2.4.0
074: */
075: public ListModelMap(Map map, boolean live) {
076: _map = live ? map : new LinkedHashMap(map);
077: }
078:
079: /**
080: * Constructor.
081: */
082: public ListModelMap() {
083: _map = new LinkedHashMap();
084: }
085:
086: /**
087: * Constructor.
088: * It mades a copy of the specified map (i.e., not live).
089: */
090: public ListModelMap(Map map) {
091: _map = new LinkedHashMap(map);
092: }
093:
094: /**
095: * Constructor.
096: * @param initialCapacity the initial capacity for this ListModelMap.
097: */
098: public ListModelMap(int initialCapacity) {
099: _map = new LinkedHashMap(initialCapacity);
100: }
101:
102: /**
103: * Constructor.
104: * @param initialCapacity the initial capacity for this ListModelMap.
105: * @param loadFactor the loadFactor to increase capacity of this ListModelMap.
106: */
107: public ListModelMap(int initialCapacity, float loadFactor) {
108: _map = new LinkedHashMap(initialCapacity, loadFactor);
109: }
110:
111: /**
112: * Get the inner real Map.
113: */
114: public Map getInnerMap() {
115: return _map;
116: }
117:
118: //-- ListModel --//
119: public int getSize() {
120: return _map.size();
121: }
122:
123: /**
124: * Returns the entry (Map.Entry) at the specified index.
125: */
126: public Object getElementAt(int j) {
127: if (j < 0 || j >= _map.size())
128: throw new IndexOutOfBoundsException("" + j);
129:
130: for (Iterator it = _map.entrySet().iterator();;) {
131: final Object o = it.next();
132: if (--j < 0)
133: return o;
134: }
135: }
136:
137: //-- Map --//
138: public void clear() {
139: int i2 = _map.size() - 1;
140: if (i2 < 0) {
141: return;
142: }
143: _map.clear();
144: fireEvent(ListDataEvent.INTERVAL_REMOVED, 0, i2);
145: }
146:
147: public boolean containsKey(Object key) {
148: return _map.containsKey(key);
149: }
150:
151: public boolean containsValue(Object value) {
152: return _map.containsValue(value);
153: }
154:
155: public Set entrySet() {
156: return new MyEntrySet(_map.entrySet());
157: }
158:
159: public boolean equals(Object o) {
160: return _map
161: .equals(o instanceof ListModelMap ? ((ListModelMap) o)._map
162: : o);
163: }
164:
165: public String toString() {
166: return _map.toString();
167: }
168:
169: public Object get(Object key) {
170: return _map.get(key);
171: }
172:
173: public int hashCode() {
174: return _map.hashCode();
175: }
176:
177: public boolean isEmpty() {
178: return _map.isEmpty();
179: }
180:
181: public Set keySet() {
182: return new MyKeySet(_map.keySet());
183: }
184:
185: public Object put(Object key, Object o) {
186: final Object ret;
187: if (_map.containsKey(key)) {
188: if (Objects.equals(o, _map.get(key))) {
189: return o; //nothing changed
190: }
191: int index = indexOfKey(key);
192: ret = _map.put(key, o);
193: fireEvent(ListDataEvent.CONTENTS_CHANGED, index, index);
194: } else {
195: ret = _map.put(key, o);
196:
197: //After put, the position can change if not LinkedHashMap
198: //bug #1819318 Problem while using SortedSet with Databinding
199: //bug #1839634 Problem while using HashSet with Databinding
200: if (_map instanceof LinkedHashMap) {
201: //bug 1869614 Problem when switching from ListModelList to ListModelMap,
202: //java.lang.IndexOutOfBoundsException: 1, interval added index should be _map.size() - 1
203: final int i1 = _map.size() - 1;
204: fireEvent(ListDataEvent.INTERVAL_ADDED, i1, i1);
205: } else if (_map instanceof SortedMap) {
206: final int i1 = indexOfKey(key);
207: fireEvent(ListDataEvent.INTERVAL_ADDED, i1, i1);
208: } else {//bug #1839634, HashMap, not sure the iteration sequence
209: //of the HashMap, must resync
210: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
211: }
212: }
213: return ret;
214: }
215:
216: /** Returns the index of the specified object based on the key.
217: *
218: * @param o the key to look for
219: */
220: public int indexOfKey(Object o) {
221: int j = 0;
222: for (Iterator it = _map.keySet().iterator(); it.hasNext(); ++j) {
223: if (Objects.equals(o, it.next()))
224: return j;
225: }
226: return -1;
227: }
228:
229: /** Returns the index of the specified object based on the entry
230: * (Map.Entry).
231: *
232: * @param o the object to look for. It must be an instance of Map.Entry.
233: */
234: public int indexOf(Object o) {
235: int j = 0;
236: for (Iterator it = _map.entrySet().iterator(); it.hasNext(); ++j) {
237: if (Objects.equals(o, it.next()))
238: return j;
239: }
240: return -1;
241: }
242:
243: public void putAll(Map c) {
244: if (_map instanceof LinkedHashMap) {
245: int sz = c.size();
246: if (sz <= 0) {
247: return;
248: }
249: if (c == _map) { //special case
250: return;
251: }
252:
253: List added = new ArrayList(c.size());
254: for (Iterator it = c.entrySet().iterator(); it.hasNext();) {
255: final Entry entry = (Entry) it.next();
256: Object key = entry.getKey();
257: Object val = entry.getValue();
258: if (_map.containsKey(key)) {
259: put(key, val);
260: } else {
261: added.add(entry);
262: }
263: }
264:
265: for (Iterator it = added.iterator(); it.hasNext();) {
266: final Entry entry = (Entry) it.next();
267: Object key = entry.getKey();
268: Object val = entry.getValue();
269: _map.put(key, val);
270: }
271:
272: int len = added.size();
273: if (len > 0) {
274: fireEvent(ListDataEvent.INTERVAL_ADDED, sz, sz + len
275: - 1);
276: }
277: } else { //bug #1819318 Problem while using SortedSet with Databinding
278: //bug #1839634 Problem while using HashSet with Databinding
279: _map.putAll(c);
280: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
281: }
282: }
283:
284: public Object remove(Object key) {
285: if (_map.containsKey(key)) {
286: //bug #1819318 Problem while using SortedSet with Databinding
287: Object ret = null;
288: if (_map instanceof LinkedHashMap
289: || _map instanceof SortedMap) {
290: int index = indexOfKey(key);
291: ret = _map.remove(key);
292: fireEvent(ListDataEvent.INTERVAL_REMOVED, index, index);
293: } else { //bug #1839634 Problem while using HashSet with Databinding
294: ret = _map.remove(key);
295: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
296: }
297: return ret;
298: }
299: return null;
300: }
301:
302: public int size() {
303: return _map.size();
304: }
305:
306: public Collection values() {
307: return new MyCollection(_map.values());
308: }
309:
310: //-- ListModelExt --//
311: /** Sorts the data.
312: *
313: * @param cmpr the comparator.
314: * @param ascending whether to sort in the ascending order.
315: * It is ignored since this implementation uses cmprt to compare.
316: */
317: public void sort(Comparator cmpr, final boolean ascending) {
318: final List copy = new ArrayList(_map.entrySet());
319: Collections.sort(copy, cmpr);
320: _map.clear();
321: for (Iterator it = copy.iterator(); it.hasNext();) {
322: Entry entry = (Entry) it.next();
323: _map.put(entry.getKey(), entry.getValue());
324: }
325: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
326: }
327:
328: private boolean removePartial(Collection master, Collection c,
329: boolean isRemove) {
330: int sz = c.size();
331: int removed = 0;
332: int retained = 0;
333: int index = 0;
334: int begin = -1;
335: for (final Iterator it = master.iterator(); it.hasNext()
336: && (!isRemove || removed < sz)
337: && (isRemove || retained < sz); ++index) {
338: Object item = it.next();
339: if (c.contains(item) == isRemove) {
340: if (begin < 0) {
341: begin = index;
342: }
343: ++removed;
344: it.remove();
345: } else {
346: ++retained;
347: if (begin >= 0) {
348: fireEvent(ListDataEvent.INTERVAL_REMOVED, begin,
349: index - 1);
350: index = begin; //this range removed, the index is reset to begin
351: begin = -1;
352: }
353: }
354: }
355: if (begin >= 0) {
356: fireEvent(ListDataEvent.INTERVAL_REMOVED, begin, index - 1);
357: }
358:
359: return removed > 0;
360: }
361:
362: private class MyIterator implements Iterator {
363: private Iterator _it;
364: private int _index = -1;
365:
366: public MyIterator(Iterator inner) {
367: _it = inner;
368: _index = -1;
369: }
370:
371: public boolean hasNext() {
372: return _it.hasNext();
373: }
374:
375: public Object next() {
376: ++_index;
377: return _it.next();
378: }
379:
380: public void remove() {
381: if (_index >= 0) {
382: //bug #1819318 Problem while using SortedSet with Databinding
383: _it.remove();
384: if (_map instanceof LinkedHashMap
385: || _map instanceof SortedMap) {
386: fireEvent(ListDataEvent.INTERVAL_REMOVED, _index,
387: _index);
388: } else {
389: //bug #1839634 Problem while using HashSet with Databinding
390: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
391: }
392: }
393: }
394: }
395:
396: /** Represents the key set.
397: */
398: private class MyKeySet implements Set {
399: private final Set _set;
400:
401: public MyKeySet(Set inner) {
402: _set = inner;
403: }
404:
405: public void clear() {
406: int i2 = _set.size() - 1;
407: if (i2 < 0) {
408: return;
409: }
410: _set.clear();
411: fireEvent(ListDataEvent.INTERVAL_REMOVED, 0, i2);
412: }
413:
414: public boolean remove(Object o) {
415: boolean ret = false;
416: if (_set.contains(o)) {
417: //bug #1819318 Problem while using SortedSet with Databinding
418: if (_map instanceof LinkedHashMap
419: || _map instanceof SortedMap) {
420: final int index = indexOf(o);
421: ret = _set.remove(o);
422: fireEvent(ListDataEvent.INTERVAL_REMOVED, index,
423: index);
424: } else { //bug #1839634 Problem while using HashSet with Databinding
425: ret = _set.remove(o);
426: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
427: }
428: }
429: return ret;
430: }
431:
432: protected int indexOf(Object o) {
433: return indexOfKey(o);
434: }
435:
436: public boolean removeAll(Collection c) {
437: if (_set == c || this == c) { //special case
438: clear();
439: return true;
440: }
441:
442: //bug #1819318 Problem while using SortedSet with Databinding
443: if (_map instanceof LinkedHashMap
444: || _map instanceof SortedMap) {
445: return removePartial(_set, c, true);
446: } else { //bug #1839634 Problem while using HashSet with Databinding
447: final boolean ret = _set.removeAll(c);
448: if (ret) {
449: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
450: }
451: return ret;
452: }
453: }
454:
455: public boolean retainAll(Collection c) {
456: if (_set == c || this == c) { //special case
457: return false;
458: }
459: //bug #1819318 Problem while using SortedSet with Databinding
460: if (_map instanceof LinkedHashMap
461: || _map instanceof SortedMap) {
462: return removePartial(_set, c, false);
463: } else { //bug #1839634 Problem while using HashSet with Databinding
464: final boolean ret = _set.retainAll(c);
465: if (ret) {
466: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
467: }
468: return ret;
469: }
470: }
471:
472: public Iterator iterator() {
473: return new MyIterator(_set.iterator());
474: }
475:
476: public boolean add(Object o) {
477: throw new UnsupportedOperationException("add()");
478: }
479:
480: public boolean addAll(Collection col) {
481: throw new UnsupportedOperationException("addAll()");
482: }
483:
484: public boolean contains(Object o) {
485: return _set == null ? false : _set.contains(o);
486: }
487:
488: public boolean containsAll(Collection c) {
489: return _set == null ? false : _set.containsAll(c);
490: }
491:
492: public boolean equals(Object o) {
493: if (this == o) {
494: return true;
495: }
496: if (o instanceof MyKeySet) {
497: return Objects.equals(((MyKeySet) o)._set, _set);
498: } else {
499: return Objects.equals(_set, o);
500: }
501: }
502:
503: public int hashCode() {
504: return _set == null ? 0 : _set.hashCode();
505: }
506:
507: public boolean isEmpty() {
508: return _set == null ? true : _set.isEmpty();
509: }
510:
511: public int size() {
512: return _set == null ? 0 : _set.size();
513: }
514:
515: public Object[] toArray() {
516: return _set == null ? new Object[0] : _set.toArray();
517: }
518:
519: public Object[] toArray(Object[] a) {
520: return _set == null ? a : _set.toArray(a);
521: }
522: }
523:
524: private class MyEntrySet extends MyKeySet {
525: private MyEntrySet(Set inner) {
526: super (inner);
527: }
528:
529: protected int indexOf(Object o) {
530: return indexOf(o);
531: }
532: }
533:
534: private class MyCollection implements Collection {
535: private Collection _inner;
536:
537: public MyCollection(Collection inner) {
538: _inner = inner;
539: }
540:
541: public void clear() {
542: int i2 = _inner.size() - 1;
543: if (i2 < 0) {
544: return;
545: }
546: _inner.clear();
547: fireEvent(ListDataEvent.INTERVAL_REMOVED, 0, i2);
548: }
549:
550: private int indexOfAndRemove(Object o) {
551: int j = 0;
552: for (Iterator it = _inner.iterator(); it.hasNext(); ++j) {
553: final Object val = it.next();
554: if (Objects.equals(val, o)) {
555: it.remove();
556: return j;
557: }
558: }
559: return -1;
560: }
561:
562: public boolean remove(Object o) {
563: //bug #1819318 Problem while using SortedSet with Databinding
564: if (_map instanceof LinkedHashMap
565: || _map instanceof SortedMap) {
566: int index = indexOfAndRemove(o);
567: if (index < 0) {
568: return false;
569: }
570: fireEvent(ListDataEvent.INTERVAL_REMOVED, index, index);
571: return true;
572: } else {
573: final boolean ret = _inner.remove(o);
574: if (ret) {
575: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
576: }
577: return ret;
578: }
579: }
580:
581: public boolean removeAll(Collection c) {
582: if (_inner == c || this == c) { //special case
583: clear();
584: return true;
585: }
586: //bug #1819318 Problem while using SortedSet with Databinding
587: if (_map instanceof LinkedHashMap
588: || _map instanceof SortedMap) {
589: return removePartial(_inner, c, true);
590: } else { //bug #1839634 Problem while using HashSet with Databinding
591: final boolean ret = _inner.removeAll(c);
592: if (ret) {
593: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
594: }
595: return ret;
596: }
597: }
598:
599: public boolean retainAll(Collection c) {
600: if (_inner == c || this == c) { //special case
601: return false;
602: }
603: //bug #1819318 Problem while using SortedSet with Databinding
604: if (_map instanceof LinkedHashMap
605: || _map instanceof SortedMap) {
606: return removePartial(_inner, c, false);
607: } else { //bug #1839634 Problem while using HashSet with Databinding
608: final boolean ret = _inner.retainAll(c);
609: if (ret) {
610: fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
611: }
612: return ret;
613: }
614: }
615:
616: public Iterator iterator() {
617: return new MyIterator(_inner.iterator());
618: }
619:
620: public boolean add(Object o) {
621: throw new UnsupportedOperationException("add()");
622: }
623:
624: public boolean addAll(Collection col) {
625: throw new UnsupportedOperationException("addAll()");
626: }
627:
628: public boolean contains(Object o) {
629: return _inner == null ? false : _inner.contains(o);
630: }
631:
632: public boolean containsAll(Collection c) {
633: return _inner == null ? false : _inner.containsAll(c);
634: }
635:
636: public boolean equals(Object o) {
637: if (this == o) {
638: return true;
639: }
640: if (o instanceof MyCollection) {
641: return Objects
642: .equals(((MyCollection) o)._inner, _inner);
643: } else {
644: return Objects.equals(_inner, o);
645: }
646: }
647:
648: public int hashCode() {
649: return _inner == null ? 0 : _inner.hashCode();
650: }
651:
652: public boolean isEmpty() {
653: return _inner == null ? true : _inner.isEmpty();
654: }
655:
656: public int size() {
657: return _inner == null ? 0 : _inner.size();
658: }
659:
660: public Object[] toArray() {
661: return _inner == null ? new Object[0] : _inner.toArray();
662: }
663:
664: public Object[] toArray(Object[] a) {
665: return _inner == null ? a : _inner.toArray(a);
666: }
667: }
668: }
|