Source code: org/progeeks/meta/AbstractMetaObject.java
1 /*
2 * $Id: AbstractMetaObject.java,v 1.5 2003/09/20 10:24:29 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.meta.*;
39 import org.progeeks.meta.util.*;
40 import org.progeeks.util.beans.*;
41
42 /**
43 * An abstract meta-object implementation making it easier to create
44 * kit-specific meta-object adapters. Hooks are provided to allow
45 * subclasses to override specific functionality that may be important
46 * to a meta-kit implementation.
47 *
48 * Subclasses must provide, at the minimum, getProperty() and setProperty()
49 * methods.
50 *
51 * @version $Revision: 1.5 $
52 * @author Paul Speed
53 */
54 public abstract class AbstractMetaObject extends BeanChangeSupport
55 implements MetaObject
56 {
57 private MetaKit metaKit;
58 private MetaClass metaClass;
59 private Map wrappedValues = new HashMap();
60
61 /**
62 * Creates a meta-object that will use the specified
63 * meta-kit and meta-class.
64 */
65 protected AbstractMetaObject( MetaClass metaClass, MetaKit metaKit )
66 {
67 setMetaClass( metaClass );
68 this.metaKit = metaKit;
69 }
70
71 /**
72 * Creates a meta-object that will use the specified
73 * meta-kit. The metaClass must be set by subclasses by calling
74 * the setMetaClass() method before any other local methods
75 * are accessed.
76 */
77 protected AbstractMetaObject( MetaKit metaKit )
78 {
79 this.metaKit = metaKit;
80 }
81
82 /**
83 * Called by subclasses to set the meta-class when setting
84 * it on the constructor is not possible. The meta-class
85 * can only be set once or a RuntimeException will be thrown.
86 */
87 protected void setMetaClass( MetaClass metaClass )
88 {
89 if( this.metaClass != null )
90 throw new RuntimeException( "A MetaClass can only by set once." );
91 if( metaClass == null )
92 throw new IllegalArgumentException( "MetaClass cannot be null." );
93 this.metaClass = metaClass;
94
95 // Resolve any deferred types
96 metaClass.resolveTypes();
97 }
98
99 /**
100 * Overridden by subclasses to provide implementation-specific
101 * field-level access.
102 */
103 protected abstract Object setPropertyValue( String name, Object value );
104
105 /**
106 * Overridden by subclasses to provide implementation-specific
107 * field-level access.
108 */
109 protected abstract Object getPropertyValue( String name );
110
111 /**
112 * Sticks a wrapped value into the wrapper cache.
113 */
114 protected void cacheWrapper( String name, Object value )
115 {
116 wrappedValues.put( name, value );
117 }
118
119 /**
120 * Retrieves a wrapped value from the wrapper cache.
121 */
122 protected Object getCachedWrapper( String name )
123 {
124 return( wrappedValues.get( name ) );
125 }
126
127 /**
128 * Wraps the object as appropriate to return to a caller or
129 * returns the original value if no wrapping is needed. This
130 * implementation checks the property type and wraps objects
131 * in meta-objects or MetaObjectListAdapters if needed. Wrapped
132 * values are cached in an internal HashMap to speed later retrievals
133 * and reuse meta-objects as much as possible. Objects are wrapped
134 * using the local MetaKit.
135 */
136 protected Object wrapPropertyValue( String name, PropertyType type, Object value )
137 {
138 if( value == null )
139 return( null ); // don't wrap nulls for now. FIXME?
140
141 if( type instanceof MetaClassPropertyType )
142 {
143 // See if the object is pre-wrapped
144 if( value instanceof MetaObject )
145 return( value );
146
147 // See if the object is already wrapped by us
148 MetaObject wrapped = (MetaObject)getCachedWrapper( name );
149 if( wrapped != null && metaKit.getInternalObject( wrapped ) == value )
150 {
151 value = wrapped;
152 }
153 else
154 {
155 MetaClass mClass = ((MetaClassPropertyType)type).getMetaClass();
156
157 // Try for a specific class
158 MetaClass c = metaKit.getMetaClassForObject( value, mClass.getClassRegistry() );
159 if( c != null )
160 value = metaKit.wrapObject( value, c );
161 else
162 value = metaKit.wrapObject( value, mClass );
163
164 // Store it away to use later
165 cacheWrapper( name, value );
166 }
167 }
168 else if( type instanceof ListPropertyType )
169 {
170 PropertyType elementType = ((ListPropertyType)type).getValueType();
171 if( elementType instanceof MetaClassPropertyType )
172 {
173 // See if the object is pre-wrapped
174 if( value instanceof MetaObjectListAdapter )
175 return( value );
176
177 // See if the object is already wrapped by us
178 MetaObjectListAdapter wrapped = (MetaObjectListAdapter)getCachedWrapper( name );
179 if( wrapped != null && wrapped.getList() == value )
180 {
181 value = wrapped;
182 }
183 else
184 {
185 MetaClass mClass = ((MetaClassPropertyType)elementType).getMetaClass();
186
187 // Wrap the value in a meta-object wrapper list, we assume it's
188 // a list of the appropriate objects
189 value = new MetaObjectListAdapter( metaKit, mClass, (List)value );
190
191 // Store it away to use later
192 cacheWrapper( name, value );
193 }
194 }
195 }
196
197 return( value );
198 }
199
200 /**
201 * Unwraps the object as appropriate to pass to the internal
202 * setPropertyValue() method or returns the original value if no
203 * unwrapping is needed. This implementation currently only
204 * checks for MetaObjectListAdapter values and returns the internal
205 * list. MetaObjects that can from a different meta-kit are not
206 * properly unwrapped yet. This is because technically a local
207 * meta-kit version of the meta-object created and the properties
208 * copied. This will be easier at a future time and is unnecessary
209 * for most users.
210 */
211 protected Object unwrapPropertyValue( Object value )
212 {
213 if( value instanceof MetaObject )
214 {
215 MetaObject metaObject = (MetaObject)value;
216
217 // Is it the same meta-kit?
218 if( metaObject.getMetaKit().equals( metaKit ) )
219 {
220 // Safe to unwrap
221 value = metaKit.getInternalObject( metaObject );
222 }
223 else
224 {
225 // Need to translate it back into a regular object
226 // by creating a new bean and copying the properties
227 // over
228 throw new UnsupportedOperationException( "Incompatible meta-kits in applied property value." );
229 }
230 }
231 else if( value instanceof MetaObjectListAdapter )
232 {
233 // Unwrap the list value
234 value = ((MetaObjectListAdapter)value).getList();
235 }
236 return( value );
237 }
238
239 /**
240 * Sets the value of the specified property. The default
241 * implementation does some initial type checking, unwraps
242 * the value as needed by calling unwrapPropertyValue() and
243 * then calls setPropertyValue() before firing a change event.
244 */
245 public void setProperty( String name, Object value )
246 {
247 PropertyType type = metaClass.getPropertyType( name );
248 if( type == null )
249 throw new RuntimeException( "No property defined:" + name );
250 if( value == null || !type.isInstance( value ) )
251 throw new RuntimeException( "Invalid value type for property:" + name + " type:" + type );
252
253 value = unwrapPropertyValue( value );
254
255 Object oldValue = setPropertyValue( name, value );
256
257 // Note: if the contained map is an observable map, we
258 // probably don't want to do this. FIXME
259 firePropertyChange( name, oldValue, value );
260 }
261
262 /**
263 * Returns the value of the specified property. The
264 * default version calls getPropertyValue() to retrieve
265 * the property value and then wraps the value as needed
266 * by calling wrapPropertyValue() before returning it.
267 */
268 public Object getProperty( String name )
269 {
270 PropertyType type = metaClass.getPropertyType( name );
271 if( type == null )
272 throw new RuntimeException( "No property defined:" + name );
273
274 Object val = getPropertyValue( name );
275
276 return( wrapPropertyValue( name, type, val ) );
277 }
278
279 /**
280 * Returns a mutator for the specified property. This implementation
281 * calls createListMutator() or createPropertyMutator() as is appropriate
282 * based on the PropertyInfo class.
283 */
284 public PropertyMutator getPropertyMutator( String name )
285 {
286 PropertyInfo info = metaClass.getPropertyInfo( name );
287 if( info == null )
288 throw new RuntimeException( "Property:" + name + " does not exist in:" + metaClass );
289
290 if( info instanceof ContainerPropertyInfo && info.getPropertyType() instanceof ListPropertyType )
291 {
292 return( createListMutator( name ) );
293 }
294 return( createPropertyMutator( name ) );
295 }
296
297 /**
298 * Returns the meta-class associated with this object.
299 */
300 public MetaClass getMetaClass()
301 {
302 return( metaClass );
303 }
304
305 /**
306 * Returns the meta-kit for this meta-object's implementation
307 * layer.
308 */
309 public MetaKit getMetaKit()
310 {
311 return( metaKit );
312 }
313
314 /**
315 * Creates a PropertyMutator for the specified property. The
316 * default implementation creates a DefaultPropertyMutator.
317 */
318 protected PropertyMutator createPropertyMutator( String name )
319 {
320 return( new DefaultPropertyMutator( name, this ) );
321 }
322
323 /**
324 * Creates a ListMutator for the specified property. The
325 * default implementation creates an instance of the inner
326 * class BaseListMutator which is derived from
327 * DefaultListMutator.
328 */
329 protected ListMutator createListMutator( String name )
330 {
331 return( new BaseListMutator( name ) );
332 }
333
334 /**
335 * Overridden to provide access to a firePropertyChange() method.
336 */
337 protected class BaseListMutator extends DefaultListMutator
338 {
339 public BaseListMutator( String propertyName )
340 {
341 super( propertyName, AbstractMetaObject.this );
342 }
343
344 protected void firePropertyChangeEvent( PropertyChangeEvent event )
345 {
346 AbstractMetaObject.this.firePropertyChange( event );
347 }
348 }
349 }
350