package devopsdistilled.operp.client.abstracts.libs; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 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 final long serialVersionUID = -1901856741007321381L; @SuppressWarnings("rawtypes") 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 final 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. */ 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. */ 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 = 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 = 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 = 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 = 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 final Class<?> returnType; private final Method getter; private final 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 */ @Override public int compareTo(ColumnInformation o) { return getName().compareTo(o.getName()); } } }