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.digester.XMLIntrospectorHelper;
24  import org.apache.commons.betwixt.expression.DynaBeanExpression;
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         // todo: add updater
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 beanProperty the BeanProperty specifying the property
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         // XXX: ignore class property ??
173         if ( Class.class.equals( getPropertyType() ) && "class".equals( getPropertyName() ) ) {
174             log.trace( "Ignoring class property" );
175             return null;
176             
177         }
178         
179         //TODO this big conditional should be replaced with subclasses based
180         // on the type
181         
182         //TODO complete simple type implementation
183         TypeBindingStrategy.BindingType bindingType 
184         		= configuration.getTypeBindingStrategy().bindingType( getPropertyType() ) ;
185         if ( bindingType.equals( TypeBindingStrategy.BindingType.PRIMITIVE ) ) {
186             descriptor =
187                 createDescriptorForPrimitive(
188                     configuration,
189                     propertyExpression,
190                     propertyUpdater);
191             
192         } else if ( XMLIntrospectorHelper.isLoopType( getPropertyType() ) ) {
193             
194             if (log.isTraceEnabled()) {
195                 log.trace("Loop type: " + getPropertyName());
196                 log.trace("Wrap in collections? " + configuration.isWrapCollectionsInElement());
197             }
198             
199             if ( Map.class.isAssignableFrom( getPropertyType() )) {
200                 descriptor = createDescriptorForMap( configuration, propertyExpression );
201             } else {
202             
203                 descriptor 
204                     = createDescriptorForCollective( configuration, propertyUpdater, propertyExpression );
205             }
206         } else {
207             if (log.isTraceEnabled()) {
208                 log.trace( "Standard property: " + getPropertyName());
209             }
210             descriptor =
211                 createDescriptorForStandard(
212                     propertyExpression,
213                     propertyUpdater,
214                     configuration);
215         }
216         
217         
218        
219         if (log.isTraceEnabled()) {
220             log.trace( "Created descriptor:" );
221             log.trace( descriptor );
222         }
223         return descriptor;
224     }
225 
226     /***
227      * Configures descriptor (in the standard way).
228      * This sets the common properties.
229      * 
230      * @param propertyName the name of the property mapped to the Descriptor, not null
231      * @param propertyType the type of the property mapped to the Descriptor, not null
232      * @param descriptor Descriptor to map, not null
233      * @param configuration IntrospectionConfiguration, not null
234      */
235     private void configureDescriptor(
236         NodeDescriptor descriptor,
237         IntrospectionConfiguration configuration) {
238         NameMapper nameMapper = configuration.getElementNameMapper();
239         if (descriptor instanceof AttributeDescriptor) {
240             // we want to use the attributemapper only when it is an attribute.. 
241             nameMapper = configuration.getAttributeNameMapper();
242         
243         }           
244         descriptor.setLocalName( nameMapper.mapTypeToElementName( propertyName ));
245         descriptor.setPropertyName( getPropertyName() );
246         descriptor.setPropertyType( getPropertyType() );
247     }
248     
249     /***
250      * Creates an <code>ElementDescriptor</code> for a standard property
251      * @param propertyExpression
252      * @param propertyUpdater
253      * @return
254      */
255     private ElementDescriptor createDescriptorForStandard(
256         Expression propertyExpression,
257         Updater propertyUpdater, 
258         IntrospectionConfiguration configuration) {
259             
260         ElementDescriptor result;
261 
262         ElementDescriptor elementDescriptor = new ElementDescriptor();
263         elementDescriptor.setContextExpression( propertyExpression );
264         if ( propertyUpdater != null ) {
265             elementDescriptor.setUpdater( propertyUpdater );
266         }
267         
268         elementDescriptor.setHollow(true);
269         
270         result = elementDescriptor;
271         
272         configureDescriptor(result, configuration);
273         return result;
274     }
275 
276     /***
277      * Creates an ElementDescriptor for an <code>Map</code> type property
278      * @param configuration
279      * @param propertyExpression
280      * @return
281      */
282     private ElementDescriptor createDescriptorForMap(
283         IntrospectionConfiguration configuration,
284         Expression propertyExpression) {
285             
286         //TODO: need to clean the element descriptors so that the wrappers are plain
287         ElementDescriptor result;
288         
289         ElementDescriptor entryDescriptor = new ElementDescriptor();
290         entryDescriptor.setContextExpression(
291             new IteratorExpression( propertyExpression )
292         );
293 
294         entryDescriptor.setLocalName( "entry" );
295         entryDescriptor.setPropertyName( getPropertyName() );
296         entryDescriptor.setPropertyType( getPropertyType() );
297         
298         // add elements for reading
299         ElementDescriptor keyDescriptor = new ElementDescriptor( "key" );
300         keyDescriptor.setHollow( true );
301         entryDescriptor.addElementDescriptor( keyDescriptor );
302         
303         ElementDescriptor valueDescriptor = new ElementDescriptor( "value" );
304         valueDescriptor.setHollow( true );
305         entryDescriptor.addElementDescriptor( valueDescriptor );
306         
307         
308         if ( configuration.isWrapCollectionsInElement() ) {
309             ElementDescriptor wrappingDescriptor = new ElementDescriptor();
310             wrappingDescriptor.setElementDescriptors( new ElementDescriptor[] { entryDescriptor } );
311             NameMapper nameMapper = configuration.getElementNameMapper();   
312             wrappingDescriptor.setLocalName( nameMapper.mapTypeToElementName( propertyName ));           
313             result = wrappingDescriptor;
314                         
315         } else {
316             result = entryDescriptor;
317         }
318 
319         return result;
320     }
321 
322     /***
323      * Creates an <code>ElementDescriptor</code> for a collective type property
324      * @param configuration
325      * @param propertyUpdater, <code>Updater</code> for the property, possibly null
326      * @param propertyExpression
327      * @return
328      */
329     private ElementDescriptor createDescriptorForCollective(
330         IntrospectionConfiguration configuration,
331         Updater propertyUpdater,
332         Expression propertyExpression) {
333             
334         ElementDescriptor result;
335         
336         ElementDescriptor loopDescriptor = new ElementDescriptor();
337         loopDescriptor.setContextExpression(
338             new IteratorExpression( propertyExpression )
339         );
340         loopDescriptor.setPropertyName(getPropertyName());
341         loopDescriptor.setPropertyType(getPropertyType());
342         loopDescriptor.setHollow(true);
343         // set the property updater (if it exists)
344         // may be overridden later by the adder
345         loopDescriptor.setUpdater(propertyUpdater);
346         
347         if ( configuration.isWrapCollectionsInElement() ) {
348             // create wrapping desctiptor
349             ElementDescriptor wrappingDescriptor = new ElementDescriptor();
350             wrappingDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
351             wrappingDescriptor.setLocalName(
352                 configuration.getElementNameMapper().mapTypeToElementName( propertyName ));
353             result = wrappingDescriptor;
354         
355         } else {   
356             // unwrapped Descriptor
357             result = loopDescriptor;
358         }
359         return result;
360     }
361 
362     /***
363      * Creates a NodeDescriptor for a primitive type node
364      * @param configuration
365      * @param name
366      * @param log
367      * @param propertyExpression
368      * @param propertyUpdater
369      * @return
370      */
371     private NodeDescriptor createDescriptorForPrimitive(
372         IntrospectionConfiguration configuration,
373         Expression propertyExpression,
374         Updater propertyUpdater) {
375         Log log = configuration.getIntrospectionLog();
376         NodeDescriptor descriptor;
377         if (log.isTraceEnabled()) {
378             log.trace( "Primitive type: " + getPropertyName());
379         }
380         SimpleTypeMapper.Binding binding 
381             = configuration.getSimpleTypeMapper().bind(
382                                                         propertyName, 
383                                                         propertyType, 
384                                                         configuration);
385         if ( SimpleTypeMapper.Binding.ATTRIBUTE.equals( binding )) {
386             if (log.isTraceEnabled()) {
387                 log.trace( "Adding property as attribute: " + getPropertyName() );
388             }
389             descriptor = new AttributeDescriptor();
390         } else {
391             if (log.isTraceEnabled()) {
392                 log.trace( "Adding property as element: " + getPropertyName() );
393             }
394             descriptor = new ElementDescriptor();
395         }
396         descriptor.setTextExpression( propertyExpression );
397         if ( propertyUpdater != null ) {
398             descriptor.setUpdater( propertyUpdater );
399         }
400         configureDescriptor(descriptor, configuration);
401         return descriptor;
402     }
403 }