001 package org.apache.commons.betwixt;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one or more
005 * contributor license agreements. See the NOTICE file distributed with
006 * this work for additional information regarding copyright ownership.
007 * The ASF licenses this file to You under the Apache License, Version 2.0
008 * (the "License"); you may not use this file except in compliance with
009 * the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020 import java.beans.PropertyDescriptor;
021 import java.lang.reflect.Method;
022 import java.util.Map;
023
024 import org.apache.commons.beanutils.DynaProperty;
025 import org.apache.commons.betwixt.expression.DynaBeanExpression;
026 import org.apache.commons.betwixt.expression.DynaBeanUpdater;
027 import org.apache.commons.betwixt.expression.Expression;
028 import org.apache.commons.betwixt.expression.IteratorExpression;
029 import org.apache.commons.betwixt.expression.MethodExpression;
030 import org.apache.commons.betwixt.expression.MethodUpdater;
031 import org.apache.commons.betwixt.expression.Updater;
032 import org.apache.commons.betwixt.strategy.NameMapper;
033 import org.apache.commons.betwixt.strategy.SimpleTypeMapper;
034 import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
035 import org.apache.commons.logging.Log;
036
037 /**
038 * Betwixt-centric view of a bean (or pseudo-bean) property.
039 * This object decouples the way that the (possibly pseudo) property introspection
040 * is performed from the results of that introspection.
041 *
042 * @author Robert Burrell Donkin
043 * @since 0.5
044 */
045 public class BeanProperty {
046
047 /** The bean name for the property (not null) */
048 private final String propertyName;
049 /** The type of this property (not null) */
050 private final Class propertyType;
051 /** The Expression used to read values of this property (possibly null) */
052 private Expression propertyExpression;
053 /** The Updater used to write values of this property (possibly null) */
054 private Updater propertyUpdater;
055
056 /**
057 * Construct a BeanProperty.
058 * @param propertyName not null
059 * @param propertyType not null
060 * @param propertyExpression the Expression used to read the property,
061 * null if the property is not readable
062 * @param propertyUpdater the Updater used to write the property,
063 * null if the property is not writable
064 */
065 public BeanProperty (
066 String propertyName,
067 Class propertyType,
068 Expression propertyExpression,
069 Updater propertyUpdater) {
070 this.propertyName = propertyName;
071 this.propertyType = propertyType;
072 this.propertyExpression = propertyExpression;
073 this.propertyUpdater = propertyUpdater;
074 }
075
076 /**
077 * Constructs a BeanProperty from a <code>PropertyDescriptor</code>.
078 * @param descriptor not null
079 */
080 public BeanProperty(PropertyDescriptor descriptor) {
081 this.propertyName = descriptor.getName();
082 this.propertyType = descriptor.getPropertyType();
083
084 Method readMethod = descriptor.getReadMethod();
085 if ( readMethod != null ) {
086 this.propertyExpression = new MethodExpression( readMethod );
087 }
088
089 Method writeMethod = descriptor.getWriteMethod();
090 if ( writeMethod != null ) {
091 this.propertyUpdater = new MethodUpdater( writeMethod );
092 }
093 }
094
095 /**
096 * Constructs a BeanProperty from a <code>DynaProperty</code>
097 * @param dynaProperty not null
098 */
099 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 }