Source code: org/progeeks/meta/DefaultMetaTable.java
1 /*
2 * $Id: DefaultMetaTable.java,v 1.6 2003/09/12 08:06:21 pspeed Exp $
3 *
4 * Copyright (c) 2001-2003, Paul Speed
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1) Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * 2) Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3) Neither the names "Progeeks", "Meta-JB", nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 package org.progeeks.meta;
34
35 import java.beans.*;
36 import java.util.*;
37
38 import org.progeeks.util.*;
39 import org.progeeks.util.beans.*;
40
41 /**
42 * A default implementation of the MetaTable interface. This
43 * meta-table implementation contains an internal list of objects.
44 * If these objects are meta-objects then they are access directly.
45 * Otherwise, they are wrapped in meta-objects using the meta-kit
46 * provided.
47 * Optionally a list of property names can be provided or the table
48 * will pull the property names from the specified meta-class.
49 *
50 * @version $Revision: 1.6 $
51 * @author Paul Speed
52 */
53 public class DefaultMetaTable extends BeanChangeSupport
54 implements MetaTable
55 {
56 /**
57 * Keeps cached version of the meta-objects as they
58 * are created. If the original object list contains
59 * meta-objects, then this field will be null.
60 */
61 private List wrapperObjects;
62
63 /**
64 * The original object list. This will be the same size
65 * as the metaList, or null if the objects are all meta-objects.
66 */
67 private ObservableList objects;
68
69 /**
70 * An array of the supported properties. Easy to map column to
71 * property name this way.
72 */
73 private String[] properties;
74
75 /**
76 * The meta-class that this table is constrained by.
77 */
78 private MetaClass metaClass;
79
80 /**
81 * A reference to the meta-kit we'll use to create new meta-objects
82 * when we need to.
83 */
84 private MetaKit kit;
85
86 /**
87 * The listener that is added to all meta-objects created internally.
88 * This passes on the change events to table listeners.
89 */
90 private MetaObjectListener objectListener = new MetaObjectListener();
91
92 /**
93 * Creates a default meta-table that contains the specified list
94 * of objects.
95 */
96 public DefaultMetaTable( List objects, List properties, MetaClass metaClass, MetaKit kit )
97 {
98 this.metaClass = metaClass;
99 this.kit = kit;
100
101 Object obj = objects.size() > 0 ? objects.get(0) : null;
102
103 setObjects( objects, !(obj instanceof MetaObject) );
104 setProperties( properties );
105 }
106
107 /**
108 * Creates a default meta-table that contains the specified list
109 * of objects.
110 */
111 public DefaultMetaTable( List objects, List properties, MetaClass metaClass, MetaKit kit,
112 boolean autoWrap )
113 {
114 this.metaClass = metaClass;
115 this.kit = kit;
116
117 Object obj = objects.size() > 0 ? objects.get(0) : null;
118
119 setObjects( objects, autoWrap );
120 setProperties( properties );
121 }
122
123 protected void setObjects( List objects, boolean autoWrap )
124 {
125 // You know. Potentially, we should always keep a separate
126 // list containing the wrapper objects. This is so that we
127 // can better manage listener add/remove when changes to the
128 // original list happen. Right now, if the caller passes in
129 // an ObservableList containing MetaObjects then we create a
130 // resource leak every time an object is removed from the list.
131
132 this.wrapperObjects = null;
133 this.objects = null;
134
135 if( objects instanceof ObservableList )
136 {
137 this.objects = (ObservableList)objects;
138 }
139 else
140 {
141 // We make our own copy since we can't detect changes
142 // otherwise.
143 this.objects = new ObservableList( new ArrayList( objects ) );
144 }
145
146 if( !autoWrap )
147 {
148 // Don't need to keep a separate set of wrappers.
149
150 // Add our listener to all of the objects
151 for( Iterator i = objects.iterator(); i.hasNext(); )
152 {
153 MetaObject mObj = (MetaObject)i.next();
154 if( mObj != null )
155 mObj.addPropertyChangeListener( objectListener );
156 }
157 }
158 else
159 {
160 // Otherwise, make sure the wrapper list is the same
161 // size as the objects list
162 wrapperObjects = new ArrayList( objects.size() );
163 for( int i = 0; i < objects.size(); i++ )
164 wrapperObjects.add( null );
165 }
166
167 // Register ourselves a listener to the list... if it's
168 // external, this is vital. If it's internal, it just
169 // makes things simpler.
170 this.objects.addPropertyChangeListener( objectListener );
171 }
172
173 protected void setProperties( List names )
174 {
175 properties = new String[ names.size() ];
176 properties = (String[])names.toArray( properties );
177 }
178
179 /**
180 * Adds the specified object to the list. If an object is a
181 * meta-object then it will be directly added to the meta-object
182 * list. Otherwise, a meta-object will be created.
183 */
184 public void addObject( Object obj )
185 {
186 MetaObject mObj = null;
187 if( obj instanceof MetaObject )
188 {
189 mObj = (MetaObject)mObj;
190 if( wrapperObjects != null )
191 {
192 // Handle the case even though callers really shouldn't
193 // be doing this.
194 wrapperObjects.add( obj );
195 objects.add( kit.getInternalObject( (MetaObject)obj ) );
196 }
197 else
198 {
199 objects.add( mObj );
200 }
201 }
202 else if( wrapperObjects == null )
203 {
204 throw new RuntimeException( "Non-meta-object cannot be added to homogenous meta-object list." );
205 }
206 else
207 {
208 // Go ahead and add the meta-object now instead of waiting
209 // for a cache miss to keep the event propagation simpler
210 mObj = kit.wrapObject( obj, metaClass );
211
212 objects.add( obj );
213 wrapperObjects.add( mObj );
214 }
215
216 // Add our listener
217 mObj.addPropertyChangeListener( objectListener );
218 }
219
220 /**
221 * Returns the specified row as a meta-object.
222 */
223 public MetaObject getMetaObject( int row )
224 {
225 if( wrapperObjects == null )
226 return( (MetaObject)objects.get(row) );
227
228 MetaObject mObj = (MetaObject)wrapperObjects.get( row );
229 if( mObj != null )
230 return( mObj );
231
232 Object obj = objects.get( row );
233 mObj = kit.wrapObject( obj, metaClass );
234 wrapperObjects.set( row, mObj );
235
236 // Add our listener
237 mObj.addPropertyChangeListener( objectListener );
238
239 return( mObj );
240 }
241
242 /**
243 * Returns the row index for the specified meta-object. This is
244 * an optional operation and may throw an UnsupportedOpreationException.
245 */
246 public int getRowIndex( MetaObject obj )
247 {
248 // Note: there is no way they would have a valid meta-object
249 // without it already being in one of the lists.
250 if( wrapperObjects != null )
251 return( wrapperObjects.indexOf( obj ) );
252 return( objects.indexOf( obj ) );
253 }
254
255 /**
256 * Returns the property info for a given column.
257 */
258 public PropertyInfo getPropertyInfo( int column )
259 {
260 return( metaClass.getPropertyInfo( properties[column] ) );
261 }
262
263 /**
264 * Returns the column index of the specified property.
265 * @return The index of the property or -1 if the name does
266 * not represent a supported property.
267 */
268 public int getColumnIndex( String propertyName )
269 {
270 for( int i = 0; i < properties.length; i++ )
271 {
272 if( propertyName.equals( properties[i] ) )
273 return( i );
274 }
275 return( -1 );
276 }
277
278 /**
279 * Returns the number of rows in the table.
280 */
281 public int getRowCount()
282 {
283 return( objects.size() );
284 }
285
286 /**
287 * Returns the number of columns in the table.
288 */
289 public int getColumnCount()
290 {
291 return( properties.length );
292 }
293
294 /**
295 * Sets the value of the specified property.
296 */
297 public void setProperty( int row, int column, Object value )
298 {
299 getMetaObject( row ).setProperty( properties[column], value );
300 }
301
302 /**
303 * Returns the value of the specified property.
304 */
305 public Object getProperty( int row, int column )
306 {
307 return( getMetaObject( row ).getProperty( properties[column] ) );
308 }
309
310 /**
311 * Returns a mutator for the specified property.
312 */
313 public PropertyMutator getPropertyMutator( int row, int column )
314 {
315 return( getMetaObject( row ).getPropertyMutator( properties[column] ) );
316 }
317
318 /**
319 * Returns the meta-class associated with this object.
320 */
321 public MetaClass getMetaClass()
322 {
323 return( metaClass );
324 }
325
326 /**
327 * Returns the meta-kit for this meta-object's implementation
328 * layer.
329 */
330 public MetaKit getMetaKit()
331 {
332 return( kit );
333 }
334
335 /**
336 * Adds a property change listener that will be notified
337 * whenever the specified property changes in any row.
338 */
339 public void addPropertyChangeListener( int column,
340 PropertyChangeListener l )
341 {
342 addPropertyChangeListener( properties[column], l );
343 }
344
345 /**
346 * Returns true if the specified property name has listeners
347 * registered.
348 */
349 public boolean hasListeners( int column )
350 {
351 return( hasListeners( properties[column] ) );
352 }
353
354 /**
355 * Removes the property change listener from a specific
356 * property.
357 */
358 public void removePropertyChangeListener( int column,
359 PropertyChangeListener l )
360 {
361 removePropertyChangeListener( properties[column], l );
362 }
363
364 /**
365 * This is the listener that is added to all meta-objects
366 * so that any changes are forwarded along to table listeners.
367 */
368 private class MetaObjectListener implements PropertyChangeListener
369 {
370 public void propertyChange( PropertyChangeEvent event )
371 {
372 // Check to see if the event is from our internal list
373 if( event.getSource() == objects )
374 {
375 ListPropertyChangeEvent le = new ListPropertyChangeEvent( DefaultMetaTable.this,
376 (ListPropertyChangeEvent)event );
377
378 // Make sure the appropriate update is made
379 // to our own structures
380 if( wrapperObjects != null )
381 {
382 int count = le.getLastIndex() - le.getFirstIndex() + 1;
383
384 switch( le.getType() )
385 {
386 case ListPropertyChangeEvent.INSERT:
387 for( int i = 0; i < count; i++ )
388 wrapperObjects.add( le.getFirstIndex(), null );
389 break;
390 case ListPropertyChangeEvent.UPDATE:
391 for( int i = le.getFirstIndex(); i <= le.getLastIndex(); i++ )
392 {
393 MetaObject mObj = (MetaObject)wrapperObjects.set( i, null );
394 if( mObj != null )
395 mObj.removePropertyChangeListener( objectListener );
396 }
397 break;
398 case ListPropertyChangeEvent.DELETE:
399 for( int i = 0; i < count; i++ )
400 {
401 MetaObject mObj = (MetaObject)wrapperObjects.remove( le.getFirstIndex() );
402 if( mObj != null )
403 mObj.removePropertyChangeListener( objectListener );
404 }
405 break;
406 }
407 }
408
409 // Need to resource the event
410 firePropertyChange( le );
411 return;
412 }
413
414 // Only send the event along if it's in our
415 // property list.
416 String name = event.getPropertyName();
417 if( getColumnIndex(name) < 0 )
418 return;
419
420 firePropertyChange( event );
421 }
422 }
423 }
424