Clover coverage report - gsbase - 2.0.1
Coverage timestamp: Sat Jan 1 2005 12:30:02 EST
file stats: LOC: 495   Methods: 26
NCLOC: 257   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
ReflectedTableModel.java 27.5% 51.4% 61.5% 47.4%
coverage coverage
 1    /*
 2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. All rights reserved.
 3    *
 4    * Redistribution and use in source and binary forms, with or without
 5    * modification, are permitted provided that the following conditions are met:
 6    *
 7    * 1. Redistributions of source code must retain the above copyright notice,
 8    * this list of conditions and the following disclaimer.
 9    * 2. Redistributions in binary form must reproduce the above copyright notice,
 10    * this list of conditions and the following disclaimer in the documentation
 11    * and/or other materials provided with the distribution.
 12    * 3. The end-user documentation included with the redistribution, if any, must
 13    * include the following acknowledgment:
 14    *
 15    * "This product includes software developed by Gargoyle Software Inc.
 16    * (http://www.GargoyleSoftware.com/)."
 17    *
 18    * Alternately, this acknowledgment may appear in the software itself, if
 19    * and wherever such third-party acknowledgments normally appear.
 20    * 4. The name "Gargoyle Software" must not be used to endorse or promote
 21    * products derived from this software without prior written permission.
 22    * For written permission, please contact info@GargoyleSoftware.com.
 23    * 5. Products derived from this software may not be called "GSBase", nor may
 24    * "GSBase" appear in their name, without prior written permission of
 25    * Gargoyle Software Inc.
 26    *
 27    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 28    * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 29    * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
 30    * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 31    * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 32    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 33    * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 34    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 35    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 36    * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 37    */
 38    package com.gargoylesoftware.base.gui;
 39   
 40    import com.gargoylesoftware.base.collections.NotificationList;
 41    import com.gargoylesoftware.base.collections.NotificationListEvent;
 42    import com.gargoylesoftware.base.collections.NotificationListListener;
 43    import com.gargoylesoftware.base.trace.Trace;
 44    import com.gargoylesoftware.base.trace.TraceChannel;
 45    import com.gargoylesoftware.base.util.DetailedIllegalArgumentException;
 46    import com.gargoylesoftware.base.util.DetailedNullPointerException;
 47    import java.beans.BeanInfo;
 48    import java.beans.IntrospectionException;
 49    import java.beans.Introspector;
 50    import java.beans.PropertyChangeEvent;
 51    import java.beans.PropertyChangeListener;
 52    import java.beans.PropertyDescriptor;
 53    import java.lang.reflect.InvocationTargetException;
 54    import java.lang.reflect.Method;
 55    import java.util.ArrayList;
 56    import java.util.Collections;
 57    import java.util.HashMap;
 58    import java.util.List;
 59    import java.util.Map;
 60    import javax.swing.table.AbstractTableModel;
 61    import java.util.Iterator;
 62   
 63    /**
 64    * A table model that uses reflection to retrieve values out of the row objects.
 65    * <p>
 66    * The sample below will create a JTable with one row of data and one column per property
 67    * in the Date class (Date has 10 properties in JDK1.3).
 68    * <pre>
 69    * final JTable table = new JTable();
 70    * final ReflectedTableModel model = new ReflectedTableModel(Date.class);
 71    * model.getRows().add( new Date() );
 72    * table.setModel(model);
 73    * </pre>
 74    *
 75    * This sample will only provide columns for month and year.
 76    *
 77    * <pre>
 78    * final JTable table = new JTable();
 79    * final ReflectedTableModel model = new ReflectedTableModel();
 80    * model.getRows().add( new Date() );
 81    * model.getColumns().add( new ReflectedTableModel.ColumnInfo("month") );
 82    * model.getColumns().add( new ReflectedTableModel.ColumnInfo("year") );
 83    * table.setModel(model);
 84    * </pre>
 85    * <b>Tip: </b>To enable debugging information call {@link #setTraceChannel(TraceChannel)}
 86    * with a non-null TraceChannel.
 87    * <pre>
 88    * model.setTraceChannel(Trace.out)
 89    * </pre>
 90    *
 91    * @version $Revision: 1.8 $
 92    * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
 93    */
 94    public class ReflectedTableModel extends AbstractTableModel {
 95    //TODO: Register ourselves as PropertyChangeListeners on the row objects so
 96    // that we know when to fire a model changed event.
 97    //TODO: Listen for changes to the row object list so we can fire model
 98    // changed.
 99    //TODO: Listen for changes to the column objects so we can update the model.
 100   
 101    private static final long serialVersionUID = -7537209244601820035L;
 102   
 103    /**
 104    * This class contains information about one specific column in the table.
 105    */
 106    public static class ColumnInfo {
 107    private final String columnName_;
 108    private final String propertyName_;
 109   
 110    /**
 111    * Create a new ColumnInfo with the specified column name and property name.
 112    * @param columnName The name used by the table
 113    * @param propertyName The name of the property that we will get the data from.
 114    */
 115  6 public ColumnInfo( final String columnName, final String propertyName ) {
 116  6 if( columnName == null ) {
 117  0 throw new DetailedNullPointerException("columnName");
 118    }
 119  6 if( propertyName == null ) {
 120  0 throw new DetailedNullPointerException("propertyName");
 121    }
 122   
 123  6 columnName_ = columnName;
 124  6 propertyName_ = propertyName;
 125    }
 126   
 127    /**
 128    * Create a new ColumnInfo where the column name and property name are the same.
 129    * @param name The name.
 130    */
 131  6 public ColumnInfo( final String name ) {
 132  6 this( name, name );
 133    }
 134   
 135    /**
 136    * Return the column name.
 137    * @return The column name.
 138    */
 139  3 public String getColumnName() {
 140  3 return columnName_;
 141    }
 142   
 143    /**
 144    * Return the property name
 145    * @return The property name.
 146    */
 147  11 public String getPropertyName() {
 148  11 return propertyName_;
 149    }
 150    }
 151   
 152    /**
 153    * If the row list changes then fire the appropriate table event.
 154    */
 155    private NotificationListListener rowListener_
 156    = new NotificationListListener() {
 157   
 158  4 public void listElementsAdded( final NotificationListEvent event ) {
 159  4 addRowElements( event.getNewValues() );
 160  4 fireTableRowsInserted( event.getStartIndex(), event.getEndIndex() );
 161    }
 162  0 public void listElementsRemoved( final NotificationListEvent event ) {
 163  0 removeRowElements( event.getOldValues() );
 164  0 fireTableRowsDeleted( event.getStartIndex(), event.getEndIndex() );
 165    }
 166  0 public void listElementsChanged( final NotificationListEvent event ) {
 167  0 removeRowElements( event.getOldValues() );
 168  0 addRowElements( event.getNewValues() );
 169  0 fireTableRowsUpdated( event.getStartIndex(), event.getEndIndex() );
 170    }
 171    };
 172   
 173    /**
 174    * If any change occurs to the columns then fire a structure changed.
 175    */
 176    private NotificationListListener columnListener_
 177    = new NotificationListListener() {
 178   
 179  6 public void listElementsAdded( final NotificationListEvent event ) {
 180  6 fireTableStructureChanged();
 181    }
 182  0 public void listElementsRemoved( final NotificationListEvent event ) {
 183  0 fireTableStructureChanged();
 184    }
 185  0 public void listElementsChanged( final NotificationListEvent event ) {
 186  0 fireTableStructureChanged();
 187    }
 188    };
 189   
 190    /**
 191    *
 192    */
 193    private PropertyChangeListener propertyChangeListener_
 194    = new PropertyChangeListener() {
 195   
 196  0 public void propertyChange( final PropertyChangeEvent event ) {
 197  0 final int row = rows_.indexOf( event.getSource() );
 198  0 final int columnCount = columns_.size();
 199  0 final String propertyName = event.getPropertyName();
 200  0 ColumnInfo columnInfo;
 201   
 202  0 int i;
 203  0 for( i=0; i<columnCount; i++ ) {
 204  0 columnInfo = (ColumnInfo)columns_.get(i);
 205  0 if( columnInfo.getPropertyName().equals(propertyName) ) {
 206  0 fireTableCellUpdated( row, i );
 207  0 if( traceChannel_ != null ) {
 208  0 Trace.println(traceChannel_,
 209    "ReflectedTableModel property changed:"
 210    + " property=" + propertyName
 211    + " row=" +row
 212    + " column=" + i);
 213    }
 214    }
 215    }
 216    }
 217    };
 218   
 219    private class RowElementControlData {
 220    /** How many times has this element been placed in the list. Used to ensure
 221    * that we only add/remove a PropertyChangeListener once per object.
 222    */
 223    public int instanceCounter_ = 0;
 224   
 225    /** This will only be true if the specified object has both add and remove
 226    * methods for PropertyChangeListeners.
 227    */
 228    public boolean supportsPropertyChangeEvents_ = false;
 229    }
 230   
 231    // Keys are the row elements, values are instances of RowElementControlData
 232    private final Map rowElementControlDatas_ = new HashMap();
 233   
 234    private final List rows_;
 235    private final List columns_;
 236   
 237    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
 238   
 239    // If non-null then tracing will be enabled.
 240    private TraceChannel traceChannel_ = null;
 241   
 242    /**
 243    * Create an empty model with no columns and no rows.
 244    */
 245  2 public ReflectedTableModel() {
 246   
 247  2 final NotificationList columnList = new NotificationList( new ArrayList() );
 248  2 columnList.addNotificationListListener(columnListener_);
 249  2 columns_ = Collections.synchronizedList( columnList );
 250   
 251  2 final NotificationList rowList = new NotificationList( new ArrayList() );
 252  2 rowList.addNotificationListListener(rowListener_);
 253  2 rows_ = Collections.synchronizedList( rowList );
 254    }
 255   
 256    /**
 257    * Create an empty model with no rows but the columns preset to match the
 258    * properties in the given class.
 259    *
 260    * @param clazz The class to get properties from.
 261    * @throws IntrospectionException If the Introspector is unable to get
 262    * the properties for this class.
 263    */
 264  2 public ReflectedTableModel( final Class clazz ) throws IntrospectionException {
 265  2 this();
 266  2 final BeanInfo beanInfo = Introspector.getBeanInfo( clazz );
 267  2 final PropertyDescriptor propertyDescriptors[]
 268    = beanInfo.getPropertyDescriptors();
 269   
 270  2 String propertyName;
 271   
 272  2 int i;
 273  2 for( i=0; i<propertyDescriptors.length; i++ ) {
 274  6 propertyName = propertyDescriptors[i].getName();
 275   
 276  6 if( traceChannel_ != null ) {
 277  0 Trace.println( traceChannel_,
 278    "ReflectedTableModel(Class) adding column: ["
 279    + propertyName +"]" );
 280    }
 281  6 columns_.add( new ColumnInfo( propertyName ) );
 282    }
 283    }
 284   
 285    /**
 286    * Return a list containing the objects that are used to create each row. This
 287    * list is backed by the original store such that changes to this list will be
 288    * reflected in the table model.
 289    * @return The rows.
 290    */
 291  1 public List getRows() {
 292  1 return rows_;
 293    }
 294   
 295    /**
 296    * Return a list containing the ColumnInfo objects that are used to define each
 297    * column. This list is backed by the original store such that changes to this
 298    * list will be reflected in the table model.
 299    * @return The columns.
 300    */
 301  1 public List getColumns() {
 302  1 return columns_;
 303    }
 304   
 305    /**
 306    * Return the number of columns.
 307    * @return the number of columns.
 308    */
 309  1 public int getColumnCount() {
 310  1 return columns_.size();
 311    }
 312   
 313    /**
 314    * Return the number of rows.
 315    * @return The number of rows.
 316    */
 317  1 public int getRowCount() {
 318  1 return rows_.size();
 319    }
 320   
 321    /**
 322    * Return the specified object.
 323    * @param rowIndex The row index
 324    * @param columnIndex The columnIndex
 325    * @return The object at the specified row and column.
 326    */
 327  8 public Object getValueAt( final int rowIndex, final int columnIndex ) {
 328  8 final Object rowObject = rows_.get(rowIndex);
 329  8 final ColumnInfo columnInfo = (ColumnInfo)columns_.get(columnIndex);
 330  8 final String propertyName = columnInfo.getPropertyName();
 331   
 332  8 try {
 333  8 final BeanInfo beanInfo = Introspector.getBeanInfo( rowObject.getClass() );
 334  8 final PropertyDescriptor propertyDescriptors[]
 335    = beanInfo.getPropertyDescriptors();
 336   
 337  8 int i;
 338  16 for( i=0; i<propertyDescriptors.length; i++ ) {
 339  16 if( propertyDescriptors[i].getName().equals( propertyName ) ) {
 340  8 final Method readMethod = propertyDescriptors[i].getReadMethod();
 341  8 return readMethod.invoke( rowObject, EMPTY_OBJECT_ARRAY );
 342    }
 343    }
 344    }
 345    catch( final Exception e ) {
 346  0 if( traceChannel_ != null ) {
 347  0 Trace.printStackTrace( traceChannel_, e );
 348    }
 349    }
 350   
 351  0 return null;
 352    }
 353   
 354    /**
 355    * Return the name of the column at the specified index.
 356    * @param index The index of the column.
 357    * @return The name of the column at the specified index.
 358    */
 359  3 public String getColumnName( final int index ) {
 360  3 return((ColumnInfo)columns_.get(index)).getColumnName();
 361    }
 362   
 363    /**
 364    * Set the channel to be used for tracing.
 365    *
 366    * @param channel The channel to be used for tracing or null if tracing is
 367    * to be disabled.
 368    */
 369  0 public void setTraceChannel( final TraceChannel channel ) {
 370  0 traceChannel_ = channel;
 371    }
 372   
 373    /**
 374    * Return the channel currently being used for tracing or null if tracing
 375    * is disabled.
 376    * @return The trace channel or null if a channel hasn't been set.
 377    */
 378  0 public TraceChannel getTraceChannel() {
 379  0 return traceChannel_;
 380    }
 381   
 382    /**
 383    * Add a row element
 384    * @param object the object that will be used to populate this row
 385    */
 386  4 private synchronized void addRowElement( final Object object ) {
 387   
 388  4 RowElementControlData data =
 389    (RowElementControlData)rowElementControlDatas_.get(object);
 390   
 391  4 if( data == null ) {
 392  4 data = new RowElementControlData();
 393   
 394  4 final Class clazz = object.getClass();
 395  4 final Class parms[] = { PropertyChangeListener.class};
 396  4 Method method;
 397  4 try {
 398    // We try the "remove" first just to ensure that there is one.
 399  4 method = clazz.getMethod("removePropertyChangeListener", parms);
 400  4 method = clazz.getMethod("addPropertyChangeListener", parms);
 401  4 method.invoke(object, new Object[]{propertyChangeListener_} );
 402    }
 403    catch( NoSuchMethodException e ) {
 404  0 data.supportsPropertyChangeEvents_ = false;
 405    }
 406    catch( IllegalAccessException e ) {
 407  0 if( traceChannel_ != null ) {
 408  0 Trace.printStackTrace(traceChannel_, e);
 409    }
 410    }
 411    catch( InvocationTargetException e ) {
 412  0 if( traceChannel_ != null ) {
 413  0 Trace.printStackTrace(traceChannel_, e.getTargetException());
 414    }
 415    }
 416   
 417  4 rowElementControlDatas_.put(object, data);
 418    }
 419   
 420  4 data.instanceCounter_++;
 421    }
 422   
 423    /**
 424    * @param list The list of objects that will be used to create the rows.
 425    */
 426  4 private void addRowElements( final List list ) {
 427  4 final Iterator iterator = list.iterator();
 428  4 while( iterator.hasNext() ) {
 429  4 addRowElement( iterator.next() );
 430    }
 431    }
 432   
 433    /**
 434    * @param list The list of object that will be removed from the model
 435    */
 436  0 private void removeRowElements( final List list ) {
 437  0 final Iterator iterator = list.iterator();
 438  0 while( iterator.hasNext() ) {
 439  0 removeRowElement( iterator.next() );
 440    }
 441    }
 442   
 443    /**
 444    * Remove one row
 445    * @param object The object that was used to create this row.
 446    */
 447  0 private synchronized void removeRowElement( final Object object ) {
 448   
 449  0 RowElementControlData data =
 450    (RowElementControlData)rowElementControlDatas_.get(object);
 451   
 452  0 if( data == null ) {
 453  0 throw new DetailedIllegalArgumentException("object", object, "Not in row list");
 454    }
 455   
 456  0 final Class clazz = object.getClass();
 457  0 final Class parms[] = { PropertyChangeListener.class};
 458  0 Method method;
 459  0 try {
 460  0 method = clazz.getMethod("removePropertyChangeListener", parms);
 461  0 method.invoke(object, new Object[]{propertyChangeListener_} );
 462    }
 463    catch( final NoSuchMethodException e ) {
 464  0 throw new IllegalStateException(
 465    "object doesn't have a removePropertyChange(): "+object);
 466    }
 467    catch( IllegalAccessException e ) {
 468  0 if( traceChannel_ != null ) {
 469  0 Trace.printStackTrace(traceChannel_, e);
 470    }
 471    }
 472    catch( InvocationTargetException e ) {
 473  0 if( traceChannel_ != null ) {
 474  0 Trace.printStackTrace(traceChannel_, e.getTargetException());
 475    }
 476    }
 477   
 478  0 data.instanceCounter_--;
 479  0 if( data.instanceCounter_ == 0 ) {
 480  0 rowElementControlDatas_.remove(object);
 481    }
 482    }
 483   
 484   
 485    /**
 486    * Throw an exception if the specified object is null
 487    * @param fieldName The name of the paremeter we are checking
 488    * @param object The value of the parameter we are checking
 489    */
 490  0 protected final void assertNotNull( final String fieldName, final Object object ) {
 491  0 if( object == null ) {
 492  0 throw new DetailedNullPointerException(fieldName);
 493    }
 494    }
 495    }