View Javadoc

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