1. package org.apache.commons.betwixt;
  2. /*
  3. * Copyright 2001-2004 The Apache Software Foundation.
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import java.beans.PropertyDescriptor;
  17. import java.lang.reflect.Method;
  18. import java.util.Map;
  19. import org.apache.commons.beanutils.DynaProperty;
  20. import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
  21. import org.apache.commons.betwixt.expression.DynaBeanExpression;
  22. import org.apache.commons.betwixt.expression.Expression;
  23. import org.apache.commons.betwixt.expression.IteratorExpression;
  24. import org.apache.commons.betwixt.expression.MethodExpression;
  25. import org.apache.commons.betwixt.expression.MethodUpdater;
  26. import org.apache.commons.betwixt.expression.Updater;
  27. import org.apache.commons.betwixt.strategy.NameMapper;
  28. import org.apache.commons.betwixt.strategy.SimpleTypeMapper;
  29. import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
  30. import org.apache.commons.logging.Log;
  31. /**
  32. * Betwixt-centric view of a bean (or pseudo-bean) property.
  33. * This object decouples the way that the (possibly pseudo) property introspection
  34. * is performed from the results of that introspection.
  35. *
  36. * @author Robert Burrell Donkin
  37. * @since 0.5
  38. */
  39. public class BeanProperty {
  40. /** The bean name for the property (not null) */
  41. private final String propertyName;
  42. /** The type of this property (not null) */
  43. private final Class propertyType;
  44. /** The Expression used to read values of this property (possibly null) */
  45. private Expression propertyExpression;
  46. /** The Updater used to write values of this property (possibly null) */
  47. private Updater propertyUpdater;
  48. /**
  49. * Construct a BeanProperty.
  50. * @param propertyName not null
  51. * @param propertyType not null
  52. * @param propertyExpression the Expression used to read the property,
  53. * null if the property is not readable
  54. * @param propertyUpdater the Updater used to write the property,
  55. * null if the property is not writable
  56. */
  57. public BeanProperty (
  58. String propertyName,
  59. Class propertyType,
  60. Expression propertyExpression,
  61. Updater propertyUpdater) {
  62. this.propertyName = propertyName;
  63. this.propertyType = propertyType;
  64. this.propertyExpression = propertyExpression;
  65. this.propertyUpdater = propertyUpdater;
  66. }
  67. /**
  68. * Constructs a BeanProperty from a <code>PropertyDescriptor</code>.
  69. * @param descriptor not null
  70. */
  71. public BeanProperty(PropertyDescriptor descriptor) {
  72. this.propertyName = descriptor.getName();
  73. this.propertyType = descriptor.getPropertyType();
  74. Method readMethod = descriptor.getReadMethod();
  75. if ( readMethod != null ) {
  76. this.propertyExpression = new MethodExpression( readMethod );
  77. }
  78. Method writeMethod = descriptor.getWriteMethod();
  79. if ( writeMethod != null ) {
  80. this.propertyUpdater = new MethodUpdater( writeMethod );
  81. }
  82. }
  83. /**
  84. * Constructs a BeanProperty from a <code>DynaProperty</code>
  85. * @param dynaProperty not null
  86. */
  87. public BeanProperty(DynaProperty dynaProperty) {
  88. this.propertyName = dynaProperty.getName();
  89. this.propertyType = dynaProperty.getType();
  90. this.propertyExpression = new DynaBeanExpression( propertyName );
  91. // todo: add updater
  92. }
  93. /**
  94. * Gets the bean name for this property.
  95. * Betwixt will map this to an xml name.
  96. * @return the bean name for this property, not null
  97. */
  98. public String getPropertyName() {
  99. return propertyName;
  100. }
  101. /**
  102. * Gets the type of this property.
  103. * @return the property type, not null
  104. */
  105. public Class getPropertyType() {
  106. return propertyType;
  107. }
  108. /**
  109. * Gets the expression used to read this property.
  110. * @return the expression to be used to read this property
  111. * or null if this property is not readable.
  112. */
  113. public Expression getPropertyExpression() {
  114. return propertyExpression;
  115. }
  116. /**
  117. * Gets the updater used to write to this properyty.
  118. * @return the Updater to the used to write to this property
  119. * or null if this property is not writable.
  120. */
  121. public Updater getPropertyUpdater() {
  122. return propertyUpdater;
  123. }
  124. /**
  125. * Create a XML descriptor from a bean one.
  126. * Go through and work out whether it's a loop property, a primitive or a standard.
  127. * The class property is ignored.
  128. *
  129. * @param beanProperty the BeanProperty specifying the property
  130. * @return a correctly configured <code>NodeDescriptor</code> for the property
  131. */
  132. public Descriptor createXMLDescriptor( IntrospectionConfiguration configuration ) {
  133. Log log = configuration.getIntrospectionLog();
  134. if (log.isTraceEnabled()) {
  135. log.trace("Creating descriptor for property: name="
  136. + getPropertyName() + " type=" + getPropertyType());
  137. }
  138. NodeDescriptor descriptor = null;
  139. Expression propertyExpression = getPropertyExpression();
  140. Updater propertyUpdater = getPropertyUpdater();
  141. if ( propertyExpression == null ) {
  142. if (log.isTraceEnabled()) {
  143. log.trace( "No read method for property: name="
  144. + getPropertyName() + " type=" + getPropertyType());
  145. }
  146. return null;
  147. }
  148. if ( log.isTraceEnabled() ) {
  149. log.trace( "Property expression=" + propertyExpression );
  150. }
  151. // choose response from property type
  152. // XXX: ignore class property ??
  153. if ( Class.class.equals( getPropertyType() ) && "class".equals( getPropertyName() ) ) {
  154. log.trace( "Ignoring class property" );
  155. return null;
  156. }
  157. //TODO this big conditional should be replaced with subclasses based
  158. // on the type
  159. //TODO complete simple type implementation
  160. TypeBindingStrategy.BindingType bindingType
  161. = configuration.getTypeBindingStrategy().bindingType( getPropertyType() ) ;
  162. if ( bindingType.equals( TypeBindingStrategy.BindingType.PRIMITIVE ) ) {
  163. descriptor =
  164. createDescriptorForPrimitive(
  165. configuration,
  166. propertyExpression,
  167. propertyUpdater);
  168. } else if ( XMLIntrospectorHelper.isLoopType( getPropertyType() ) ) {
  169. if (log.isTraceEnabled()) {
  170. log.trace("Loop type: " + getPropertyName());
  171. log.trace("Wrap in collections? " + configuration.isWrapCollectionsInElement());
  172. }
  173. if ( Map.class.isAssignableFrom( getPropertyType() )) {
  174. descriptor = createDescriptorForMap( configuration, propertyExpression );
  175. } else {
  176. descriptor
  177. = createDescriptorForCollective( configuration, propertyUpdater, propertyExpression );
  178. }
  179. } else {
  180. if (log.isTraceEnabled()) {
  181. log.trace( "Standard property: " + getPropertyName());
  182. }
  183. descriptor =
  184. createDescriptorForStandard(
  185. propertyExpression,
  186. propertyUpdater,
  187. configuration);
  188. }
  189. if (log.isTraceEnabled()) {
  190. log.trace( "Created descriptor:" );
  191. log.trace( descriptor );
  192. }
  193. return descriptor;
  194. }
  195. /**
  196. * Configures descriptor (in the standard way).
  197. * This sets the common properties.
  198. *
  199. * @param propertyName the name of the property mapped to the Descriptor, not null
  200. * @param propertyType the type of the property mapped to the Descriptor, not null
  201. * @param descriptor Descriptor to map, not null
  202. * @param configuration IntrospectionConfiguration, not null
  203. */
  204. private void configureDescriptor(
  205. NodeDescriptor descriptor,
  206. IntrospectionConfiguration configuration) {
  207. NameMapper nameMapper = configuration.getElementNameMapper();
  208. if (descriptor instanceof AttributeDescriptor) {
  209. // we want to use the attributemapper only when it is an attribute..
  210. nameMapper = configuration.getAttributeNameMapper();
  211. }
  212. descriptor.setLocalName( nameMapper.mapTypeToElementName( propertyName ));
  213. descriptor.setPropertyName( getPropertyName() );
  214. descriptor.setPropertyType( getPropertyType() );
  215. }
  216. /**
  217. * Creates an <code>ElementDescriptor</code> for a standard property
  218. * @param propertyExpression
  219. * @param propertyUpdater
  220. * @return
  221. */
  222. private ElementDescriptor createDescriptorForStandard(
  223. Expression propertyExpression,
  224. Updater propertyUpdater,
  225. IntrospectionConfiguration configuration) {
  226. ElementDescriptor result;
  227. ElementDescriptor elementDescriptor = new ElementDescriptor();
  228. elementDescriptor.setContextExpression( propertyExpression );
  229. if ( propertyUpdater != null ) {
  230. elementDescriptor.setUpdater( propertyUpdater );
  231. }
  232. elementDescriptor.setHollow(true);
  233. result = elementDescriptor;
  234. configureDescriptor(result, configuration);
  235. return result;
  236. }
  237. /**
  238. * Creates an ElementDescriptor for an <code>Map</code> type property
  239. * @param configuration
  240. * @param propertyExpression
  241. * @return
  242. */
  243. private ElementDescriptor createDescriptorForMap(
  244. IntrospectionConfiguration configuration,
  245. Expression propertyExpression) {
  246. //TODO: need to clean the element descriptors so that the wrappers are plain
  247. ElementDescriptor result;
  248. ElementDescriptor entryDescriptor = new ElementDescriptor();
  249. entryDescriptor.setContextExpression(
  250. new IteratorExpression( propertyExpression )
  251. );
  252. entryDescriptor.setLocalName( "entry" );
  253. entryDescriptor.setPropertyName( getPropertyName() );
  254. entryDescriptor.setPropertyType( getPropertyType() );
  255. // add elements for reading
  256. ElementDescriptor keyDescriptor = new ElementDescriptor( "key" );
  257. keyDescriptor.setHollow( true );
  258. entryDescriptor.addElementDescriptor( keyDescriptor );
  259. ElementDescriptor valueDescriptor = new ElementDescriptor( "value" );
  260. valueDescriptor.setHollow( true );
  261. entryDescriptor.addElementDescriptor( valueDescriptor );
  262. if ( configuration.isWrapCollectionsInElement() ) {
  263. ElementDescriptor wrappingDescriptor = new ElementDescriptor();
  264. wrappingDescriptor.setElementDescriptors( new ElementDescriptor[] { entryDescriptor } );
  265. NameMapper nameMapper = configuration.getElementNameMapper();
  266. wrappingDescriptor.setLocalName( nameMapper.mapTypeToElementName( propertyName ));
  267. result = wrappingDescriptor;
  268. } else {
  269. result = entryDescriptor;
  270. }
  271. return result;
  272. }
  273. /**
  274. * Creates an <code>ElementDescriptor</code> for a collective type property
  275. * @param configuration
  276. * @param propertyUpdater, <code>Updater</code> for the property, possibly null
  277. * @param propertyExpression
  278. * @return
  279. */
  280. private ElementDescriptor createDescriptorForCollective(
  281. IntrospectionConfiguration configuration,
  282. Updater propertyUpdater,
  283. Expression propertyExpression) {
  284. ElementDescriptor result;
  285. ElementDescriptor loopDescriptor = new ElementDescriptor();
  286. loopDescriptor.setContextExpression(
  287. new IteratorExpression( propertyExpression )
  288. );
  289. loopDescriptor.setPropertyName(getPropertyName());
  290. loopDescriptor.setPropertyType(getPropertyType());
  291. loopDescriptor.setHollow(true);
  292. // set the property updater (if it exists)
  293. // may be overridden later by the adder
  294. loopDescriptor.setUpdater(propertyUpdater);
  295. if ( configuration.isWrapCollectionsInElement() ) {
  296. // create wrapping desctiptor
  297. ElementDescriptor wrappingDescriptor = new ElementDescriptor();
  298. wrappingDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
  299. wrappingDescriptor.setLocalName(
  300. configuration.getElementNameMapper().mapTypeToElementName( propertyName ));
  301. result = wrappingDescriptor;
  302. } else {
  303. // unwrapped Descriptor
  304. result = loopDescriptor;
  305. }
  306. return result;
  307. }
  308. /**
  309. * Creates a NodeDescriptor for a primitive type node
  310. * @param configuration
  311. * @param name
  312. * @param log
  313. * @param propertyExpression
  314. * @param propertyUpdater
  315. * @return
  316. */
  317. private NodeDescriptor createDescriptorForPrimitive(
  318. IntrospectionConfiguration configuration,
  319. Expression propertyExpression,
  320. Updater propertyUpdater) {
  321. Log log = configuration.getIntrospectionLog();
  322. NodeDescriptor descriptor;
  323. if (log.isTraceEnabled()) {
  324. log.trace( "Primitive type: " + getPropertyName());
  325. }
  326. SimpleTypeMapper.Binding binding
  327. = configuration.getSimpleTypeMapper().bind(
  328. propertyName,
  329. propertyType,
  330. configuration);
  331. if ( SimpleTypeMapper.Binding.ATTRIBUTE.equals( binding )) {
  332. if (log.isTraceEnabled()) {
  333. log.trace( "Adding property as attribute: " + getPropertyName() );
  334. }
  335. descriptor = new AttributeDescriptor();
  336. } else {
  337. if (log.isTraceEnabled()) {
  338. log.trace( "Adding property as element: " + getPropertyName() );
  339. }
  340. descriptor = new ElementDescriptor();
  341. }
  342. descriptor.setTextExpression( propertyExpression );
  343. if ( propertyUpdater != null ) {
  344. descriptor.setUpdater( propertyUpdater );
  345. }
  346. configureDescriptor(descriptor, configuration);
  347. return descriptor;
  348. }
  349. }