1 package org.apache.commons.betwixt;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
171
172
173 if ( Class.class.equals( getPropertyType() ) && "class".equals( getPropertyName() ) ) {
174 log.trace( "Ignoring class property" );
175 return null;
176
177 }
178
179
180
181
182
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
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
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
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
344
345 loopDescriptor.setUpdater(propertyUpdater);
346
347 if ( configuration.isWrapCollectionsInElement() ) {
348
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
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 }