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.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
171
172
173
174
175
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
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
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
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
337
338 loopDescriptor.setUpdater(propertyUpdater);
339 loopDescriptor.setCollective(true);
340
341 if ( configuration.isWrapCollectionsInElement() ) {
342
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
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 }