001: // ============================================================================
002: // $Id: GenericTableModel.java,v 1.14 2005/08/02 23:45:21 davidahall Exp $
003: // Copyright (c) 2003-2005 David A. Hall
004: // ============================================================================
005: // The contents of this file are subject to the Common Development and
006: // Distribution License (CDDL), Version 1.0 (the License); you may not use this
007: // file except in compliance with the License. You should have received a copy
008: // of the the License along with this file: if not, a copy of the License is
009: // available from Sun Microsystems, Inc.
010: //
011: // http://www.sun.com/cddl/cddl.html
012: //
013: // From time to time, the license steward (initially Sun Microsystems, Inc.) may
014: // publish revised and/or new versions of the License. You may not use,
015: // distribute, or otherwise make this file available under subsequent versions
016: // of the License.
017: //
018: // Alternatively, the contents of this file may be used under the terms of the
019: // GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which
020: // case the provisions of the LGPL are applicable instead of those above. If you
021: // wish to allow use of your version of this file only under the terms of the
022: // LGPL, and not to allow others to use your version of this file under the
023: // terms of the CDDL, indicate your decision by deleting the provisions above
024: // and replace them with the notice and other provisions required by the LGPL.
025: // If you do not delete the provisions above, a recipient may use your version
026: // of this file under the terms of either the CDDL or the LGPL.
027: //
028: // This library is distributed in the hope that it will be useful,
029: // but WITHOUT ANY WARRANTY; without even the implied warranty of
030: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
031: // ============================================================================
032: package net.sf.jga.swing;
033:
034: import java.util.Iterator;
035: import java.util.List;
036: import javax.swing.table.AbstractTableModel;
037: import javax.swing.table.DefaultTableColumnModel;
038: import javax.swing.table.TableCellEditor;
039: import javax.swing.table.TableCellRenderer;
040: import javax.swing.table.TableColumnModel;
041: import net.sf.jga.fn.BinaryFunctor;
042: import net.sf.jga.fn.EvaluationException;
043: import net.sf.jga.fn.UnaryFunctor;
044: import net.sf.jga.fn.property.ConstructUnary;
045: import net.sf.jga.fn.property.GetProperty;
046: import net.sf.jga.fn.property.SetProperty;
047: import net.sf.jga.fn.string.DefaultFormat;
048: import net.sf.jga.fn.string.FormatValue;
049: import net.sf.jga.fn.string.ParseFormat;
050: import net.sf.jga.fn.adaptor.Identity;
051:
052: /**
053: * TableModel that uses a list of data for storage, and whose columns contain
054: * functors that read (and possibly write) properties of the objects in the
055: * list.
056: * <p>
057: * This class uses GenericTableColumns to store information about each specific
058: * column. To keep the correct column classes in use by the table, it is
059: * important to build the table using the constructor that takes both the data
060: * model and the column model, as shown in the code sample below. Otherwise,
061: * the table will build a default TableColumnModel of its own, and additions
062: * of columns to the data model won't be reflected in the column model.
063: * <p>
064: * Another caveat on the use of this class is that if columns are to be added to
065: * the model after it has been given to a table, then the table's
066: * autoCreateColumnsFromModel flag must be false (otherwise, the table will
067: * discard the existing columns and attempt to build new ones: JTable won't know
068: * how to create instances of the generic classes.
069: * <P>
070: * Working with this class can be fairly simple. To build a simple table that
071: * allows for the display and editing of a typical business object (Item, in
072: * this example),
073: * <p>
074: * <pre>
075: * List<Item> data = // initialized from somewhere
076: * GenericTableModel<Item> model =
077: * new GenericTableModel<Item>(Item.class, data);
078: *
079: * // adds a read-only column for the object's id
080: * model.addColumn(Integer.class, "ID");
081: *
082: * // adds a read-only column for the object's name
083: * model.addColumn(String.class, "Name");
084: *
085: * // adds an editable column for the object's description
086: * model.addColumn(String.class, "Desc", true);
087: *
088: * // adds an editable column for the object's count
089: * model.addColumn(Integer.class, "Count", true);
090: *
091: * // adds an editable column for the object's price
092: * model.addColumn(BigDecimal.class, "Price", true);
093: *
094: * JTable table = new JTable(model, model.getColumnModel());
095: * </pre>
096: * <p>
097: * In this example, the Item class is presumed to have the appropriate
098: * getter/setter methods defined.
099: * <p>
100: * Copyright © 2003-2005 David A. Hall
101: *
102: * @author <a href="mailto:davidahall@users.sf.net">David A. Hall</a>
103: */
104:
105: // TODO: there should be an associated GenericTableColumnModel to go along
106: // with this. It might be possible for addColumn to be safe after the
107: // table has been assigned (if the table consults the TableColumnModel
108: // when it rebuilds (but it probably doesn't))
109: // NOTE: Tried this -- there are problems. Specifically, the column model
110: // really wants to share (or take over)some of the internal state. It
111: // leads to having column, model, and column model, all of which need to
112: // be used together or not at all. The only real way to keep everything
113: // in sync is to create a GenericTable that overrides the default class
114: // used for model,column,and column model. I didn't want to go that far.
115: // TODO: this should wrap a ListModel
116: public class GenericTableModel<T> extends AbstractTableModel {
117:
118: static final long serialVersionUID = 8996104516840561845L;
119:
120: private Class<T> _rowtype;
121: private List<T> _values;
122:
123: private DefaultTableColumnModel _columnModel = new DefaultTableColumnModel();
124:
125: /**
126: * Builds a GenericTableModel for the given list of data. The columns
127: * must be added separately before the table may be rendered.
128: */
129: public GenericTableModel(Class<T> rowtype, List<T> values) {
130: _rowtype = rowtype;
131: _values = values;
132: }
133:
134: /**
135: * Builds a GenericTableModel for the given list of data, using the
136: * given list of columns.
137: */
138: public GenericTableModel(Class<T> rowtype, List<T> values,
139: List<GenericTableColumn<T, ?>> columns) {
140: _rowtype = rowtype;
141: _values = values;
142: Iterator<GenericTableColumn<T, ?>> iter = columns.iterator();
143: while (iter.hasNext())
144: _columnModel.addColumn(iter.next());
145: }
146:
147: /**
148: * Returns the column model that should be used in conjunction with this
149: * data model.
150: */
151:
152: public TableColumnModel getColumnModel() {
153: return _columnModel;
154: }
155:
156: /**
157: * Returns the value that corresponds to the given row of the table
158: */
159: public T getRowValue(int row) {
160: return _values.get(row);
161: }
162:
163: // Flag that determines if the columns' names are to be used in the table
164: // header. Generally, that will be the case, so this defaults to true.
165: private boolean _nameUsedInHeader = true;
166:
167: /**
168: * Enables/Disables use of property names in the column headers of all
169: * columns subsequently added to the table. Existing header values will
170: * not be changed when this value changes. Defaults to true (enabled).
171: */
172: public void setNameUsedInHeader(boolean b) {
173: _nameUsedInHeader = b;
174: }
175:
176: /**
177: * Returns true if property names are to be used in the headers of the
178: * corresponding columns.
179: */
180: public boolean isNameUsedInHeader() {
181: return _nameUsedInHeader;
182: }
183:
184: /**
185: * Adds a read-only column that uses a GetProperty functor for the named
186: * property. Do not call this method after the model has been given to a
187: * table unless the table's autoCreateColumnsFromModel flag is unset.
188: */
189:
190: public <C> GenericTableColumn<T, C> addColumn(Class<C> coltype,
191: String name) {
192: GetProperty<T, C> getter = new GetProperty<T, C>(_rowtype, name);
193: return addColumnImpl(name, new GenericTableColumn<T, C>(
194: coltype, getter));
195: }
196:
197: /**
198: * Adds a possibly editable column that uses a GetProperty functor for
199: * the named property, and a SetProperty functor if the column is
200: * editable. Do not call this method after the model has been given to
201: * a table unless the table's autoCreateColumnsFromModel flag is unset.
202: */
203:
204: public <C> GenericTableColumn<T, C> addColumn(Class<C> coltype,
205: String name, boolean editable) {
206: GetProperty<T, C> getter = new GetProperty<T, C>(_rowtype, name);
207: SetProperty<T, C> setter = editable ? new SetProperty<T, C>(
208: _rowtype, name, coltype) : null;
209: return addColumnImpl(name, new GenericTableColumn<T, C>(
210: coltype, getter, setter));
211: }
212:
213: /**
214: * Adds a read-only column that uses a GetProperty functor for the named
215: * property and renders the property value in the given formatter. Do not
216: * call this method after the model has been given to a table unless the
217: * table's autoCreateColumnsFromModel flag is unset.
218: */
219: public <C> GenericTableColumn<T, C> addColumn(Class<C> coltype,
220: String name, UnaryFunctor<C, String> formatter) {
221: GetProperty<T, C> getter = new GetProperty<T, C>(_rowtype, name);
222: return addColumnImpl(name, new GenericTableColumn<T, C>(
223: coltype, getter, null, formatter, null));
224: }
225:
226: /**
227: * Adds an editable column that uses a GetProperty functor for
228: * the named property, and a SetProperty functor if the column is
229: * editable, and is rendered (and edited) using the given format. Do not
230: * call this method after the model has been given to a table unless the
231: * table's autoCreateColumnsFromModel flag is unset.
232: */
233: public <C> GenericTableColumn<T, C> addColumn(Class<C> coltype,
234: String name, UnaryFunctor<C, String> formatter,
235: UnaryFunctor<String, C> parser) {
236: GetProperty<T, C> getter = new GetProperty<T, C>(_rowtype, name);
237: SetProperty<T, C> setter = (parser != null) ? new SetProperty<T, C>(
238: _rowtype, name, coltype)
239: : null;
240:
241: return addColumnImpl(name, new GenericTableColumn<T, C>(
242: coltype, getter, setter, formatter, parser));
243: }
244:
245: /**
246: * Completes the configuration of a new column and adds it to the table
247: */
248: private <C> GenericTableColumn<T, C> addColumnImpl(String name,
249: GenericTableColumn<T, C> column) {
250: if (_nameUsedInHeader)
251: column.setHeaderValue(name);
252:
253: addColumn(column);
254: return column;
255: }
256:
257: /**
258: * Adds the given column to the table. Do not call this method
259: * after the model has been given to a table unless the table's
260: * autoCreateColumnsFromModel flag is unset.
261: */
262:
263: public void addColumn(GenericTableColumn<T, ?> col) {
264: col.setModelIndex(getColumnCount());
265: _columnModel.addColumn(col);
266: }
267:
268: /**
269: * Returns the GenericTableColumn associated with the given column index.
270: */
271:
272: public GenericTableColumn<T, ?> getGenericColumn(int col) {
273: // @SuppressWarnings
274: //
275: return (GenericTableColumn<T, ?>) _columnModel.getColumn(col);
276: }
277:
278: // -------------------------------------------------------------
279: // TableModel interface
280: // -------------------------------------------------------------
281:
282: public int getRowCount() {
283: return _values.size();
284: }
285:
286: public int getColumnCount() {
287: return _columnModel.getColumnCount();
288: }
289:
290: public Class getColumnClass(int col) {
291: return getGenericColumn(col).getColumnClass();
292: }
293:
294: public String getColumnName(int col) {
295: GenericTableColumn<T, ?> column = getGenericColumn(col);
296: Object name = column.getHeaderValue();
297: return (name != null) ? name.toString() : super
298: .getColumnName(col);
299: }
300:
301: public boolean isCellEditable(int row, int col) {
302: return getGenericColumn(col).isEditable();
303: }
304:
305: public Object getValueAt(int row, int col) {
306: T rowvalue = getRowValue(row);
307: GenericTableColumn<T, ?> column = getGenericColumn(col);
308: try {
309: return column.getValueAt(rowvalue);
310: } catch (EvaluationException x) {
311: x.printStackTrace();
312: return "### ERROR ###";
313: }
314: }
315:
316: public void setValueAt(Object value, int row, int col) {
317: T rowvalue = getRowValue(row);
318: GenericTableColumn<T, ?> column = getGenericColumn(col);
319: assert column.getColumnClass().isInstance(value);
320:
321: column.setValueAt(rowvalue, value);
322: fireTableCellUpdated(row, col);
323: }
324: }
|