Source code: com/pjsofts/eurobudget/beans/BeanTableModel.java
1 package com.pjsofts.eurobudget.beans;
2
3 import com.pjsofts.eurobudget.EBConstants;
4 import java.beans.*;
5 import java.lang.reflect.InvocationTargetException;
6 import java.util.*;
7 import javax.swing.table.AbstractTableModel;
8
9 /**
10 *
11 * Simple Table model to display any bean in read only or write mode.
12 * Need to use it with a nice bean (got bean info ..)
13 * quite powerful already (editable, filterable (one column value), ...)
14 * Future:
15 * make it autosizable, find, goto,sortable, choose columns, save columns order...
16 *
17 */
18 public class BeanTableModel extends AbstractTableModel {
19
20 /** List of same Bean class, original list of beans */
21 protected List list;
22
23 /** displayed list */
24 protected List filteredList;
25
26 /** */
27 private BeanInfo beanInfo = null;
28
29 /** not hidden descriptors */
30 private PropertyDescriptor[] descriptors = null;
31
32 /** contains the displayed columns order */
33 private String[] columns=null;
34
35 /** is the whole table editable ?? */
36 private boolean tableEditable = true;
37
38 private static final ResourceBundle i8n = EBConstants.i18n;
39
40 /**
41 * @param not empty list and modifiable also (if editable), should not contain twice same bean, better to be a RandomAccess
42 * Should also contains only instances of same bean (same class !!!)
43 */
44 public BeanTableModel(List list, boolean editable, Class beanClass) {
45 if ( list == null ) list = new ArrayList();
46 this.list = list;
47 this.filteredList = list;
48 assert !list.isEmpty();
49 //assert list contains only instances of same class
50 //Object bean = list.get(0);
51 initStructureToBean(beanClass);//bean.getClass());
52 this.tableEditable = editable;
53 }
54
55 private void initStructureToBean(Class beanClass) {
56 int size = 0;
57 BeanDescriptor descriptor;
58 PropertyDescriptor[] props = null;
59 try {
60 this.beanInfo= Introspector.getBeanInfo(beanClass,Introspector.USE_ALL_BEANINFO);
61 descriptor = beanInfo.getBeanDescriptor();
62 props = beanInfo.getPropertyDescriptors();
63 size = props.length;
64 } catch ( IntrospectionException ie) {
65 ie.printStackTrace();
66 }
67 // remove hidden descriptor
68 if ( size > 0 && props != null) {
69 Vector v = new Vector();
70 for (int i=0; i<props.length ;i++) {
71 if ( ! props[i].isHidden() ) {
72 v.add(props[i]);
73 }
74 }
75 this.descriptors = (PropertyDescriptor[])v.toArray(new PropertyDescriptor[v.size()]);
76 columns = new String[v.size()];
77 for (int i=0; i<v.size() ;i++) {
78 columns[i] = this.descriptors[i].getName();
79 }
80 }
81 }
82
83 /** convenient method
84 * @param attribute name of used bean (ignore case)
85 * @return column index in model or -1 if not found
86 */
87 public int getColumnIndex(String attName) {
88 for (int i=0; i<descriptors.length ;i++) {
89 if ( descriptors[i].getName().equalsIgnoreCase(attName) )
90 return i;
91 }
92 return -1;
93 }
94
95 public int getRowCount() {return filteredList != null ? filteredList.size() : 0; }
96
97 public int getColumnCount(){
98 return columns != null ? columns.length : 0;
99 }
100
101 public Object getValueAt(int row, int col){
102 Object bean = filteredList.get(row);
103 Object result = null;
104 try {
105 result = descriptors[col].getReadMethod().invoke(bean,null);
106 } catch (InvocationTargetException ite ) {
107 ite.printStackTrace();
108 } catch (IllegalAccessException iae ) {
109 iae.printStackTrace();
110 }
111 return result;
112 }
113
114 /** */
115 public String getColumnName(int col) {
116 return descriptors[col].getDisplayName();
117 }
118
119 /*
120 * JTable uses this method to determine the default renderer/
121 * editor for each cell. If we didn't implement this method,
122 * then the last column would contain text ("true"/"false"),
123 * rather than a check box.
124 */
125 public Class getColumnClass(int c) {
126 return descriptors[c].getPropertyType();
127 }
128
129 public boolean isCellEditable(int row, int col) {
130 return this.tableEditable;
131 }
132
133 /**
134 * Sets the value in the cell at <code>columnIndex</code> and
135 * <code>rowIndex</code> to <code>aValue</code>.
136 *
137 * @param aValue the new value
138 * @param rowIndex the row whose value is to be changed
139 * @param columnIndex the column whose value is to be changed
140 * @see #getValueAt
141 * @see #isCellEditable
142 */
143 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
144 Object bean = filteredList.get(rowIndex);
145 try {
146 Object []args = new Object[1];
147 args[0] = aValue;
148 descriptors[columnIndex].getWriteMethod().invoke(bean,args);
149 } catch (InvocationTargetException ite ) {
150 ite.printStackTrace();
151 } catch (IllegalAccessException iae ) {
152 iae.printStackTrace();
153 }
154 // super.setValueAt(aValue, rowIndex, columnIndex);
155 fireTableCellUpdated(rowIndex,columnIndex);
156 }
157
158 /** create and @return line of a new bean (or -1 if error)*/
159 public int newRow() {
160 Class beanClass = beanInfo.getBeanDescriptor().getBeanClass();
161 Object newBean = null;
162 try { newBean =beanClass.newInstance();
163 } catch(InstantiationException ie) {
164 ie.printStackTrace();
165 } catch(IllegalAccessException iae) {
166 iae.printStackTrace();
167 }
168 if ( newBean != null ) {
169 int row = addRow(newBean);
170 return row;
171 }
172 return -1;
173 }
174
175 /** add a bean to the list
176 * @return line number
177 */
178 public int addRow(Object bean) {
179 int ind = filteredList.size();
180 this.filteredList.add(ind, bean);
181 if ( this.list != this.filteredList )
182 this.list.add(bean);
183 fireTableRowsInserted(ind,ind);
184 return ind;
185 }
186
187 public void deleteRow(int row){
188 Object o = filteredList.remove(row);
189 if ( this.list != this.filteredList )
190 this.list.remove(o);
191 fireTableRowsDeleted(row, row);
192 }
193
194 /**
195 * change the model structure to show only beans that got 'value' as 'column'
196 * @param column column index, -1 means no more filter
197 * @param value on which to filter (use equals not == ), don't like null
198 */
199 public void applyFilter(int col, Object value) {
200 if ( col == -1 ) {
201 this.filteredList = this.list;
202 } else {
203 List newList = null;
204 synchronized ( list ) {
205 newList = new ArrayList(this.list.size());
206 for (Iterator it = this.list.iterator(); it.hasNext() ;) {
207 Object object = it.next();
208 Object beanValue = null;
209 try { beanValue = descriptors[col].getReadMethod().invoke(object,null);
210 } catch (IllegalAccessException iae) {
211 iae.printStackTrace();
212 } catch (InvocationTargetException ite) {
213 ite.printStackTrace();
214 }
215 if ( value.equals(beanValue) )
216 newList.add(object);
217 }
218 }
219 this.filteredList = null;//gc flag
220 this.filteredList = newList;
221 }
222 fireTableDataChanged();
223 }
224
225 /**
226 * change the model structure to sort items on this column.
227 * Stable sort.
228 * Bean properties classes must support a Comparable interface
229 * @param column column index, -1 means no more sort.
230 */
231 public void sortColumn(int col, boolean ascending ) {
232 if ( col == -1 ) {
233 this.filteredList = this.list;
234 fireTableStructureChanged(); //reinit headers
235 } else {
236 List newList;
237 synchronized ( this.filteredList ) {
238 newList = new ArrayList(this.filteredList);//copy old list
239 Collections.sort(newList, new BeanComparator(col,ascending));
240 }
241 this.filteredList = null;//gc flag
242 this.filteredList = newList;
243 fireTableDataChanged(); //update only data, caller should update header if they want
244 }
245 }
246
247
248 private class BeanComparator implements Comparator {
249 private int index;
250 private boolean ascending;
251 public BeanComparator(int index, boolean ascending) {
252 this.index = index;
253 this.ascending = ascending;
254 }
255 /**
256 * Compares its two arguments for order. Returns a negative integer,
257 * zero, or a positive integer as the first argument is less than, equal
258 * to, or greater than the second.<p>
259 *
260 * The implementor must ensure that <tt>sgn(compare(x, y)) ==
261 * -sgn(compare(y, x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
262 * implies that <tt>compare(x, y)</tt> must throw an exception if and only
263 * if <tt>compare(y, x)</tt> throws an exception.)<p>
264 *
265 * The implementor must also ensure that the relation is transitive:
266 * <tt>((compare(x, y)>0) && (compare(y, z)>0))</tt> implies
267 * <tt>compare(x, z)>0</tt>.<p>
268 *
269 * Finally, the implementer must ensure that <tt>compare(x, y)==0</tt>
270 * implies that <tt>sgn(compare(x, z))==sgn(compare(y, z))</tt> for all
271 * <tt>z</tt>.<p>
272 *
273 * It is generally the case, but <i>not</i> strictly required that
274 * <tt>(compare(x, y)==0) == (x.equals(y))</tt>. Generally speaking,
275 * any comparator that violates this condition should clearly indicate
276 * this fact. The recommended language is "Note: this comparator
277 * imposes orderings that are inconsistent with equals."
278 *
279 * @param o1 the first object to be compared.
280 * @param o2 the second object to be compared.
281 * @return a negative integer, zero, or a positive integer as the
282 * first argument is less than, equal to, or greater than the
283 * second.
284 * @throws ClassCastException if the arguments' types prevent them from
285 * being compared by this Comparator.
286 */
287 public int compare(Object o1, Object o2) {
288 Object v1 = null,v2 = null;
289 try {
290 v1 = descriptors[index].getReadMethod().invoke(o1,null);
291 v2 = descriptors[index].getReadMethod().invoke(o2,null);
292 } catch (IllegalAccessException iae) {
293 iae.printStackTrace();
294 } catch (InvocationTargetException ite) {
295 ite.printStackTrace();
296 }
297 if ( v1 == null ) return ascending ? 1 : -1;
298 if ( v1 instanceof Comparable) {
299 Comparable c1 = (Comparable)v1;
300 Comparable c2 = (Comparable)v2;
301 //System.out.println(c1 + " <> "+c2);
302 return ascending ? c1.compareTo(c2) : c2.compareTo(c1);
303 } else {
304 throw new IllegalArgumentException(i8n.getString("error_Use_sort_only_on_comparable_fields!"));
305 }
306 }
307 }
308 }
309
310