-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathBeanTableModel.java
More file actions
396 lines (337 loc) · 11 KB
/
BeanTableModel.java
File metadata and controls
396 lines (337 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
import java.lang.reflect.*;
import java.util.*;
/**
* The BeanTableModel will use reflection to determine the columns of
* data to be displayed in the table model. Reflection is used to find all
* the methods declared in the specified bean. The criteria used for
* adding columns to the model are:
*
* a) the method name must start with either "get" or "is"
* b) the parameter list for the method must contain 0 parameters
*
* You can also specify an ancestor class in which case the declared methods
* of the ancestor and all its descendents will be included in the model.
*
* A column name will be assigned to each column based on the method name.
*
* The cell will be considered editable when a corresponding "set" method
* name is found.
*
* Reflection will also be used to implement the getValueAt() and
* setValueAt() methods.
*/
public class BeanTableModel<T> extends RowTableModel<T>
{
// Map "type" to "class". Class is needed for the getColumnClass() method.
private static Map<Class, Class> primitives = new HashMap<Class, Class>(10);
static
{
primitives.put(Boolean.TYPE, Boolean.class);
primitives.put(Byte.TYPE, Byte.class);
primitives.put(Character.TYPE, Character.class);
primitives.put(Double.TYPE, Double.class);
primitives.put(Float.TYPE, Float.class);
primitives.put(Integer.TYPE, Integer.class);
primitives.put(Long.TYPE, Long.class);
primitives.put(Short.TYPE, Short.class);
}
private Class beanClass;
private Class ancestorClass;
private List<ColumnInformation> columns = new ArrayList<ColumnInformation>();
/**
* Constructs an empty <code>BeanTableModel</code> for the specified bean.
*
* @param beanClass class of the beans that will be added to the model.
* The class is also used to determine the columns that
* will be displayed in the model
*/
public BeanTableModel(Class beanClass)
{
this(beanClass, beanClass, new ArrayList<T>());
}
/**
* Constructs an empty <code>BeanTableModel</code> for the specified bean.
*
* @param beanClass class of the beans that will be added to the model.
* @param ancestorClass the methods of this class and its descendents down
* to the bean class can be included in the model.
*/
public BeanTableModel(Class beanClass, Class ancestorClass)
{
this(beanClass, ancestorClass, new ArrayList<T>());
}
/**
* Constructs an empty <code>BeanTableModel</code> for the specified bean.
*
* @param beanClass class of the beans that will be added to the model.
* @param modelData the data of the table
*/
public BeanTableModel(Class beanClass, List<T> modelData)
{
this(beanClass, beanClass, modelData);
}
/**
* Constructs an empty <code>BeanTableModel</code> for the specified bean.
*
* @param beanClass class of the beans that will be added to the model.
* @param ancestorClass the methods of this class and its descendents down
* to the bean class can be included in the model.
* @param modelData the data of the table
*/
public BeanTableModel(Class beanClass, Class ancestorClass, List<T> modelData)
{
super( beanClass );
this.beanClass = beanClass;
this.ancestorClass = ancestorClass;
// Use reflection on the beanClass and ancestorClass to find properties
// to add to the TableModel
createColumnInformation();
// Initialize the column name List to the proper size. The actual
// column names will be reset in the resetModelDefaults() method.
List<String> columnNames = new ArrayList<String>();
for (ColumnInformation info: columns)
{
columnNames.add( info.getName() );
}
// Reset all the values in the RowTableModel
super.setDataAndColumnNames(modelData, columnNames);
resetModelDefaults();
}
/*
* Use reflection to find all the methods that should be included in the
* model.
*/
@SuppressWarnings("unchecked")
private void createColumnInformation()
{
Method[] theMethods = beanClass.getMethods();
// Check each method to make sure it should be used in the model
for (int i = 0; i < theMethods.length; i++)
{
Method theMethod = theMethods[i];
if (theMethod.getParameterTypes().length == 0
&& ancestorClass.isAssignableFrom(theMethod.getDeclaringClass()) )
{
String methodName = theMethod.getName();
if (theMethod.getName().startsWith("get"))
buildColumnInformation(theMethod, methodName.substring(3));
if (theMethod.getName().startsWith("is"))
buildColumnInformation(theMethod, methodName.substring(2));
}
}
}
/*
* We found a method candidate so gather the information needed to fully
* implemennt the table model.
*/
@SuppressWarnings("unchecked")
private void buildColumnInformation(Method theMethod, String theMethodName)
{
// Make sure the method returns an appropriate type
Class returnType = getReturnType( theMethod );
if (returnType == null) return;
// Convert the method name to a display name for each column and
// then check for a related "set" method.
String headerName = formatColumnName( theMethodName );
Method setMethod = null;
try
{
String setMethodName = "set" + theMethodName;
setMethod = beanClass.getMethod(setMethodName, theMethod.getReturnType());
}
catch(NoSuchMethodException e) {}
// We have all the information we need, so save it for later use
// by the table model methods.
ColumnInformation ci = new ColumnInformation(headerName, returnType, theMethod, setMethod);
columns.add( ci );
}
/*
* Make sure the return type of the method is something we can use
*/
private Class getReturnType(Method theMethod)
{
Class returnType = theMethod.getReturnType();
if (returnType.isInterface()
|| returnType.isArray())
return null;
// The primitive class type is different then the wrapper class of the
// primitive. We need the wrapper class.
if (returnType.isPrimitive())
returnType = primitives.get(returnType);
return returnType;
}
/*
* Use information collected from the bean to set model default values.
*/
private void resetModelDefaults()
{
columnNames.clear();
for (int i = 0; i < columns.size(); i++)
{
ColumnInformation info = columns.get(i);
columnNames.add( info.getName() );
super.setColumnClass(i, info.getReturnType());
super.setColumnEditable(i, info.getSetter() == null ? false : true);
}
}
/**
* Returns an attribute value for the cell at <code>row</code>
* and <code>column</code>.
*
* @param row the row whose value is to be queried
* @param column the column whose value is to be queried
* @return the value Object at the specified cell
* @exception IndexOutOfBoundsException
* if an invalid row or column was given
*/
@Override
public Object getValueAt(int row, int column)
{
ColumnInformation ci = (ColumnInformation)columns.get( column );
Object value = null;
try
{
value = ci.getGetter().invoke(getRow(row));
}
catch(IllegalAccessException e) {}
catch(InvocationTargetException e) {}
return value;
}
/**
* Sets the object value for the cell at <code>column</code> and
* <code>row</code>. <code>value</code> is the new value. This method
* will generate a <code>tableChanged</code> notification.
*
* @param value the new value; this can be null
* @param row the row whose value is to be changed
* @param column the column whose value is to be changed
* @exception IndexOutOfBoundsException if an invalid row or
* column was given
*/
@Override
public void setValueAt(Object value, int row, int column)
{
ColumnInformation ci = (ColumnInformation)columns.get( column );
try
{
Method setMethod = ci.getSetter();
if (setMethod != null)
{
setMethod.invoke(getRow(row), value);
fireTableCellUpdated(row, column);
}
}
catch(IllegalAccessException e) {}
catch(InvocationTargetException e) {}
}
/**
* You are not allowed to change the class of any column.
*/
@Override
public void setColumnClass(int column, Class columnClass)
{
}
/**
* Sets the editability for the specified column.
*
* Override to make sure you can't set a column editable that doesn't
* have a defined setter method.
*
* @param column the column whose Class is being changed
* @param isEditable indicates if the column is editable or not
* @exception ArrayIndexOutOfBoundsException
* if an invalid column was given
*/
@Override
public void setColumnEditable(int column, boolean isEditable)
{
ColumnInformation ci = (ColumnInformation)columns.get( column );
if (isEditable && ci.getSetter() == null) return;
super.setColumnEditable(column, isEditable);
}
/**
* Convenience method to change the generated column header name.
*
* This method must be invoked before the model is added to the table.
*
* @param column the column whose value is to be queried
* @exception IndexOutOfBoundsException if an invalid column
* was given
*/
public void setColumnName(int column, String name)
{
ColumnInformation ci = (ColumnInformation)columns.get( column );
ci.setName( name );
resetModelDefaults();
}
/*
* Columns are created in the order in which they are defined in the
* bean class. This method will sort the columns by colum header name.
*
* This method must be invoked before the model is added to the table.
*/
public void sortColumnNames()
{
Collections.sort(columns);
resetModelDefaults();
}
/*
* Class to hold data required to implement the TableModel interface
*/
private class ColumnInformation implements Comparable<ColumnInformation>
{
private String name;
private Class returnType;
private Method getter;
private Method setter;
public ColumnInformation(String name, Class returnType, Method getter, Method setter)
{
this.name = name;
this.returnType = returnType;
this.getter = getter;
this.setter = setter;
}
/*
* The column class of the model
*/
public Class getReturnType()
{
return returnType;
}
/*
* Used by the getValueAt() method to get the data for the cell
*/
public Method getGetter()
{
return getter;
}
/*
* The value used as the column header name
*/
public String getName()
{
return name;
}
/*
* Used by the setValueAt() method to update the bean
*/
public Method getSetter()
{
return setter;
}
/*
* Use to change the column header name
*/
public void setName(String name)
{
this.name = name;
}
/*
* Implement the natural sort order for this class
*/
public int compareTo(ColumnInformation o)
{
return getName().compareTo(o.getName());
}
}
}