001: /*--
002:
003: Copyright (C) 2000-2003 Anthony Eden.
004: All rights reserved.
005:
006: Redistribution and use in source and binary forms, with or without
007: modification, are permitted provided that the following conditions
008: are met:
009:
010: 1. Redistributions of source code must retain the above copyright
011: notice, this list of conditions, and the following disclaimer.
012:
013: 2. Redistributions in binary form must reproduce the above copyright
014: notice, this list of conditions, and the disclaimer that follows
015: these conditions in the documentation and/or other materials
016: provided with the distribution.
017:
018: 3. The name "EdenLib" must not be used to endorse or promote products
019: derived from this software without prior written permission. For
020: written permission, please contact me@anthonyeden.com.
021:
022: 4. Products derived from this software may not be called "EdenLib", nor
023: may "EdenLib" appear in their name, without prior written permission
024: from Anthony Eden (me@anthonyeden.com).
025:
026: In addition, I request (but do not require) that you include in the
027: end-user documentation provided with the redistribution and/or in the
028: software itself an acknowledgement equivalent to the following:
029: "This product includes software developed by
030: Anthony Eden (http://www.anthonyeden.com/)."
031:
032: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
033: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
034: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
035: DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
036: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
037: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
038: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
039: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
040: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
041: IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
042: POSSIBILITY OF SUCH DAMAGE.
043:
044: For more information on EdenLib, please see <http://edenlib.sf.net/>.
045:
046: */
047:
048: package com.anthonyeden.lib.util;
049:
050: import java.awt.datatransfer.Transferable;
051: import java.awt.datatransfer.DataFlavor;
052: import java.awt.datatransfer.UnsupportedFlavorException;
053: import java.lang.reflect.Method;
054: import java.util.List;
055: import java.util.Iterator;
056: import java.util.ArrayList;
057: import java.util.Collection;
058: import java.beans.PropertyChangeEvent;
059: import java.beans.PropertyChangeListener;
060:
061: import javax.swing.ListModel;
062: import javax.swing.ComboBoxModel;
063: import javax.swing.event.ListDataEvent;
064: import javax.swing.event.ListDataListener;
065:
066: import org.apache.commons.logging.Log;
067: import org.apache.commons.logging.LogFactory;
068:
069: /** This class extends the ArrayList class to support direct placement of the
070: list in a JList or JComboBox.
071:
072: This class can also listen for PropertyChangeEvents on objects in the
073: collection which have an
074: <code>addPropertyChangeListener(PropertyChangeListener l)</code>
075: method. This option is on by default, but can be turned off to invoid
076: introspection overhead required to find and invoke this method.
077:
078: @author Anthony Eden
079: */
080:
081: public class XArrayList extends ArrayList implements XList {
082:
083: /** Constant for an empty XArrayList. */
084: public static final XArrayList EMPTY_LIST = new XArrayList();
085:
086: private static final Log log = LogFactory.getLog(XArrayList.class);
087:
088: public static DataFlavor dataFlavor = new DataFlavor(
089: XArrayList.class, "Collection");
090: private static DataFlavor[] dataFlavors = { dataFlavor };
091:
092: private static Class[] addPCListenerArgTypes = { PropertyChangeListener.class };
093:
094: private ArrayList listDataListeners;
095: private Object selectedItem;
096:
097: private boolean propertyChangeListenerEnabled = true;
098:
099: /** Populate an XArrayList with the data from the given object array.
100:
101: @param array The object array
102: @return The XArrayList
103: */
104:
105: public static XArrayList arrayToList(Object[] array) {
106: XArrayList list = new XArrayList();
107: for (int i = 0; i < array.length; i++) {
108: list.add(array[i]);
109: }
110: return list;
111: }
112:
113: /** Add the given element to the list at the given index.
114:
115: @param index The index
116: @param element The element
117: */
118:
119: public void add(int index, Object element) {
120: super .add(index, element);
121: fireIntervalAdded(index, index);
122: addPropertyChangeListener(element);
123: }
124:
125: /** Add the given object to the list.
126:
127: @param element The element
128: @return boolean If the element was added
129: */
130:
131: public boolean add(Object element) {
132: boolean added = super .add(element);
133: if (added) {
134: //log.debug("Firing interval added.");
135: fireIntervalAdded(size() - 1, size() - 1);
136: }
137: addPropertyChangeListener(element);
138: return added;
139: }
140:
141: /** Add all of the objects in the collection to this collection.
142:
143: @param collection The collection to add
144: @return Return true if this collection was modified
145: */
146:
147: public boolean addAll(Collection collection) {
148: boolean added = false;
149: int startSize = size();
150: if (collection.size() > 0) {
151: added = super .addAll(collection);
152: fireIntervalAdded(startSize, size());
153: }
154: addPropertyChangeListener(collection);
155: return added;
156: }
157:
158: /** Add all elements in the collection to this list inserting at the
159: given index.
160:
161: @param index The offset index
162: @param collection The collection to add
163: */
164:
165: public boolean addAll(int index, Collection collection) {
166: boolean added = false;
167: int collectionSize = collection.size();
168: if (collectionSize > 0) {
169: added = super .addAll(index, collection);
170: fireIntervalAdded(index, index + collectionSize);
171: }
172: addPropertyChangeListener(collection);
173: return added;
174: }
175:
176: /** Clear the list data. */
177:
178: public void clear() {
179: int listSize = size();
180: if (listSize > 0) {
181: removePropertyChangeListener(this );
182: super .clear();
183: fireIntervalRemoved(0, listSize);
184: }
185: }
186:
187: /** Remove the object at the given index.
188:
189: @param index The index
190: @return The Object removed or null
191: */
192:
193: public Object remove(int index) {
194: //log.debug("remove(" + index + ")");
195: Object removed = super .remove(index);
196: if (removed != null) {
197: removePropertyChangeListener(removed);
198: fireIntervalRemoved(index, index);
199: }
200: return removed;
201: }
202:
203: /** Remove the given object. Return true if the data was changed (the
204: element was removed).
205:
206: @param object The object
207: @return True if the data was changed
208: */
209:
210: public boolean remove(Object object) {
211: // the ArrayList implementation calls remove(int index)
212: // and thus there is no need to fire a removal event
213:
214: // Note that this may be specific to JDK 1.4 and higher.
215:
216: return super .remove(object);
217: }
218:
219: /** See the ArrayList retainAll() method.
220:
221: @param c The Collection of objects to retain
222: @return True if this list was modified
223: */
224:
225: public boolean retainAll(Collection c) {
226: // need to implement property change listener support
227: boolean modified = super .retainAll(c);
228: if (modified) {
229: fireContentsChanged(0, size());
230: }
231: return modified;
232: }
233:
234: /** Remove the objects in the specified collection from this list.
235:
236: @param c The Collection to remove
237: @return True if this collection was modified
238: */
239:
240: public boolean removeAll(Collection c) {
241: // need to implement property change listener support
242: boolean modified = super .removeAll(c);
243: if (modified) {
244: fireContentsChanged(0, size());
245: }
246: return modified;
247: }
248:
249: // ListModel implementation
250:
251: /** Get the number of elements in the list.
252:
253: @return The list size
254: */
255:
256: public int getSize() {
257: return size();
258: }
259:
260: /** Get the element at the given index.
261:
262: @param index The index
263: @return The Object or null
264: */
265:
266: public Object getElementAt(int index) {
267: return get(index);
268: }
269:
270: /** Add a ListDataListener.
271:
272: @param l The ListDataListener
273: */
274:
275: public void addListDataListener(ListDataListener l) {
276: getListDataListeners().add(l);
277: }
278:
279: /** Remove a ListDataListener.
280:
281: @param l The ListDataListener
282: */
283:
284: public void removeListDataListener(ListDataListener l) {
285: getListDataListeners().remove(l);
286: }
287:
288: // ComboBoxModel implementation
289:
290: /** Get the selected item or null if no item is selected.
291:
292: @return The selected item or null
293: */
294:
295: public Object getSelectedItem() {
296: return selectedItem;
297: }
298:
299: /** Set the selected item. Set this value to null for no selected
300: item.
301:
302: @param selectedItem The new selected item
303: */
304:
305: public void setSelectedItem(Object selectedItem) {
306: this .selectedItem = selectedItem;
307: }
308:
309: // Transferable implementation
310:
311: /** Get the transferable data for the given data flavor.
312:
313: @param dataFlavor The DataFlavor
314: @return The data
315: @throws UnsupportedFlavorException
316: */
317:
318: public Object getTransferData(DataFlavor dataFlavor)
319: throws UnsupportedFlavorException {
320: if (isDataFlavorSupported(dataFlavor)) {
321: return this ;
322: } else {
323: throw new UnsupportedFlavorException(dataFlavor);
324: }
325: }
326:
327: /** Get an array of supported DataFlavors.
328:
329: @return An array of DataFlavor objects
330: */
331:
332: public DataFlavor[] getTransferDataFlavors() {
333: return dataFlavors;
334: }
335:
336: /** Return true if the given DataFlavor is supported.
337:
338: @param dataFlavor The DataFlavor
339: @return True if the DataFlavor is supported
340: */
341:
342: public boolean isDataFlavorSupported(DataFlavor dataFlavor) {
343: return dataFlavor.equals(XArrayList.dataFlavor);
344: }
345:
346: // PropertyChangeListener implementation
347:
348: public void propertyChange(PropertyChangeEvent evt) {
349: //log.debug("Property changed [old=" + evt.getOldValue() + ",new=" +
350: // evt.getNewValue() + "]");
351: int index = indexOf(evt.getSource());
352: if (index >= 0) {
353: //log.debug("Firing contents changed [" + index + "]");
354: fireContentsChanged(index, index);
355: }
356: }
357:
358: /** Set to false to disable property change listener support. The default
359: value is true.
360:
361: <b>Warning:</b> do not change this flag while there are objects in the
362: collection. Doing so may cause this collection to remain attached to
363: objects which are removed from the collection.
364:
365: @param propertyChangeListenerEnabled Set to false to disable
366: */
367:
368: public void setPropertyChangeListenerEnabled(
369: boolean propertyChangeListenerEnabled) {
370: this .propertyChangeListenerEnabled = propertyChangeListenerEnabled;
371: }
372:
373: // protected methods
374:
375: /** Get all registered ListDataListeners
376:
377: @return An ArrayList of ListDataListeners
378: */
379:
380: protected ArrayList getListDataListeners() {
381: if (listDataListeners == null) {
382: listDataListeners = new ArrayList();
383: }
384: return listDataListeners;
385: }
386:
387: /** Remove the given range of objects.
388:
389: @param startIndex The start index
390: @param endIndex The end index
391: */
392:
393: protected void removeRange(int startIndex, int endIndex) {
394: //log.debug("removeRange(" + startIndex + "," + endIndex + ")");
395: super .removeRange(startIndex, endIndex);
396: fireIntervalRemoved(startIndex, endIndex);
397: }
398:
399: /** Fire a ListDataEvent for the added interval.
400:
401: @param index0 The start index
402: @param index1 The end index
403: */
404:
405: protected void fireIntervalAdded(int index0, int index1) {
406: ListDataEvent evt = new ListDataEvent(this ,
407: ListDataEvent.INTERVAL_ADDED, index0, index1);
408: List l = null;
409:
410: synchronized (this ) {
411: l = (List) (getListDataListeners().clone());
412: }
413:
414: Iterator i = l.iterator();
415: while (i.hasNext()) {
416: ((ListDataListener) i.next()).intervalAdded(evt);
417: }
418: }
419:
420: /** Fire a ListDataEvent for the removed interval.
421:
422: @param index0 The start index
423: @param index1 The end index
424: */
425:
426: protected void fireIntervalRemoved(int index0, int index1) {
427: ListDataEvent evt = new ListDataEvent(this ,
428: ListDataEvent.INTERVAL_REMOVED, index0, index1);
429: List l = null;
430:
431: synchronized (this ) {
432: l = (List) (getListDataListeners().clone());
433: }
434:
435: //log.debug("Listener count: " + l.size());
436:
437: Iterator i = l.iterator();
438: while (i.hasNext()) {
439: //log.debug("Fire interval removed: " + index0 + "," + index1);
440: ((ListDataListener) i.next()).intervalRemoved(evt);
441: }
442: }
443:
444: /** Fire a ListDataEvent for the changed interval.
445:
446: @param index0 The start index
447: @param index1 The end index
448: */
449:
450: protected void fireContentsChanged(int index0, int index1) {
451: ListDataEvent evt = new ListDataEvent(this ,
452: ListDataEvent.CONTENTS_CHANGED, index0, index1);
453: List l = null;
454:
455: synchronized (this ) {
456: l = (List) (getListDataListeners().clone());
457: }
458:
459: Iterator i = l.iterator();
460: while (i.hasNext()) {
461: ((ListDataListener) i.next()).contentsChanged(evt);
462: }
463: }
464:
465: /** Add this XArrayList as a PropertyChangeListener to the given
466: target object. If the <code>propertyChangeListenerEnabled</code>
467: is false then this method returns immediately. Otherwise
468: reflection will be used on the target object to locate and invoke
469: the addPropertyChangeListener() method. If no method is found
470: then this method returns.
471:
472: @param target The target object
473: */
474:
475: private void addPropertyChangeListener(Object target) {
476: if (!propertyChangeListenerEnabled) {
477: return;
478: }
479:
480: try {
481: Method m = target.getClass().getMethod(
482: "addPropertyChangeListener", addPCListenerArgTypes);
483: Object[] args = { this };
484: m.invoke(target, args);
485: } catch (NoSuchMethodException e) {
486: // do nothing
487: } catch (Exception e) {
488: // do nothing (report errors?)
489: }
490: }
491:
492: /** Add PropertyChangeListeners to all objects in the collection.
493:
494: @param collection The collection of objects
495: */
496:
497: private void addPropertyChangeListener(Collection collection) {
498: Iterator i = collection.iterator();
499: while (i.hasNext()) {
500: addPropertyChangeListener(i.next());
501: }
502: }
503:
504: /** Remove this XArrayList as a PropertyChangeListener for the target
505: object.
506:
507: @param target The target object
508: */
509:
510: private void removePropertyChangeListener(Object target) {
511: if (!propertyChangeListenerEnabled) {
512: return;
513: }
514:
515: try {
516: Method m = target.getClass().getMethod(
517: "removePropertyChangeListener",
518: addPCListenerArgTypes);
519: Object[] args = { this };
520: m.invoke(target, args);
521: } catch (NoSuchMethodException e) {
522: // do nothing
523: } catch (Exception e) {
524: // do nothing (report errors?)
525: }
526: }
527:
528: /** Remove PropertyChangeListeners to all objects in the collection.
529:
530: @param collection The collection of objects
531: */
532:
533: private void removePropertyChangeListener(Collection collection) {
534: Iterator i = collection.iterator();
535: while (i.hasNext()) {
536: removePropertyChangeListener(i.next());
537: }
538: }
539:
540: }
|