View Javadoc

1   package org.apache.commons.betwixt;
2   
3   /*
4    * Copyright 2001-2004 The Apache Software Foundation.
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */ 
17  
18  import java.beans.PropertyDescriptor;
19  import java.lang.reflect.Method;
20  import java.util.Map;
21  
22  import org.apache.commons.beanutils.DynaProperty;
23  import org.apache.commons.betwixt.expression.DynaBeanExpression;
24  import org.apache.commons.betwixt.expression.DynaBeanUpdater;
25  import org.apache.commons.betwixt.expression.Expression;
26  import org.apache.commons.betwixt.expression.IteratorExpression;
27  import org.apache.commons.betwixt.expression.MethodExpression;
28  import org.apache.commons.betwixt.expression.MethodUpdater;
29  import org.apache.commons.betwixt.expression.Updater;
30  import org.apache.commons.betwixt.strategy.NameMapper;
31  import org.apache.commons.betwixt.strategy.SimpleTypeMapper;
32  import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
33  import org.apache.commons.logging.Log;
34  
35  /*** 
36    * Betwixt-centric view of a bean (or pseudo-bean) property.
37    * This object decouples the way that the (possibly pseudo) property introspection
38    * is performed from the results of that introspection.
39    *
40    * @author Robert Burrell Donkin
41    * @since 0.5
42    */
43  public class BeanProperty {
44  
45      /*** The bean name for the property (not null) */
46      private final String propertyName;
47      /*** The type of this property (not null) */
48      private final Class propertyType;
49      /*** The Expression used to read values of this property (possibly null) */
50      private Expression propertyExpression;
51      /*** The Updater used to write values of this property (possibly null) */
52      private Updater propertyUpdater;
53  
54      /***
55       * Construct a BeanProperty.
56       * @param propertyName not null
57       * @param propertyType not null
58       * @param propertyExpression the Expression used to read the property, 
59       * null if the property is not readable
60       * @param propertyUpdater the Updater used to write the property, 
61       * null if the property is not writable
62       */
63      public BeanProperty (
64                          String propertyName, 
65                          Class propertyType, 
66                          Expression propertyExpression, 
67                          Updater propertyUpdater) {
68          this.propertyName = propertyName;
69          this.propertyType = propertyType;
70          this.propertyExpression = propertyExpression;
71          this.propertyUpdater = propertyUpdater;        
72      }
73      
74      /***
75       * Constructs a BeanProperty from a <code>PropertyDescriptor</code>.
76       * @param descriptor not null
77       */
78      public BeanProperty(PropertyDescriptor descriptor) {
79          this.propertyName = descriptor.getName();
80          this.propertyType = descriptor.getPropertyType();
81          
82          Method readMethod = descriptor.getReadMethod();
83          if ( readMethod != null ) {
84              this.propertyExpression = new MethodExpression( readMethod );
85          }
86          
87          Method writeMethod = descriptor.getWriteMethod();
88          if ( writeMethod != null ) {
89              this.propertyUpdater = new MethodUpdater( writeMethod ); 
90          }
91      }
92      
93      /***
94       * Constructs a BeanProperty from a <code>DynaProperty</code>
95       * @param dynaProperty not null
96       */
97      public BeanProperty(DynaProperty dynaProperty) {
98          this.propertyName = dynaProperty.getName();
99          this.propertyType = dynaProperty.getType();
100         this.propertyExpression = new DynaBeanExpression( propertyName );
101         this.propertyUpdater = new DynaBeanUpdater( propertyName, propertyType );
102     }
103 
104     /***
105       * Gets the bean name for this property.
106       * Betwixt will map this to an xml name.
107       * @return the bean name for this property, not null
108       */
109     public String getPropertyName() {
110         return propertyName;
111     }
112 
113     /*** 
114       * Gets the type of this property.
115       * @return the property type, not null
116       */
117     public Class getPropertyType() {
118         return propertyType;
119     }
120     
121     /***
122       * Gets the expression used to read this property.
123       * @return the expression to be used to read this property 
124       * or null if this property is not readable.
125       */
126     public Expression getPropertyExpression() {
127         return propertyExpression;
128     }
129     
130     /***
131       * Gets the updater used to write to this properyty.
132       * @return the Updater to the used to write to this property
133       * or null if this property is not writable.
134       */ 
135     public Updater getPropertyUpdater() {
136         return propertyUpdater;
137     }
138     
139     /*** 
140      * Create a XML descriptor from a bean one. 
141      * Go through and work out whether it's a loop property, a primitive or a standard.
142      * The class property is ignored.
143      *
144      * @param configuration <code>IntrospectionConfiguration</code>, not null
145      * @return a correctly configured <code>NodeDescriptor</code> for the property
146      */
147     public Descriptor createXMLDescriptor( IntrospectionConfiguration configuration ) {
148         Log log = configuration.getIntrospectionLog();
149         if (log.isTraceEnabled()) {
150             log.trace("Creating descriptor for property: name="
151                 + getPropertyName() + " type=" + getPropertyType());
152         }
153         
154         NodeDescriptor descriptor = null;
155         Expression propertyExpression = getPropertyExpression();
156         Updater propertyUpdater = getPropertyUpdater();
157         
158         if ( propertyExpression == null ) {
159             if (log.isTraceEnabled()) {
160                 log.trace( "No read method for property: name="
161                     + getPropertyName() + " type=" + getPropertyType());
162             }
163             return null;
164         }
165         
166         if ( log.isTraceEnabled() ) {
167             log.trace( "Property expression=" + propertyExpression );
168         }
169         
170         // choose response from property type
171         
172         //TODO this big conditional should be replaced with subclasses based
173         // on the type
174         
175         //TODO complete simple type implementation
176         TypeBindingStrategy.BindingType bindingType 
177         		= configuration.getTypeBindingStrategy().bindingType( getPropertyType() ) ;
178         if ( bindingType.equals( TypeBindingStrategy.BindingType.PRIMITIVE ) ) {
179             descriptor =
180                 createDescriptorForPrimitive(
181                     configuration,
182                     propertyExpression,
183                     propertyUpdater);
184             
185         } else if ( configuration.isLoopType( getPropertyType() ) ) {
186             
187             if (log.isTraceEnabled()) {
188                 log.trace("Loop type: " + getPropertyName());
189                 log.trace("Wrap in collections? " + configuration.isWrapCollectionsInElement());
190             }
191             
192             if ( Map.class.isAssignableFrom( getPropertyType() )) {
193                 descriptor = createDescriptorForMap( configuration, propertyExpression );
194             } else {
195             
196                 descriptor 
197                     = createDescriptorForCollective( configuration, propertyUpdater, propertyExpression );
198             }
199         } else {
200             if (log.isTraceEnabled()) {
201                 log.trace( "Standard property: " + getPropertyName());
202             }
203             descriptor =
204                 createDescriptorForStandard(
205                     propertyExpression,
206                     propertyUpdater,
207                     configuration);
208         }
209         
210         
211        
212         if (log.isTraceEnabled()) {
213             log.trace( "Created descriptor:" );
214             log.trace( descriptor );
215         }
216         return descriptor;
217     }
218 
219     /***
220      * Configures descriptor (in the standard way).
221      * This sets the common properties.
222      * 
223      * @param propertyName the name of the property mapped to the Descriptor, not null
224      * @param propertyType the type of the property mapped to the Descriptor, not null
225      * @param descriptor Descriptor to map, not null
226      * @param configuration IntrospectionConfiguration, not null
227      */
228     private void configureDescriptor(
229         NodeDescriptor descriptor,
230         IntrospectionConfiguration configuration) {
231         NameMapper nameMapper = configuration.getElementNameMapper();
232         if (descriptor instanceof AttributeDescriptor) {
233             // we want to use the attributemapper only when it is an attribute.. 
234             nameMapper = configuration.getAttributeNameMapper();
235         
236         }           
237         descriptor.setLocalName( nameMapper.mapTypeToElementName( propertyName ));
238         descriptor.setPropertyName( getPropertyName() );
239         descriptor.setPropertyType( getPropertyType() );
240     }
241     
242     /***
243      * Creates an <code>ElementDescriptor</code> for a standard property
244      * @param propertyExpression
245      * @param propertyUpdater
246      * @return
247      */
248     private ElementDescriptor createDescriptorForStandard(
249         Expression propertyExpression,
250         Updater propertyUpdater, 
251         IntrospectionConfiguration configuration) {
252             
253         ElementDescriptor result;
254 
255         ElementDescriptor elementDescriptor = new ElementDescriptor();
256         elementDescriptor.setContextExpression( propertyExpression );
257         if ( propertyUpdater != null ) {
258             elementDescriptor.setUpdater( propertyUpdater );
259         }
260         
261         elementDescriptor.setHollow(true);
262         
263         result = elementDescriptor;
264         
265         configureDescriptor(result, configuration);
266         return result;
267     }
268 
269     /***
270      * Creates an ElementDescriptor for an <code>Map</code> type property
271      * @param configuration
272      * @param propertyExpression
273      * @return
274      */
275     private ElementDescriptor createDescriptorForMap(
276         IntrospectionConfiguration configuration,
277         Expression propertyExpression) {
278             
279         //TODO: need to clean the element descriptors so that the wrappers are plain
280         ElementDescriptor result;
281         
282         ElementDescriptor entryDescriptor = new ElementDescriptor();
283         entryDescriptor.setContextExpression(
284             new IteratorExpression( propertyExpression )
285         );
286 
287         entryDescriptor.setLocalName( "entry" );
288         entryDescriptor.setPropertyName( getPropertyName() );
289         entryDescriptor.setPropertyType( getPropertyType() );
290         
291         // add elements for reading
292         ElementDescriptor keyDescriptor = new ElementDescriptor( "key" );
293         keyDescriptor.setHollow( true );
294         entryDescriptor.addElementDescriptor( keyDescriptor );
295         
296         ElementDescriptor valueDescriptor = new ElementDescriptor( "value" );
297         valueDescriptor.setHollow( true );
298         entryDescriptor.addElementDescriptor( valueDescriptor );
299         
300         
301         if ( configuration.isWrapCollectionsInElement() ) {
302             ElementDescriptor wrappingDescriptor = new ElementDescriptor();
303             wrappingDescriptor.setElementDescriptors( new ElementDescriptor[] { entryDescriptor } );
304             NameMapper nameMapper = configuration.getElementNameMapper();   
305             wrappingDescriptor.setLocalName( nameMapper.mapTypeToElementName( propertyName ));           
306             result = wrappingDescriptor;
307                         
308         } else {
309             result = entryDescriptor;
310         }
311         result.setCollective(true);
312         return result;
313     }
314 
315     /***
316      * Creates an <code>ElementDescriptor</code> for a collective type property
317      * @param configuration
318      * @param propertyUpdater, <code>Updater</code> for the property, possibly null
319      * @param propertyExpression
320      * @return
321      */
322     private ElementDescriptor createDescriptorForCollective(
323         IntrospectionConfiguration configuration,
324         Updater propertyUpdater,
325         Expression propertyExpression) {
326             
327         ElementDescriptor result;
328         
329         ElementDescriptor loopDescriptor = new ElementDescriptor();
330         loopDescriptor.setContextExpression(
331             new IteratorExpression( propertyExpression )
332         );
333         loopDescriptor.setPropertyName(getPropertyName());
334         loopDescriptor.setPropertyType(getPropertyType());
335         loopDescriptor.setHollow(true);
336         // set the property updater (if it exists)
337         // may be overridden later by the adder
338         loopDescriptor.setUpdater(propertyUpdater);
339         loopDescriptor.setCollective(true);
340         
341         if ( configuration.isWrapCollectionsInElement() ) {
342             // create wrapping desctiptor
343             ElementDescriptor wrappingDescriptor = new ElementDescriptor();
344             wrappingDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
345             wrappingDescriptor.setLocalName(
346                 configuration.getElementNameMapper().mapTypeToElementName( propertyName ));
347             result = wrappingDescriptor;
348         
349         } else {   
350             // unwrapped Descriptor
351             result = loopDescriptor;
352         }
353         return result;
354     }
355 
356     /***
357      * Creates a NodeDescriptor for a primitive type node
358      * @param configuration
359      * @param name
360      * @param log
361      * @param propertyExpression
362      * @param propertyUpdater
363      * @return
364      */
365     private NodeDescriptor createDescriptorForPrimitive(
366         IntrospectionConfiguration configuration,
367         Expression propertyExpression,
368         Updater propertyUpdater) {
369         Log log = configuration.getIntrospectionLog();
370         NodeDescriptor descriptor;
371         if (log.isTraceEnabled()) {
372             log.trace( "Primitive type: " + getPropertyName());
373         }
374         SimpleTypeMapper.Binding binding 
375             = configuration.getSimpleTypeMapper().bind(
376                                                         propertyName, 
377                                                         propertyType, 
378                                                         configuration);
379         if ( SimpleTypeMapper.Binding.ATTRIBUTE.equals( binding )) {
380             if (log.isTraceEnabled()) {
381                 log.trace( "Adding property as attribute: " + getPropertyName() );
382             }
383             descriptor = new AttributeDescriptor();
384         } else {
385             if (log.isTraceEnabled()) {
386                 log.trace( "Adding property as element: " + getPropertyName() );
387             }
388             descriptor = new ElementDescriptor();
389         }
390         descriptor.setTextExpression( propertyExpression );
391         if ( propertyUpdater != null ) {
392             descriptor.setUpdater( propertyUpdater );
393         }
394         configureDescriptor(descriptor, configuration);
395         return descriptor;
396     }
397 }