001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.adapter;
032:
033: import javax.swing.ListModel;
034: import javax.swing.event.ListDataEvent;
035: import javax.swing.event.ListDataListener;
036: import javax.swing.table.AbstractTableModel;
037:
038: /**
039: * An abstract implementation of the {@link javax.swing.table.TableModel}
040: * interface that converts a {@link javax.swing.ListModel} of row elements.<p>
041: *
042: * This class provides default implementations for the <code>TableModel</code>
043: * methods <code>#getColumnCount()</code> and <code>#getColumnName(int)</code>.
044: * To use these methods you must use the constructor that accepts an
045: * array of column names and this array must not be <code>null</code>.
046: * If a subclass constructs itself with the column names set to <code>null</code>
047: * it must override the methods <code>#getColumnCount()</code> and
048: * <code>#getColumnName(int)</code>.<p>
049: *
050: * <strong>Example:</strong> API users subclass <code>AbstractTableAdapter</code>
051: * and just implement the method <code>TableModel#getValueAt(int, int)</code>.<p>
052: *
053: * The following example implementation is based on a list of customer rows
054: * and exposes the first and last name as well as the customer ages:<pre>
055: * public class CustomerTableModel extends AbstractTableAdapter {
056: *
057: * private static final String[] COLUMN_NAMES =
058: * { "Last Name", "First Name", "Age" };
059: *
060: * public CustomerTableModel(ListModel listModel) {
061: * super(listModel, COLUMN_NAMES);
062: * }
063: *
064: * public Object getValueAt(int rowIndex, int columnIndex) {
065: * Customer customer = (Customer) getRow(rowIndex);
066: * switch (columnIndex) {
067: * case 0 : return customer.getLastName();
068: * case 1 : return customer.getFirstName();
069: * case 2 : return customer.getAge();
070: * default: return null;
071: * }
072: * }
073: *
074: * }
075: * </pre>
076: *
077: * @author Karsten Lentzsch
078: * @version $Revision: 1.8 $
079: *
080: * @see javax.swing.ListModel
081: * @see javax.swing.JTable
082: *
083: * @param <E> the type of the ListModel elements
084: */
085: public abstract class AbstractTableAdapter<E> extends
086: AbstractTableModel {
087:
088: /**
089: * Refers to the <code>ListModel</code> that holds the table row elements
090: * and reports changes in the structure and content. The elements of
091: * the list model can be requested using <code>#getRow(int)</code>.
092: * A typical subclass will use the elements to implement the
093: * <code>TableModel</code> method <code>#getValueAt(int, int)</code>.
094: *
095: * @see #getRow(int)
096: * @see #getRowCount()
097: * @see javax.swing.table.TableModel#getValueAt(int, int)
098: */
099: private final ListModel listModel;
100:
101: /**
102: * Holds an optional array of column names that is used by the
103: * default implementation of the <code>TableModel</code> methods
104: * <code>#getColumnCount()</code> and <code>#getColumnName(int)</code>.
105: *
106: * @see #getColumnCount()
107: * @see #getColumnName(int)
108: */
109: private final String[] columnNames;
110:
111: // Instance Creation ******************************************************
112:
113: /**
114: * Constructs an AbstractTableAdapter on the given ListModel.
115: * Subclasses that use this constructor must override the methods
116: * <code>#getColumnCount()</code> and <code>#getColumnName(int)</code>.
117: *
118: * @param listModel the ListModel that holds the row elements
119: * @throws NullPointerException if the list model is <code>null</code>
120: */
121: public AbstractTableAdapter(ListModel listModel) {
122: this (listModel, (String[]) null);
123: }
124:
125: /**
126: * Constructs an AbstractTableAdapter on the given ListModel using
127: * the specified table column names. If the column names array is
128: * non-<code>null</code>, it is copied to avoid external mutation.<p>
129: *
130: * Subclasses that invoke this constructor with a <code>null</code> column
131: * name array must override the methods <code>#getColumnCount()</code> and
132: * <code>#getColumnName(int)</code>.
133: *
134: * @param listModel the ListModel that holds the row elements
135: * @param columnNames optional column names
136: * @throws NullPointerException if the list model is <code>null</code>
137: */
138: public AbstractTableAdapter(ListModel listModel,
139: String... columnNames) {
140: this .listModel = listModel;
141: if (listModel == null)
142: throw new NullPointerException(
143: "The list model must not be null.");
144:
145: if ((columnNames == null) || (columnNames.length == 0)) {
146: this .columnNames = null;
147: } else {
148: this .columnNames = new String[columnNames.length];
149: System.arraycopy(columnNames, 0, this .columnNames, 0,
150: columnNames.length);
151: }
152:
153: listModel.addListDataListener(createChangeHandler());
154: }
155:
156: // TableModel Implementation **********************************************
157:
158: /**
159: * Returns the number of columns in the model. A JTable uses
160: * this method to determine how many columns it should create and
161: * display by default.<p>
162: *
163: * Subclasses must override this method if they don't provide an
164: * array of column names in the constructor.
165: *
166: * @return the number of columns in the model
167: * @throws NullPointerException if the optional column names array
168: * has not been set in the constructor. In this case API users
169: * must override this method.
170: *
171: * @see #getColumnName(int)
172: * @see #getRowCount()
173: */
174: public int getColumnCount() {
175: return columnNames.length;
176: }
177:
178: /**
179: * Returns the name of the column at the given column index.
180: * This is used to initialize the table's column header name.
181: * Note: this name does not need to be unique; two columns in a table
182: * can have the same name.<p>
183: *
184: * Subclasses must override this method if they don't provide an
185: * array of column names in the constructor.
186: *
187: * @param columnIndex the index of the column
188: * @return the name of the column
189: * @throws NullPointerException if the optional column names array
190: * has not been set in the constructor. In this case API users
191: * must override this method.
192: *
193: * @see #getColumnCount()
194: * @see #getRowCount()
195: */
196: @Override
197: public String getColumnName(int columnIndex) {
198: return columnNames[columnIndex];
199: }
200:
201: /**
202: * Returns the number of rows in the model. A
203: * <code>JTable</code> uses this method to determine how many rows it
204: * should display. This method should be quick, as it
205: * is called frequently during rendering.
206: *
207: * @return the number of rows in the model
208: *
209: * @see #getRow(int)
210: */
211: public final int getRowCount() {
212: return listModel.getSize();
213: }
214:
215: // Misc *******************************************************************
216:
217: /**
218: * Returns the row at the specified row index.
219: *
220: * @param index row index in the underlying list model
221: * @return the row at the specified row index.
222: */
223: public final E getRow(int index) {
224: return (E) listModel.getElementAt(index);
225: }
226:
227: // Event Handling *********************************************************
228:
229: /**
230: * Creates and returns a listener that handles changes
231: * in the underlying list model.
232: *
233: * @return the listener that handles changes in the underlying ListModel
234: */
235: protected ListDataListener createChangeHandler() {
236: return new ListDataChangeHandler();
237: }
238:
239: /**
240: * Listens to subject changes and fires a contents change event.
241: */
242: private final class ListDataChangeHandler implements
243: ListDataListener {
244:
245: /**
246: * Sent after the indices in the index0,index1
247: * interval have been inserted in the data model.
248: * The new interval includes both index0 and index1.
249: *
250: * @param evt a <code>ListDataEvent</code> encapsulating the
251: * event information
252: */
253: public void intervalAdded(ListDataEvent evt) {
254: fireTableRowsInserted(evt.getIndex0(), evt.getIndex1());
255: }
256:
257: /**
258: * Sent after the indices in the index0,index1 interval
259: * have been removed from the data model. The interval
260: * includes both index0 and index1.
261: *
262: * @param evt a <code>ListDataEvent</code> encapsulating the
263: * event information
264: */
265: public void intervalRemoved(ListDataEvent evt) {
266: fireTableRowsDeleted(evt.getIndex0(), evt.getIndex1());
267: }
268:
269: /**
270: * Sent when the contents of the list has changed in a way
271: * that's too complex to characterize with the previous
272: * methods. For example, this is sent when an item has been
273: * replaced. Index0 and index1 bracket the change.
274: *
275: * @param evt a <code>ListDataEvent</code> encapsulating the
276: * event information
277: */
278: public void contentsChanged(ListDataEvent evt) {
279: int firstRow = evt.getIndex0();
280: int lastRow = evt.getIndex1();
281: fireTableRowsUpdated(firstRow, lastRow);
282: }
283:
284: }
285:
286: }
|