1 package org.apache.commons.betwixt;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
173
174
175
176
177
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
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
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
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
339
340 loopDescriptor.setUpdater(propertyUpdater);
341 loopDescriptor.setCollective(true);
342
343 if ( configuration.isWrapCollectionsInElement() ) {
344
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
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 }