View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Michael Powers
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.ui.swing.components;
20  
21  import java.awt.Color;
22  import java.awt.Component;
23  import java.awt.Font;
24  import java.awt.event.ActionEvent;
25  import java.awt.event.ActionListener;
26  import java.lang.reflect.Method;
27  import java.util.Hashtable;
28  import java.util.Vector;
29  
30  import javax.swing.Timer;
31  import javax.swing.table.AbstractTableModel;
32  
33  /***
34  * PropertyEditorTableModel introspects an object to facilitate
35  * editing it in a PropertyTable.
36  *
37  * Because the model always reflects the current state of the
38  * inspected object, it is useful to have a table update at
39  * automated intervals.  By default, this feature is turned off.
40  * If you turn it on, you'll want to remember to turn it off
41  * when you're done with the table model.
42  *
43  * @author michael@mpowers.net
44  * @author $Author: cgruber $
45  * @version $Revision: 904 $
46  */
47  public class PropertyEditorTableModel extends AbstractTableModel implements ActionListener
48  {
49  	protected Object inspectedObject = null;
50  
51  	final String[] columnNames = { "Property", "Value" };
52      static final String METHOD_TAG = " ";
53  
54  	Vector properties = new Vector();
55  	Hashtable methods = new Hashtable(0);
56  
57  	public void setObject( Object o ) {
58  		inspectedObject = o;
59  		Class c = o.getClass();
60  		Method[] m = c.getMethods();
61  
62  		properties = new Vector();
63  		methods = new Hashtable(m.length, 1F);
64  		String name, propertyName;
65  		for ( int i = 0; i < m.length; i++ ) {
66  //				if ( m[i].getName().startsWith( "get" ) )
67  //					System.out.println( m[i].getName() + ": " + m[i].getReturnType().getName() );
68  
69  			// get methods
70  			if (
71  				 ( ( m[i].getName().startsWith( "get" ) ) || ( m[i].getName().startsWith( "is" ) ) )
72  				&& ( m[i].getParameterTypes().length == 0 )
73  			) {
74                  name = m[i].getName();
75                  if ( m[i].getName().startsWith( "is" ) ) {
76                      propertyName = name.substring( 2, name.length() );
77                      // probably should only add "is" methods if accompanied by "set" method
78                  } else { // "get"
79                      propertyName = name.substring( 3, name.length() );
80                  }
81  				if (
82  					( m[i].getReturnType().getName().equals( String.class.getName() ) )
83  				||  ( m[i].getReturnType().getName().equals( "boolean" ) )
84  				||  ( m[i].getReturnType().getName().equals( "int" ) )
85  				||  ( m[i].getReturnType().getName().equals( "float" ) )
86  				||  ( m[i].getReturnType().getName().equals( "char" ) )
87  				||  ( m[i].getReturnType().getName().equals( Color.class.getName() ) )
88  				||  ( m[i].getReturnType().getName().equals( Font.class.getName() ) )
89  				) {
90  					properties.addElement( propertyName ); // put property
91  					methods.put( name, m[i] ); // put method
92  				}
93                  else
94                  {
95                      // handle unknown types as invokable methods:
96                      properties.addElement( propertyName + METHOD_TAG );
97                      methods.put( propertyName + METHOD_TAG, m[i] );
98                  }
99  			}
100             else
101 			// set methods
102 			if ( ( m[i].getName().startsWith( "set" ) ) &&
103 				( m[i].getParameterTypes().length == 1 ) ) {
104 				name = m[i].getName();
105 				if (
106 					( m[i].getParameterTypes()[0].getName().equals( String.class.getName() ) )
107 				||  ( m[i].getParameterTypes()[0].getName().equals( "boolean" ) )
108 				||  ( m[i].getParameterTypes()[0].getName().equals( "int" ) )
109 				||  ( m[i].getParameterTypes()[0].getName().equals( "float" ) )
110 				||  ( m[i].getParameterTypes()[0].getName().equals( Color.class.getName() ) )
111 //				||  ( m[i].getParameterTypes()[0].getName().equals( Font.class.getName() ) )
112 				) {
113 //					System.out.println( "Accepted: " + name + ": " + m[i].getParameterTypes()[0].getName() );
114 					methods.put( name, m[i] ); // set method
115 				} else {
116 //					System.out.println( "Rejected: " + name + ": " + m[i].getParameterTypes()[0].getName() );
117 				}
118 			}
119             else
120             // zero-parameter methods to be invoked on click
121 			if ( m[i].getParameterTypes().length == 0 )
122             {
123                 properties.addElement( m[i].getName() + METHOD_TAG );
124                 methods.put( m[i].getName() + METHOD_TAG, m[i] );
125             }
126 
127 		}
128 
129 		sort(properties);
130 		fireTableDataChanged();
131 	}
132 
133 	public int getColumnCount() {
134 	  return columnNames.length;
135 	}
136 
137 	public int getRowCount() {
138 	  return properties.size();
139 	}
140 
141 	public String getColumnName(int col) {
142 	  return columnNames[col];
143 	}
144 
145 	public Object getValueAt(int row, int col) {
146 		if ( col == 0 )
147 			return properties.elementAt( row );
148 		else
149 		{
150 			Method m = null;
151 				m = (Method) methods.get( "get" + ( (String) properties.elementAt( row ) ) ) ;
152 			if ( m == null ) // try "is"
153 				m = (Method) methods.get( "is" + ( (String) properties.elementAt( row ) ) ) ;
154             if ( m == null ) { // try entire method name
155 				m = (Method) methods.get( (String) properties.elementAt( row ) ) ;
156                 if ( m != null ) return m;
157             }
158 			try {
159 				return m.invoke( inspectedObject, (Object[])null );
160 			} catch ( Exception exc ) {
161 				System.out.println( "InspectorFrame.tableModel.getValueAt: error occured while reflecting: " );
162 				System.out.println( exc );
163 			}
164 			return null;
165 		}
166 	}
167 
168 	public Class getColumnClass( int col ) {
169 //		System.out.println( "getColumnClass" );
170 /*		try {
171 			throw new Exception();
172 		} catch ( Exception exc ) {
173 			exc.printStackTrace( System.out );
174 		}
175 */		return new String().getClass();
176 	}
177 
178 	public Class getCellClass(int row, int col) {
179 //		System.out.println( "getCellClass" );
180 /*
181 
182 		Class c;
183 		Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ;
184 		if ( m == null )
185 			c = new Object().getClass();
186 		else {
187 			c = m.getParameterTypes()[0];
188 
189 			// special case for boolean
190 			if ( c.getName().equals( "boolean" ) )
191 				c = new Boolean(true).getClass();
192 
193 			// special case for int
194 			if ( c.getName().equals( "int" ) )
195 				c = new Integer(0).getClass();
196 		}
197 		System.out.println( row + ": " + c.getName() );
198 		return c;
199 */
200 
201 //		return new String().getClass();
202 
203 		Object o = getValueAt( row, col );
204 		if ( o == null ) o = "null";
205 		return o.getClass();
206 	}
207 
208 	/*
209 	* Don't need to implement this method unless your table's
210 	* editable.
211 	*/
212 	public boolean isCellEditable(int row, int col) {
213 	  //Note that the data/cell address is constant,
214 	  //no matter where the cell appears onscreen.
215 	  if (col < 1) {
216 		  return false;
217 	  } else {
218         // handle method invocation
219         if ( ((String)properties.elementAt(row)).endsWith(METHOD_TAG) ) return true;
220         // handle read-only properties
221 		Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ;
222 		if ( m == null )
223 			return false;
224 		else
225 			return true;
226 	  }
227 	}
228 
229 	/*
230 	* Don't need to implement this method unless your table's
231 	* data can change.
232 	*/
233 	public void setValueAt(Object value, int row, int col) {
234         // test for inspected object
235 		if ( inspectedObject == null ) return;
236         // handle method invocation - no need to update values
237         if ( ((String)properties.elementAt(row)).endsWith( METHOD_TAG ) )
238         {
239             fireTableDataChanged();
240             return;
241         };
242 
243         // handle writable properties
244 		Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ;
245 		String parameterType = m.getParameterTypes()[0].getName();
246 
247 		// ugly cast code
248 		if (
249 			( parameterType.equals( "int" ) )
250 		||	( parameterType.equals( "java.lang.Integer" ) )
251 		)
252 		{
253             try {
254                 value = new Integer((String)value);
255             } catch (NumberFormatException e) {
256                  System.out.println("PropertyEditorTableModel.setValueAt: User attempted to enter non-integer"
257                                + " value (" + value
258                                + ") into an integer-only column.");
259             }
260 		}
261 		Object[] parameters = { value };
262 		try {
263 			m.invoke( inspectedObject, parameters );
264 			if ( inspectedObject instanceof Component ) {
265 				Component c = (Component)inspectedObject;
266 				if ( c.getParent() != null )
267 					c.getParent().repaint();
268 			}
269 		} catch ( Exception exc ) {
270 			System.out.println( "PropertyEditorTableModel.setValueAt: error occured while reflecting: " );
271 			System.out.println( exc );
272 		}
273 
274 		fireTableDataChanged();
275 	}
276 
277 
278     protected void sort(Vector v){
279 	quickSort(v, 0, v.size()-1);
280     }
281 
282 
283     // Liberated from the BasicDirectoryModel which was...
284     // Liberated from the 1.1 SortDemo
285 
286     // This is a generic version of C.A.R Hoare's Quick Sort
287     // algorithm.  This will handle arrays that are already
288     // sorted, and arrays with duplicate keys.<BR>
289     //
290     // If you think of a one dimensional array as going from
291     // the lowest index on the left to the highest index on the right
292     // then the parameters to this function are lowest index or
293     // left and highest index or right.  The first time you call
294     // this function it will be with the parameters 0, a.length - 1.
295     //
296     // @param a       an integer array
297     // @param lo0     left boundary of array partition
298     // @param hi0     right boundary of array partition
299     private void quickSort(Vector v, int lo0, int hi0) {
300 	int lo = lo0;
301 	int hi = hi0;
302 	String mid;
303 
304 	if (hi0 > lo0) {
305 	    // Arbitrarily establishing partition element as the midpoint of
306 	    // the array.
307 	    mid = (String) v.elementAt((lo0 + hi0) / 2);
308 
309 	    // loop through the array until indices cross
310 	    while(lo <= hi) {
311 		// find the first element that is greater than or equal to
312 		// the partition element starting from the left Index.
313 		//
314 		// Nasty to have to cast here. Would it be quicker
315 		// to copy the vectors into arrays and sort the arrays?
316 		while((lo < hi0) && lt((String)v.elementAt(lo), mid)) {
317 		    ++lo;
318 		}
319 
320 		// find an element that is smaller than or equal to
321 		// the partition element starting from the right Index.
322 		while((hi > lo0) && lt(mid, (String)v.elementAt(hi))) {
323 		    --hi;
324 		}
325 
326 		// if the indexes have not crossed, swap
327 		if(lo <= hi) {
328 		    swap(v, lo, hi);
329 		    ++lo;
330 		    --hi;
331 		}
332 	    }
333 
334 
335 	    // If the right index has not reached the left side of array
336 	    // must now sort the left partition.
337 	    if(lo0 < hi) {
338 		quickSort(v, lo0, hi);
339 	    }
340 
341 	    // If the left index has not reached the right side of array
342 	    // must now sort the right partition.
343 	    if(lo < hi0) {
344 		quickSort(v, lo, hi0);
345 	    }
346 
347 	}
348     }
349 
350     private void swap(Vector a, int i, int j) {
351 	Object T = a.elementAt(i);
352 	a.setElementAt(a.elementAt(j), i);
353 	a.setElementAt(T, j);
354     }
355 
356     protected boolean lt(String a, String b) {
357 	return a.compareTo(b) < 0;
358     }
359 
360     // automated updates
361 
362     private boolean autoUpdating = false;
363     private int updateInterval = 2000; // one-second delay on average
364     protected Timer timer = null;
365 
366     public boolean isAutoUpdating()
367     {
368         return autoUpdating;
369     }
370 
371     public void setAutoUpdating( boolean shouldAutoUpdate )
372     {
373         if ( shouldAutoUpdate )
374         {
375             if ( timer == null )
376             {
377                 timer = new Timer( updateInterval, this );
378                 timer.setRepeats( true );
379                 timer.start();
380             }
381         }
382         else
383         {
384             if ( timer != null )
385             {
386                 timer.stop();
387                 timer = null;
388             }
389         }
390 
391         autoUpdating = shouldAutoUpdate;
392     }
393 
394     public int getUpdateInterval()
395     {
396         return updateInterval;
397     }
398 
399     public void setUpdateInterval( int anInterval )
400     {
401         if ( timer != null )
402         {
403             timer.setDelay( anInterval );
404         }
405 
406         updateInterval = anInterval;
407     }
408 
409     public void actionPerformed( ActionEvent evt )
410     {
411         if ( evt.getSource() == timer )
412         {
413             fireTableDataChanged();
414         }
415     }
416 
417 }
418