/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Michael Powers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.ui.swing.components; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.lang.reflect.Method; import java.util.Hashtable; import java.util.Vector; import javax.swing.Timer; import javax.swing.table.AbstractTableModel; /** * PropertyEditorTableModel introspects an object to facilitate editing it in a * PropertyTable. * * Because the model always reflects the current state of the inspected object, * it is useful to have a table update at automated intervals. By default, this * feature is turned off. If you turn it on, you'll want to remember to turn it * off when you're done with the table model. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 904 $ */ public class PropertyEditorTableModel extends AbstractTableModel implements ActionListener { protected Object inspectedObject = null; final String[] columnNames = { "Property", "Value" }; static final String METHOD_TAG = " "; Vector properties = new Vector(); Hashtable methods = new Hashtable(0); public void setObject(Object o) { inspectedObject = o; Class c = o.getClass(); Method[] m = c.getMethods(); properties = new Vector(); methods = new Hashtable(m.length, 1F); String name, propertyName; for (int i = 0; i < m.length; i++) { // if ( m[i].getName().startsWith( "get" ) ) // System.out.println( m[i].getName() + ": " + m[i].getReturnType().getName() ); // get methods if (((m[i].getName().startsWith("get")) || (m[i].getName().startsWith("is"))) && (m[i].getParameterTypes().length == 0)) { name = m[i].getName(); if (m[i].getName().startsWith("is")) { propertyName = name.substring(2, name.length()); // probably should only add "is" methods if accompanied by "set" method } else { // "get" propertyName = name.substring(3, name.length()); } if ((m[i].getReturnType().getName().equals(String.class.getName())) || (m[i].getReturnType().getName().equals("boolean")) || (m[i].getReturnType().getName().equals("int")) || (m[i].getReturnType().getName().equals("float")) || (m[i].getReturnType().getName().equals("char")) || (m[i].getReturnType().getName().equals(Color.class.getName())) || (m[i].getReturnType().getName().equals(Font.class.getName()))) { properties.addElement(propertyName); // put property methods.put(name, m[i]); // put method } else { // handle unknown types as invokable methods: properties.addElement(propertyName + METHOD_TAG); methods.put(propertyName + METHOD_TAG, m[i]); } } else // set methods if ((m[i].getName().startsWith("set")) && (m[i].getParameterTypes().length == 1)) { name = m[i].getName(); if ((m[i].getParameterTypes()[0].getName().equals(String.class.getName())) || (m[i].getParameterTypes()[0].getName().equals("boolean")) || (m[i].getParameterTypes()[0].getName().equals("int")) || (m[i].getParameterTypes()[0].getName().equals("float")) || (m[i].getParameterTypes()[0].getName().equals(Color.class.getName())) // || ( m[i].getParameterTypes()[0].getName().equals( Font.class.getName() ) ) ) { // System.out.println( "Accepted: " + name + ": " + m[i].getParameterTypes()[0].getName() ); methods.put(name, m[i]); // set method } else { // System.out.println( "Rejected: " + name + ": " + m[i].getParameterTypes()[0].getName() ); } } else // zero-parameter methods to be invoked on click if (m[i].getParameterTypes().length == 0) { properties.addElement(m[i].getName() + METHOD_TAG); methods.put(m[i].getName() + METHOD_TAG, m[i]); } } sort(properties); fireTableDataChanged(); } public int getColumnCount() { return columnNames.length; } public int getRowCount() { return properties.size(); } public String getColumnName(int col) { return columnNames[col]; } public Object getValueAt(int row, int col) { if (col == 0) return properties.elementAt(row); else { Method m = null; m = (Method) methods.get("get" + ((String) properties.elementAt(row))); if (m == null) // try "is" m = (Method) methods.get("is" + ((String) properties.elementAt(row))); if (m == null) { // try entire method name m = (Method) methods.get((String) properties.elementAt(row)); if (m != null) return m; } try { return m.invoke(inspectedObject, (Object[]) null); } catch (Exception exc) { System.out.println("InspectorFrame.tableModel.getValueAt: error occured while reflecting: "); System.out.println(exc); } return null; } } public Class getColumnClass(int col) { // System.out.println( "getColumnClass" ); /* * try { throw new Exception(); } catch ( Exception exc ) { exc.printStackTrace( * System.out ); } */ return new String().getClass(); } public Class getCellClass(int row, int col) { // System.out.println( "getCellClass" ); /* * * Class c; Method m = (Method) methods.get( "set" + ( (String) * properties.elementAt( row ) ) ) ; if ( m == null ) c = new * Object().getClass(); else { c = m.getParameterTypes()[0]; * * // special case for boolean if ( c.getName().equals( "boolean" ) ) c = new * Boolean(true).getClass(); * * // special case for int if ( c.getName().equals( "int" ) ) c = new * Integer(0).getClass(); } System.out.println( row + ": " + c.getName() ); * return c; */ // return new String().getClass(); Object o = getValueAt(row, col); if (o == null) o = "null"; return o.getClass(); } /* * Don't need to implement this method unless your table's editable. */ public boolean isCellEditable(int row, int col) { // Note that the data/cell address is constant, // no matter where the cell appears onscreen. if (col < 1) { return false; } else { // handle method invocation if (((String) properties.elementAt(row)).endsWith(METHOD_TAG)) return true; // handle read-only properties Method m = (Method) methods.get("set" + ((String) properties.elementAt(row))); if (m == null) return false; else return true; } } /* * Don't need to implement this method unless your table's data can change. */ public void setValueAt(Object value, int row, int col) { // test for inspected object if (inspectedObject == null) return; // handle method invocation - no need to update values if (((String) properties.elementAt(row)).endsWith(METHOD_TAG)) { fireTableDataChanged(); return; } ; // handle writable properties Method m = (Method) methods.get("set" + ((String) properties.elementAt(row))); String parameterType = m.getParameterTypes()[0].getName(); // ugly cast code if ((parameterType.equals("int")) || (parameterType.equals("java.lang.Integer"))) { try { value = new Integer((String) value); } catch (NumberFormatException e) { System.out.println("PropertyEditorTableModel.setValueAt: User attempted to enter non-integer" + " value (" + value + ") into an integer-only column."); } } Object[] parameters = { value }; try { m.invoke(inspectedObject, parameters); if (inspectedObject instanceof Component) { Component c = (Component) inspectedObject; if (c.getParent() != null) c.getParent().repaint(); } } catch (Exception exc) { System.out.println("PropertyEditorTableModel.setValueAt: error occured while reflecting: "); System.out.println(exc); } fireTableDataChanged(); } protected void sort(Vector v) { quickSort(v, 0, v.size() - 1); } // Liberated from the BasicDirectoryModel which was... // Liberated from the 1.1 SortDemo // This is a generic version of C.A.R Hoare's Quick Sort // algorithm. This will handle arrays that are already // sorted, and arrays with duplicate keys.
// // If you think of a one dimensional array as going from // the lowest index on the left to the highest index on the right // then the parameters to this function are lowest index or // left and highest index or right. The first time you call // this function it will be with the parameters 0, a.length - 1. // // @param a an integer array // @param lo0 left boundary of array partition // @param hi0 right boundary of array partition private void quickSort(Vector v, int lo0, int hi0) { int lo = lo0; int hi = hi0; String mid; if (hi0 > lo0) { // Arbitrarily establishing partition element as the midpoint of // the array. mid = (String) v.elementAt((lo0 + hi0) / 2); // loop through the array until indices cross while (lo <= hi) { // find the first element that is greater than or equal to // the partition element starting from the left Index. // // Nasty to have to cast here. Would it be quicker // to copy the vectors into arrays and sort the arrays? while ((lo < hi0) && lt((String) v.elementAt(lo), mid)) { ++lo; } // find an element that is smaller than or equal to // the partition element starting from the right Index. while ((hi > lo0) && lt(mid, (String) v.elementAt(hi))) { --hi; } // if the indexes have not crossed, swap if (lo <= hi) { swap(v, lo, hi); ++lo; --hi; } } // If the right index has not reached the left side of array // must now sort the left partition. if (lo0 < hi) { quickSort(v, lo0, hi); } // If the left index has not reached the right side of array // must now sort the right partition. if (lo < hi0) { quickSort(v, lo, hi0); } } } private void swap(Vector a, int i, int j) { Object T = a.elementAt(i); a.setElementAt(a.elementAt(j), i); a.setElementAt(T, j); } protected boolean lt(String a, String b) { return a.compareTo(b) < 0; } // automated updates private boolean autoUpdating = false; private int updateInterval = 2000; // one-second delay on average protected Timer timer = null; public boolean isAutoUpdating() { return autoUpdating; } public void setAutoUpdating(boolean shouldAutoUpdate) { if (shouldAutoUpdate) { if (timer == null) { timer = new Timer(updateInterval, this); timer.setRepeats(true); timer.start(); } } else { if (timer != null) { timer.stop(); timer = null; } } autoUpdating = shouldAutoUpdate; } public int getUpdateInterval() { return updateInterval; } public void setUpdateInterval(int anInterval) { if (timer != null) { timer.setDelay(anInterval); } updateInterval = anInterval; } public void actionPerformed(ActionEvent evt) { if (evt.getSource() == timer) { fireTableDataChanged(); } } }