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 }