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.util.Map;
051: import java.util.TreeMap;
052: import java.util.Iterator;
053: import java.util.ArrayList;
054: import java.util.Collection;
055:
056: import javax.swing.ListModel;
057: import javax.swing.table.TableModel;
058: import javax.swing.event.ListDataEvent;
059: import javax.swing.event.ListDataListener;
060: import javax.swing.event.TableModelEvent;
061: import javax.swing.event.TableModelListener;
062:
063: import org.apache.commons.logging.Log;
064: import org.apache.commons.logging.LogFactory;
065:
066: /** An implementation of a TreeMap which can be placed in a JTable or
067: in a JList. When used in a JTable the table will have two columns:
068: column 1 for the key and column 2 for the value. Whan used in a
069: JList the keys will be used by default for display.
070:
071: <p>Note that since this class extends from the TreeMap class the keys
072: will be sorted in natural order as described in the TreeMap
073: documentation.
074:
075: @author Anthony Eden
076: */
077:
078: public class XTreeMap extends TreeMap implements TableModel, ListModel {
079:
080: private static final Log log = LogFactory.getLog(XTreeMap.class);
081:
082: private ArrayList tableModelListeners = new ArrayList();
083: private ArrayList listDataListeners = new ArrayList();
084: private String[] columnNames = { "Key", "Value" };
085: private Class[] columnClasses = { String.class, String.class };
086:
087: private boolean listUsesKey = false;
088:
089: /** Clear the map. */
090:
091: public void clear() {
092: int mapSize = size();
093: if (mapSize > 0) {
094: super .clear();
095: fireTableChanged(new TableModelEvent(this ));
096: fireIntervalRemoved(0, size() - 1);
097: }
098: }
099:
100: /** Put the given object into the map using the given key. If there
101: is already an object in the map with the given key then the object
102: being replaced will be returned. If there is not value for the
103: given key then the return value will be null.
104:
105: @param key The key
106: @param value The value
107: @return The previous object
108: */
109:
110: public Object put(Object key, Object value) {
111: Object oldValue = null;
112:
113: if (containsKey(key)) {
114: int index = keyIndex(key);
115: oldValue = super .put(key, value);
116: fireTableChanged(new TableModelEvent(this , index));
117: fireContentsChanged(index, index);
118: } else {
119: oldValue = super .put(key, value);
120: int index = keyIndex(key);
121: fireRowsInserted(index, index);
122: fireIntervalAdded(index, index);
123: }
124:
125: return oldValue;
126: }
127:
128: /** Put all of the given map into this map.
129:
130: @param map The map to insert
131: */
132:
133: public void putAll(Map map) {
134: super .putAll(map);
135: fireTableChanged(new TableModelEvent(this ));
136: fireContentsChanged(0, size() - 1);
137: }
138:
139: /** Remove the object for the given key. This method will return
140: the removed object or null if no object is removed.
141:
142: @param key The key
143: @return The removed object or null
144: */
145:
146: public Object remove(Object key) {
147: int index = keyIndex(key);
148: Object oldValue = super .remove(key);
149: fireRowsDeleted(index, index);
150: fireIntervalRemoved(index, index);
151: return oldValue;
152: }
153:
154: /** Get the index of the given key or -1 if there is no matching key.
155:
156: @param key The key
157: @return The index
158: */
159:
160: public int keyIndex(Object key) {
161: int index = 0;
162: Iterator i = keySet().iterator();
163: while (i.hasNext()) {
164: if (i.next().equals(key)) {
165: return index;
166: }
167: index++;
168: }
169: return -1;
170: }
171:
172: /** Get the key at the given index.
173:
174: @param index The index
175: @return The key at the given index
176: */
177:
178: public Object keyAt(int index) {
179: //log.debug("keyAt(" + index + ")");
180: return elementAt(index, keySet());
181: }
182:
183: /** Get the value at the given index.
184:
185: @param index The index
186: @return The value at the given index
187: */
188:
189: public Object valueAt(int index) {
190: //log.debug("valueAt(" + index + ")");
191: return elementAt(index, values());
192: }
193:
194: /** Get the element at the given index in the given collection.
195:
196: @param index The index
197: @param c The collection
198: @return The element at index in the collection
199: */
200:
201: protected Object elementAt(int index, Collection c) {
202: int x = 0;
203: Iterator i = c.iterator();
204: while (i.hasNext()) {
205: Object obj = i.next();
206: if (x == index) {
207: return obj;
208: }
209: x++;
210: }
211: return null;
212: }
213:
214: /** Return true if the map keys are returned in the ListModel getElementAt()
215: method. If this method returns false then the getElementAt() method
216: returns map values.
217:
218: @return True if getElementAt() returns keys
219: */
220:
221: public boolean getListUsesKey() {
222: return listUsesKey;
223: }
224:
225: /** Set to true if the map keys should be returned in the ListModel
226: getElementAt() method. Set to false if the getElementAt() method should
227: return map values.
228:
229: @param listUsesKey True if getElementAt() should return keys
230: */
231:
232: public void setListUsesKey(boolean listUsesKey) {
233: this .listUsesKey = listUsesKey;
234: }
235:
236: // TableModel implementation
237:
238: /** Get the total number of rows.
239:
240: @return The total number of rows
241: */
242:
243: public int getRowCount() {
244: return size();
245: }
246:
247: /** Get the total number of columns.
248:
249: @return The column count
250: */
251:
252: public int getColumnCount() {
253: return columnNames.length;
254: }
255:
256: /** Get the column name for the given column.
257:
258: @param column The column index
259: @return The column name
260: */
261:
262: public String getColumnName(int column) {
263: return columnNames[column];
264: }
265:
266: /** Set the column name for the given column index.
267:
268: @param column The column index
269: @param name The new name
270: */
271:
272: public synchronized void setColumnName(int column, String name) {
273: columnNames[column] = name;
274: }
275:
276: /** Get the given column's class.
277:
278: @param column The column
279: @return The column class
280: */
281:
282: public Class getColumnClass(int column) {
283: return columnClasses[column];
284: }
285:
286: public synchronized void setColumnClass(int column,
287: Class columnClass) {
288: columnClasses[column] = columnClass;
289: }
290:
291: /** Return true if the cell is editable. The default behavior allows
292: the UI user to change the key value.
293:
294: @param row The row
295: @param column The column
296: @return True if the column is editable
297: */
298:
299: public boolean isCellEditable(int row, int column) {
300: return (column == 1);
301: }
302:
303: /** Get the value at the given row and column.
304:
305: @param row The row
306: @param column The column
307: @return The value
308: */
309:
310: public Object getValueAt(int row, int column) {
311: switch (column) {
312: case 0:
313: Object key = keyAt(row);
314: //log.debug("Key at " + row + "=" + key);
315: return key;
316: case 1:
317: Object value = valueAt(row);
318: //log.debug("Value at " + row + "=" + value);
319: return value;
320: default:
321: throw new IllegalArgumentException("Column must be 0 or 1");
322: }
323: }
324:
325: /** Set the value at the given row and column.
326:
327: @param value The new value
328: @param row The row
329: @param column The column
330: */
331:
332: public synchronized void setValueAt(Object value, int row,
333: int column) {
334: if (isCellEditable(row, column)) {
335: Object mapKey = keyAt(row);
336: Object mapValue = get(mapKey);
337:
338: switch (column) {
339: case 0:
340: remove(mapKey);
341: put(value, mapValue);
342: break;
343: case 1:
344: put(mapKey, value);
345: break;
346: default:
347: throw new IllegalArgumentException(
348: "Column must be 0 or 1");
349: }
350: }
351: }
352:
353: /** Add a TableModelListener.
354:
355: @param l The TableModelListener to add
356: */
357:
358: public void addTableModelListener(TableModelListener l) {
359: tableModelListeners.add(l);
360: }
361:
362: /** Remove a TableModelListener.
363:
364: @param l The TableModelListener to remove
365: */
366:
367: public void removeTableModelListener(TableModelListener l) {
368: tableModelListeners.remove(l);
369: }
370:
371: // ListModel implementation
372:
373: /** Get the element at the given index.
374:
375: @param index The index
376: @return The element
377: */
378:
379: public Object getElementAt(int index) {
380: if (listUsesKey) {
381: return keyAt(index);
382: } else {
383: return valueAt(index);
384: }
385: }
386:
387: /** Get the list size.
388:
389: @return The list size
390: */
391:
392: public int getSize() {
393: return size();
394: }
395:
396: /** Add a ListDataListener.
397:
398: @param l The ListDataListener
399: */
400:
401: public void addListDataListener(ListDataListener l) {
402: listDataListeners.add(l);
403: }
404:
405: /** Remove a ListDataListener.
406:
407: @param l The ListDataListener
408: */
409:
410: public void removeListDataListener(ListDataListener l) {
411: listDataListeners.remove(l);
412: }
413:
414: // Event firing methods
415:
416: protected void fireTableChanged(TableModelEvent evt) {
417: ArrayList l = null;
418:
419: synchronized (this ) {
420: l = (ArrayList) (tableModelListeners.clone());
421: }
422:
423: Iterator i = l.iterator();
424: while (i.hasNext()) {
425: ((TableModelListener) i.next()).tableChanged(evt);
426: }
427: }
428:
429: protected void fireRowsInserted(int index0, int index1) {
430: fireTableChanged(new TableModelEvent(this , index0, index1,
431: TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
432: }
433:
434: protected void fireRowsDeleted(int index0, int index1) {
435: fireTableChanged(new TableModelEvent(this , index0, index1,
436: TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
437: }
438:
439: protected void fireIntervalAdded(int index0, int index1) {
440: ArrayList l = null;
441: ListDataEvent evt = new ListDataEvent(this ,
442: ListDataEvent.INTERVAL_ADDED, index0, index1);
443:
444: synchronized (this ) {
445: l = (ArrayList) (listDataListeners.clone());
446: }
447:
448: Iterator i = l.iterator();
449: while (i.hasNext()) {
450: ((ListDataListener) i.next()).intervalAdded(evt);
451: }
452: }
453:
454: protected void fireIntervalRemoved(int index0, int index1) {
455: ArrayList l = null;
456: ListDataEvent evt = new ListDataEvent(this ,
457: ListDataEvent.INTERVAL_REMOVED, index0, index1);
458:
459: synchronized (this ) {
460: l = (ArrayList) (listDataListeners.clone());
461: }
462:
463: Iterator i = l.iterator();
464: while (i.hasNext()) {
465: ((ListDataListener) i.next()).intervalRemoved(evt);
466: }
467: }
468:
469: protected void fireContentsChanged(int index0, int index1) {
470: ArrayList l = null;
471: ListDataEvent evt = new ListDataEvent(this ,
472: ListDataEvent.CONTENTS_CHANGED, index0, index1);
473:
474: synchronized (this ) {
475: l = (ArrayList) (listDataListeners.clone());
476: }
477:
478: Iterator i = l.iterator();
479: while (i.hasNext()) {
480: ((ListDataListener) i.next()).contentsChanged(evt);
481: }
482: }
483:
484: }
|