1. package org.apache.commons.betwixt;
  2. /*
  3. * Copyright 2001-2004 The Apache Software Foundation.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. import java.beans.BeanDescriptor;
  18. import java.beans.BeanInfo;
  19. import java.beans.IntrospectionException;
  20. import java.beans.Introspector;
  21. import java.beans.PropertyDescriptor;
  22. import java.lang.reflect.Method;
  23. import java.net.URL;
  24. import java.util.ArrayList;
  25. import java.util.HashMap;
  26. import java.util.Iterator;
  27. import java.util.List;
  28. import java.util.Map;
  29. import org.apache.commons.beanutils.DynaBean;
  30. import org.apache.commons.beanutils.DynaClass;
  31. import org.apache.commons.beanutils.DynaProperty;
  32. import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
  33. import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
  34. import org.apache.commons.betwixt.expression.EmptyExpression;
  35. import org.apache.commons.betwixt.expression.IteratorExpression;
  36. import org.apache.commons.betwixt.expression.MapEntryAdder;
  37. import org.apache.commons.betwixt.expression.MethodUpdater;
  38. import org.apache.commons.betwixt.expression.StringExpression;
  39. import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
  40. import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
  41. import org.apache.commons.betwixt.strategy.ClassNormalizer;
  42. import org.apache.commons.betwixt.strategy.DefaultNameMapper;
  43. import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
  44. import org.apache.commons.betwixt.strategy.NameMapper;
  45. import org.apache.commons.betwixt.strategy.PluralStemmer;
  46. import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
  47. import org.apache.commons.logging.Log;
  48. import org.apache.commons.logging.LogFactory;
  49. /**
  50. * <p><code>XMLIntrospector</code> an introspector of beans to create a
  51. * XMLBeanInfo instance.</p>
  52. *
  53. * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
  54. * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
  55. * for a particular class, the <code>XMLBeanInfo</code> is cached.
  56. * Later requests for the same class will return the cached value.</p>
  57. *
  58. * <p>Note :</p>
  59. * <p>This class makes use of the <code>java.bean.Introspector</code>
  60. * class, which contains a BeanInfoSearchPath. To make sure betwixt can
  61. * do his work correctly, this searchpath is completely ignored during
  62. * processing. The original values will be restored after processing finished
  63. * </p>
  64. *
  65. * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
  66. * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
  67. */
  68. public class XMLIntrospector {
  69. /**
  70. * Log used for logging (Doh!)
  71. * @deprecated 0.6 use the {@link #getLog()} property instead
  72. */
  73. protected Log log = LogFactory.getLog( XMLIntrospector.class );
  74. /** Maps classes to <code>XMLBeanInfo</code>'s */
  75. private XMLBeanInfoRegistry registry = new DefaultXMLBeanInfoRegistry();
  76. /** Digester used to parse the XML descriptor files */
  77. private XMLBeanInfoDigester digester;
  78. /** Configuration to be used for introspection*/
  79. private IntrospectionConfiguration configuration;
  80. /** Base constructor */
  81. public XMLIntrospector() {
  82. this(new IntrospectionConfiguration());
  83. }
  84. /**
  85. * Construct allows a custom configuration to be set on construction.
  86. * This allows <code>IntrospectionConfiguration</code> subclasses
  87. * to be easily used.
  88. * @param configuration IntrospectionConfiguration, not null
  89. */
  90. public XMLIntrospector(IntrospectionConfiguration configuration) {
  91. setConfiguration(configuration);
  92. }
  93. // Properties
  94. //-------------------------------------------------------------------------
  95. /**
  96. * <p>Gets the current logging implementation. </p>
  97. * @return the Log implementation which this class logs to
  98. */
  99. public Log getLog() {
  100. return getConfiguration().getIntrospectionLog();
  101. }
  102. /**
  103. * <p>Sets the current logging implementation.</p>
  104. * @param log the Log implementation to use for logging
  105. */
  106. public void setLog(Log log) {
  107. getConfiguration().setIntrospectionLog(log);
  108. }
  109. /**
  110. * <p>Gets the current registry implementation.
  111. * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
  112. * before introspecting.
  113. * After standard introspection is complete, the instance will be passed to the registry.</p>
  114. *
  115. * <p>This allows finely grained control over the caching strategy.
  116. * It also allows the standard introspection mechanism
  117. * to be overridden on a per class basis.</p>
  118. *
  119. * @return the XMLBeanInfoRegistry currently used
  120. */
  121. public XMLBeanInfoRegistry getRegistry() {
  122. return registry;
  123. }
  124. /**
  125. * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
  126. * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
  127. * before introspecting.
  128. * After standard introspection is complete, the instance will be passed to the registry.</p>
  129. *
  130. * <p>This allows finely grained control over the caching strategy.
  131. * It also allows the standard introspection mechanism
  132. * to be overridden on a per class basis.</p>
  133. *
  134. * @param registry the XMLBeanInfoRegistry to use
  135. */
  136. public void setRegistry(XMLBeanInfoRegistry registry) {
  137. this.registry = registry;
  138. }
  139. /**
  140. * Gets the configuration to be used for introspection.
  141. * The various introspection-time strategies
  142. * and configuration variables have been consolidated as properties
  143. * of this bean.
  144. * This allows the configuration to be more easily shared.
  145. * @return IntrospectionConfiguration, not null
  146. */
  147. public IntrospectionConfiguration getConfiguration() {
  148. return configuration;
  149. }
  150. /**
  151. * Sets the configuration to be used for introspection.
  152. * The various introspection-time strategies
  153. * and configuration variables have been consolidated as properties
  154. * of this bean.
  155. * This allows the configuration to be more easily shared.
  156. * @param configuration IntrospectionConfiguration, not null
  157. */
  158. public void setConfiguration(IntrospectionConfiguration configuration) {
  159. this.configuration = configuration;
  160. }
  161. /**
  162. * Gets the <code>ClassNormalizer</code> strategy.
  163. * This is used to determine the Class to be introspected
  164. * (the normalized Class).
  165. *
  166. * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected
  167. * for a given Object.
  168. * @deprecated 0.6 use getConfiguration().getClassNormalizer
  169. * @since 0.5
  170. */
  171. public ClassNormalizer getClassNormalizer() {
  172. return getConfiguration().getClassNormalizer();
  173. }
  174. /**
  175. * Sets the <code>ClassNormalizer</code> strategy.
  176. * This is used to determine the Class to be introspected
  177. * (the normalized Class).
  178. *
  179. * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine
  180. * the Class to be introspected for a given Object.
  181. * @deprecated 0.6 use getConfiguration().setClassNormalizer
  182. * @since 0.5
  183. *
  184. */
  185. public void setClassNormalizer(ClassNormalizer classNormalizer) {
  186. getConfiguration().setClassNormalizer(classNormalizer);
  187. }
  188. /**
  189. * Is <code>XMLBeanInfo</code> caching enabled?
  190. *
  191. * @deprecated 0.5 replaced by XMlBeanInfoRegistry
  192. * @return true if caching is enabled
  193. */
  194. public boolean isCachingEnabled() {
  195. return true;
  196. }
  197. /**
  198. * Set whether <code>XMLBeanInfo</code> caching should be enabled.
  199. *
  200. * @deprecated 0.5 replaced by XMlBeanInfoRegistry
  201. * @param cachingEnabled ignored
  202. */
  203. public void setCachingEnabled(boolean cachingEnabled) {
  204. //
  205. }
  206. /**
  207. * Should attributes (or elements) be used for primitive types.
  208. * @return true if primitive types will be mapped to attributes in the introspection
  209. * @deprecated 0.6 use getConfiguration().isAttributesForPrimitives
  210. */
  211. public boolean isAttributesForPrimitives() {
  212. return getConfiguration().isAttributesForPrimitives();
  213. }
  214. /**
  215. * Set whether attributes (or elements) should be used for primitive types.
  216. * @param attributesForPrimitives pass trus to map primitives to attributes,
  217. * pass false to map primitives to elements
  218. * @deprecated 0.6 use getConfiguration().setAttributesForPrimitives
  219. */
  220. public void setAttributesForPrimitives(boolean attributesForPrimitives) {
  221. getConfiguration().setAttributesForPrimitives(attributesForPrimitives);
  222. }
  223. /**
  224. * Should collections be wrapped in an extra element?
  225. *
  226. * @return whether we should we wrap collections in an extra element?
  227. * @deprecated 0.6 use getConfiguration().isWrapCollectionsInElement
  228. */
  229. public boolean isWrapCollectionsInElement() {
  230. return getConfiguration().isWrapCollectionsInElement();
  231. }
  232. /**
  233. * Sets whether we should we wrap collections in an extra element.
  234. *
  235. * @param wrapCollectionsInElement pass true if collections should be wrapped in a
  236. * parent element
  237. * @deprecated 0.6 use getConfiguration().setWrapCollectionsInElement
  238. */
  239. public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
  240. getConfiguration().setWrapCollectionsInElement(wrapCollectionsInElement);
  241. }
  242. /**
  243. * Get singular and plural matching strategy.
  244. *
  245. * @return the strategy used to detect matching singular and plural properties
  246. * @deprecated 0.6 use getConfiguration().getPluralStemmer
  247. */
  248. public PluralStemmer getPluralStemmer() {
  249. return getConfiguration().getPluralStemmer();
  250. }
  251. /**
  252. * Sets the strategy used to detect matching singular and plural properties
  253. *
  254. * @param pluralStemmer the PluralStemmer used to match singular and plural
  255. * @deprecated 0.6 use getConfiguration().setPluralStemmer
  256. */
  257. public void setPluralStemmer(PluralStemmer pluralStemmer) {
  258. getConfiguration().setPluralStemmer(pluralStemmer);
  259. }
  260. /**
  261. * Gets the name mapper strategy.
  262. *
  263. * @return the strategy used to convert bean type names into element names
  264. * @deprecated 0.5 getNameMapper is split up in
  265. * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
  266. */
  267. public NameMapper getNameMapper() {
  268. return getElementNameMapper();
  269. }
  270. /**
  271. * Sets the strategy used to convert bean type names into element names
  272. * @param nameMapper the NameMapper strategy to be used
  273. * @deprecated 0.5 setNameMapper is split up in
  274. * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
  275. */
  276. public void setNameMapper(NameMapper nameMapper) {
  277. setElementNameMapper(nameMapper);
  278. }
  279. /**
  280. * Gets the name mapping strategy used to convert bean names into elements.
  281. *
  282. * @return the strategy used to convert bean type names into element
  283. * names. If no element mapper is currently defined then a default one is created.
  284. * @deprecated 0.6 use getConfiguration().getElementNameMapper
  285. */
  286. public NameMapper getElementNameMapper() {
  287. return getConfiguration().getElementNameMapper();
  288. }
  289. /**
  290. * Sets the strategy used to convert bean type names into element names
  291. * @param nameMapper the NameMapper to use for the conversion
  292. * @deprecated 0.6 use getConfiguration().setElementNameMapper
  293. */
  294. public void setElementNameMapper(NameMapper nameMapper) {
  295. getConfiguration().setElementNameMapper( nameMapper );
  296. }
  297. /**
  298. * Gets the name mapping strategy used to convert bean names into attributes.
  299. *
  300. * @return the strategy used to convert bean type names into attribute
  301. * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
  302. * @deprecated 0.6 getConfiguration().getAttributeNameMapper
  303. */
  304. public NameMapper getAttributeNameMapper() {
  305. return getConfiguration().getAttributeNameMapper();
  306. }
  307. /**
  308. * Sets the strategy used to convert bean type names into attribute names
  309. * @param nameMapper the NameMapper to use for the convertion
  310. * @deprecated 0.6 use getConfiguration().setAttributeNameMapper
  311. */
  312. public void setAttributeNameMapper(NameMapper nameMapper) {
  313. getConfiguration().setAttributeNameMapper( nameMapper );
  314. }
  315. /**
  316. * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
  317. * By default it will be false.
  318. *
  319. * @return boolean if the beanInfoSearchPath should be used.
  320. * @deprecated 0.6 use getConfiguration().useBeanInfoSearchPath
  321. */
  322. public boolean useBeanInfoSearchPath() {
  323. return getConfiguration().useBeanInfoSearchPath();
  324. }
  325. /**
  326. * Specifies if you want to use the beanInfoSearchPath
  327. * @see java.beans.Introspector for more details
  328. * @param useBeanInfoSearchPath
  329. * @deprecated 0.6 use getConfiguration().setUseBeanInfoSearchPath
  330. */
  331. public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
  332. getConfiguration().setUseBeanInfoSearchPath( useBeanInfoSearchPath );
  333. }
  334. // Methods
  335. //-------------------------------------------------------------------------
  336. /**
  337. * Flush existing cached <code>XMLBeanInfo</code>'s.
  338. *
  339. * @deprecated 0.5 use flushable registry instead
  340. */
  341. public void flushCache() {}
  342. /** Create a standard <code>XMLBeanInfo</code> by introspection
  343. * The actual introspection depends only on the <code>BeanInfo</code>
  344. * associated with the bean.
  345. *
  346. * @param bean introspect this bean
  347. * @return XMLBeanInfo describing bean-xml mapping
  348. * @throws IntrospectionException when the bean introspection fails
  349. */
  350. public XMLBeanInfo introspect(Object bean) throws IntrospectionException {
  351. if (getLog().isDebugEnabled()) {
  352. getLog().debug( "Introspecting..." );
  353. getLog().debug(bean);
  354. }
  355. if ( bean instanceof DynaBean ) {
  356. // allow DynaBean implementations to be overridden by .betwixt files
  357. XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() );
  358. if (xmlBeanInfo != null) {
  359. return xmlBeanInfo;
  360. }
  361. // this is DynaBean use the DynaClass for introspection
  362. return introspect( ((DynaBean) bean).getDynaClass() );
  363. } else {
  364. // normal bean so normal introspection
  365. Class normalClass = getClassNormalizer().getNormalizedClass( bean );
  366. return introspect( normalClass );
  367. }
  368. }
  369. /**
  370. * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean.
  371. * Customizing DynaBeans using betwixt is not supported.
  372. *
  373. * @param dynaClass the DynaBean to introspect
  374. *
  375. * @return XMLBeanInfo for the DynaClass
  376. */
  377. public XMLBeanInfo introspect(DynaClass dynaClass) {
  378. // for now this method does not do much, since XMLBeanInfoRegistry cannot
  379. // use a DynaClass as a key
  380. // TODO: add caching for DynaClass XMLBeanInfo
  381. // need to work out if this is possible
  382. // this line allows subclasses to change creation strategy
  383. XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass );
  384. // populate the created info with
  385. DynaClassBeanType beanClass = new DynaClassBeanType( dynaClass );
  386. populate( xmlInfo, beanClass );
  387. return xmlInfo;
  388. }
  389. /** Create a standard <code>XMLBeanInfo</code> by introspection.
  390. * The actual introspection depends only on the <code>BeanInfo</code>
  391. * associated with the bean.
  392. *
  393. * @param aClass introspect this class
  394. * @return XMLBeanInfo describing bean-xml mapping
  395. * @throws IntrospectionException when the bean introspection fails
  396. */
  397. public XMLBeanInfo introspect(Class aClass) throws IntrospectionException {
  398. // we first reset the beaninfo searchpath.
  399. String[] searchPath = null;
  400. if ( !getConfiguration().useBeanInfoSearchPath() ) {
  401. searchPath = Introspector.getBeanInfoSearchPath();
  402. Introspector.setBeanInfoSearchPath(new String[] { });
  403. }
  404. XMLBeanInfo xmlInfo = registry.get( aClass );
  405. if ( xmlInfo == null ) {
  406. // lets see if we can find an XML descriptor first
  407. if ( getLog().isDebugEnabled() ) {
  408. getLog().debug( "Attempting to lookup an XML descriptor for class: " + aClass );
  409. }
  410. xmlInfo = findByXMLDescriptor( aClass );
  411. if ( xmlInfo == null ) {
  412. BeanInfo info = Introspector.getBeanInfo( aClass );
  413. xmlInfo = introspect( info );
  414. }
  415. if ( xmlInfo != null ) {
  416. registry.put( aClass, xmlInfo );
  417. }
  418. } else {
  419. getLog().trace( "Used cached XMLBeanInfo." );
  420. }
  421. if ( getLog().isTraceEnabled() ) {
  422. getLog().trace( xmlInfo );
  423. }
  424. if ( !getConfiguration().useBeanInfoSearchPath() ) {
  425. // we restore the beaninfo searchpath.
  426. Introspector.setBeanInfoSearchPath( searchPath );
  427. }
  428. return xmlInfo;
  429. }
  430. /** Create a standard <code>XMLBeanInfo</code> by introspection.
  431. * The actual introspection depends only on the <code>BeanInfo</code>
  432. * associated with the bean.
  433. *
  434. * @param beanInfo the BeanInfo the xml-bean mapping is based on
  435. * @return XMLBeanInfo describing bean-xml mapping
  436. * @throws IntrospectionException when the bean introspection fails
  437. */
  438. public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException {
  439. XMLBeanInfo xmlBeanInfo = createXMLBeanInfo( beanInfo );
  440. populate( xmlBeanInfo, new JavaBeanType( beanInfo ) );
  441. return xmlBeanInfo;
  442. }
  443. /**
  444. * Populates the given <code>XMLBeanInfo</code> based on the given type of bean.
  445. *
  446. * @param xmlBeanInfo populate this, not null
  447. * @param bean the type definition for the bean, not null
  448. */
  449. private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) {
  450. String name = bean.getBeanName();
  451. ElementDescriptor elementDescriptor = new ElementDescriptor();
  452. elementDescriptor.setLocalName(
  453. getElementNameMapper().mapTypeToElementName( name ) );
  454. elementDescriptor.setPropertyType( bean.getElementType() );
  455. if (getLog().isTraceEnabled()) {
  456. getLog().trace("Populating:" + bean);
  457. }
  458. // add default string value for primitive types
  459. if ( bean.isPrimitiveType() ) {
  460. getLog().trace("Bean is primitive");
  461. elementDescriptor.setTextExpression( StringExpression.getInstance() );
  462. } else if ( bean.isLoopType() ) {
  463. getLog().trace("Bean is loop");
  464. ElementDescriptor loopDescriptor = new ElementDescriptor();
  465. loopDescriptor.setContextExpression(
  466. new IteratorExpression( EmptyExpression.getInstance() )
  467. );
  468. if ( bean.isMapType() ) {
  469. loopDescriptor.setQualifiedName( "entry" );
  470. }
  471. elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
  472. } else {
  473. getLog().trace("Bean is standard type");
  474. List elements = new ArrayList();
  475. List attributes = new ArrayList();
  476. List contents = new ArrayList();
  477. addProperties( bean.getProperties(), elements, attributes, contents );
  478. int size = elements.size();
  479. if ( size > 0 ) {
  480. ElementDescriptor[] descriptors = new ElementDescriptor[size];
  481. elements.toArray( descriptors );
  482. elementDescriptor.setElementDescriptors( descriptors );
  483. }
  484. size = attributes.size();
  485. if ( size > 0 ) {
  486. AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
  487. attributes.toArray( descriptors );
  488. elementDescriptor.setAttributeDescriptors( descriptors );
  489. }
  490. size = contents.size();
  491. if ( size > 0 ) {
  492. if ( size > 0 ) {
  493. Descriptor[] descriptors = new Descriptor[size];
  494. contents.toArray( descriptors );
  495. elementDescriptor.setContentDescriptors( descriptors );
  496. }
  497. }
  498. }
  499. xmlBeanInfo.setElementDescriptor( elementDescriptor );
  500. // default any addProperty() methods
  501. defaultAddMethods( elementDescriptor, bean.getElementType() );
  502. if (getLog().isTraceEnabled()) {
  503. getLog().trace("Populated descriptor:");
  504. getLog().trace(elementDescriptor);
  505. }
  506. }
  507. /**
  508. * Creates XMLBeanInfo for the given DynaClass.
  509. *
  510. * @param dynaClass the class describing a DynaBean
  511. *
  512. * @return XMLBeanInfo that describes the properties of the given
  513. * DynaClass
  514. */
  515. protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) {
  516. // XXX is the chosen class right?
  517. XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass());
  518. return beanInfo;
  519. }
  520. /**
  521. * Create a XML descriptor from a bean one.
  522. * Go through and work out whether it's a loop property, a primitive or a standard.
  523. * The class property is ignored.
  524. *
  525. * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
  526. * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
  527. * @return a correctly configured <code>NodeDescriptor</code> for the property
  528. * @throws IntrospectionException when bean introspection fails
  529. * @deprecated 0.5 use {@link #createXMLDescriptor}.
  530. */
  531. public Descriptor createDescriptor(
  532. PropertyDescriptor propertyDescriptor,
  533. boolean useAttributesForPrimitives
  534. ) throws IntrospectionException {
  535. return createXMLDescriptor( new BeanProperty( propertyDescriptor ) );
  536. }
  537. /**
  538. * Create a XML descriptor from a bean one.
  539. * Go through and work out whether it's a loop property, a primitive or a standard.
  540. * The class property is ignored.
  541. *
  542. * @param beanProperty the BeanProperty specifying the property
  543. * @return a correctly configured <code>NodeDescriptor</code> for the property
  544. * @since 0.5
  545. */
  546. public Descriptor createXMLDescriptor( BeanProperty beanProperty ) {
  547. return beanProperty.createXMLDescriptor( configuration );
  548. }
  549. /**
  550. * Add any addPropety(PropertyType) methods as Updaters
  551. * which are often used for 1-N relationships in beans.
  552. * <br>
  553. * The tricky part here is finding which ElementDescriptor corresponds
  554. * to the method. e.g. a property 'items' might have an Element descriptor
  555. * which the method addItem() should match to.
  556. * <br>
  557. * So the algorithm we'll use
  558. * by default is to take the decapitalized name of the property being added
  559. * and find the first ElementDescriptor that matches the property starting with
  560. * the string. This should work for most use cases.
  561. * e.g. addChild() would match the children property.
  562. * <br>
  563. * TODO this probably needs refactoring. It probably belongs in the bean wrapper
  564. * (so that it'll work properly with dyna-beans) and so that the operations can
  565. * be optimized by caching. Multiple hash maps are created and getMethods is
  566. * called multiple times. This is relatively expensive and so it'd be better
  567. * to push into a proper class and cache.
  568. * <br>
  569. * TODO this probably does work properly with DynaBeans: need to push
  570. * implementation into an class and expose it on BeanType.
  571. *
  572. * @param introspector use this <code>XMLIntrospector</code> for introspection
  573. * @param rootDescriptor add defaults to this descriptor
  574. * @param beanClass the <code>Class</code> to which descriptor corresponds
  575. */
  576. public void defaultAddMethods(
  577. ElementDescriptor rootDescriptor,
  578. Class beanClass ) {
  579. // lets iterate over all methods looking for one of the form
  580. // add*(PropertyType)
  581. if ( beanClass != null ) {
  582. ArrayList singleParameterAdders = new ArrayList();
  583. ArrayList twinParameterAdders = new ArrayList();
  584. Method[] methods = beanClass.getMethods();
  585. for ( int i = 0, size = methods.length; i < size; i++ ) {
  586. Method method = methods[i];
  587. String name = method.getName();
  588. if ( name.startsWith( "add" )) {
  589. // TODO: should we filter out non-void returning methods?
  590. // some beans will return something as a helper
  591. Class[] types = method.getParameterTypes();
  592. if ( types != null) {
  593. if ( getLog().isTraceEnabled() ) {
  594. getLog().trace("Searching for match for " + method);
  595. }
  596. switch (types.length)
  597. {
  598. case 1:
  599. singleParameterAdders.add(method);
  600. break;
  601. case 2:
  602. twinParameterAdders.add(method);
  603. break;
  604. default:
  605. // ignore
  606. break;
  607. }
  608. }
  609. }
  610. }
  611. Map elementsByPropertyName = makeElementDescriptorMap( rootDescriptor );
  612. for (Iterator it=singleParameterAdders.iterator();it.hasNext();) {
  613. Method singleParameterAdder = (Method) it.next();
  614. setIteratorAdder(elementsByPropertyName, singleParameterAdder);
  615. }
  616. for (Iterator it=twinParameterAdders.iterator();it.hasNext();) {
  617. Method twinParameterAdder = (Method) it.next();
  618. setMapAdder(elementsByPropertyName, twinParameterAdder);
  619. }
  620. }
  621. }
  622. /**
  623. * Sets the adder method where the corresponding property is an iterator
  624. * @param rootDescriptor
  625. * @param singleParameterAdder
  626. */
  627. private void setIteratorAdder(
  628. Map elementsByPropertyName,
  629. Method singleParameterAdderMethod) {
  630. String adderName = singleParameterAdderMethod.getName();
  631. String propertyName = Introspector.decapitalize(adderName.substring(3));
  632. ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
  633. if (matchingDescriptor != null) {
  634. //TODO defensive code: probably should check descriptor type
  635. Class singularType = singleParameterAdderMethod.getParameterTypes()[0];
  636. if (getLog().isTraceEnabled()) {
  637. getLog().trace(adderName + "->" + propertyName);
  638. }
  639. // this may match a standard collection or iteration
  640. getLog().trace("Matching collection or iteration");
  641. matchingDescriptor.setUpdater( new MethodUpdater( singleParameterAdderMethod ) );
  642. matchingDescriptor.setSingularPropertyType( singularType );
  643. matchingDescriptor.setHollow(!isPrimitiveType(singularType));
  644. String localName = matchingDescriptor.getLocalName();
  645. if ( localName == null || localName.length() == 0 ) {
  646. matchingDescriptor.setLocalName(
  647. getElementNameMapper()
  648. .mapTypeToElementName( propertyName ) );
  649. }
  650. if ( getLog().isDebugEnabled() ) {
  651. getLog().debug( "!! " + singleParameterAdderMethod);
  652. getLog().debug( "!! " + singularType);
  653. }
  654. }
  655. }
  656. /**
  657. * Sets the adder where the corresponding property type is an map
  658. * @param rootDescriptor
  659. * @param singleParameterAdder
  660. */
  661. private void setMapAdder(
  662. Map elementsByPropertyName,
  663. Method twinParameterAdderMethod) {
  664. String adderName = twinParameterAdderMethod.getName();
  665. String propertyName = Introspector.decapitalize(adderName.substring(3));
  666. ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
  667. if ( matchingDescriptor != null
  668. && Map.class.isAssignableFrom( matchingDescriptor.getPropertyType() )) {
  669. // this may match a map
  670. getLog().trace("Matching map");
  671. ElementDescriptor[] children
  672. = matchingDescriptor.getElementDescriptors();
  673. // see if the descriptor's been set up properly
  674. if ( children.length == 0 ) {
  675. getLog().info(
  676. "'entry' descriptor is missing for map. "
  677. + "Updaters cannot be set");
  678. } else {
  679. Class[] types = twinParameterAdderMethod.getParameterTypes();
  680. Class keyType = types[0];
  681. Class valueType = types[1];
  682. // loop through children
  683. // adding updaters for key and value
  684. MapEntryAdder adder = new MapEntryAdder(twinParameterAdderMethod);
  685. for (
  686. int n=0,
  687. noOfGrandChildren = children.length;
  688. n < noOfGrandChildren;
  689. n++ ) {
  690. if ( "key".equals( children[n].getLocalName() ) ) {
  691. children[n].setUpdater( adder.getKeyUpdater() );
  692. children[n].setSingularPropertyType( keyType );
  693. if (children[n].getPropertyType() == null) {
  694. children[n].setPropertyType( valueType );
  695. }
  696. if ( isPrimitiveType(keyType) ) {
  697. children[n].setHollow(false);
  698. }
  699. if ( getLog().isTraceEnabled() ) {
  700. getLog().trace( "Key descriptor: " + children[n]);
  701. }
  702. } else if ( "value".equals( children[n].getLocalName() ) ) {
  703. children[n].setUpdater( adder.getValueUpdater() );
  704. children[n].setSingularPropertyType( valueType );
  705. if (children[n].getPropertyType() == null) {
  706. children[n].setPropertyType( valueType );
  707. }
  708. if ( isPrimitiveType( valueType) ) {
  709. children[n].setHollow(false);
  710. }
  711. if ( isLoopType( valueType )) {
  712. // need to attach a hollow descriptor
  713. // don't know the element name
  714. // so use null name (to match anything)
  715. ElementDescriptor loopDescriptor = new ElementDescriptor();
  716. loopDescriptor.setHollow(true);
  717. loopDescriptor.setSingularPropertyType( valueType );
  718. loopDescriptor.setPropertyType( valueType );
  719. children[n].addElementDescriptor(loopDescriptor);
  720. }
  721. if ( getLog().isTraceEnabled() ) {
  722. getLog().trace( "Value descriptor: " + children[n]);
  723. }
  724. }
  725. }
  726. }
  727. }
  728. }
  729. /**
  730. * Gets an ElementDescriptor for the property matching the adder
  731. * @param adderName
  732. * @param rootDescriptor
  733. * @return
  734. */
  735. private ElementDescriptor getMatchForAdder(
  736. String propertyName,
  737. Map elementsByPropertyName) {
  738. ElementDescriptor matchingDescriptor = null;
  739. if (propertyName.length() > 0) {
  740. if ( getLog().isTraceEnabled() ) {
  741. getLog().trace( "findPluralDescriptor( " + propertyName
  742. + " ):root property name=" + propertyName );
  743. }
  744. PluralStemmer stemmer = getPluralStemmer();
  745. matchingDescriptor = stemmer.findPluralDescriptor( propertyName, elementsByPropertyName );
  746. if ( getLog().isTraceEnabled() ) {
  747. getLog().trace(
  748. "findPluralDescriptor( " + propertyName
  749. + " ):ElementDescriptor=" + matchingDescriptor );
  750. }
  751. }
  752. return matchingDescriptor;
  753. }
  754. // Implementation methods
  755. //-------------------------------------------------------------------------
  756. /**
  757. * Creates a map where the keys are the property names and the values are the ElementDescriptors
  758. */
  759. private Map makeElementDescriptorMap( ElementDescriptor rootDescriptor ) {
  760. Map result = new HashMap();
  761. String rootPropertyName = rootDescriptor.getPropertyName();
  762. if (rootPropertyName != null) {
  763. result.put(rootPropertyName, rootDescriptor);
  764. }
  765. makeElementDescriptorMap( rootDescriptor, result );
  766. return result;
  767. }
  768. /**
  769. * Creates a map where the keys are the property names and the values are the ElementDescriptors
  770. *
  771. * @param rootDescriptor the values of the maps are the children of this
  772. * <code>ElementDescriptor</code> index by their property names
  773. * @param map the map to which the elements will be added
  774. */
  775. private void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) {
  776. ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
  777. if ( children != null ) {
  778. for ( int i = 0, size = children.length; i < size; i++ ) {
  779. ElementDescriptor child = children[i];
  780. String propertyName = child.getPropertyName();
  781. if ( propertyName != null ) {
  782. map.put( propertyName, child );
  783. }
  784. makeElementDescriptorMap( child, map );
  785. }
  786. }
  787. }
  788. /**
  789. * A Factory method to lazily create a new strategy
  790. * to detect matching singular and plural properties.
  791. *
  792. * @return new defualt PluralStemmer implementation
  793. * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
  794. * Those who need to vary this should subclass that class instead
  795. */
  796. protected PluralStemmer createPluralStemmer() {
  797. return new DefaultPluralStemmer();
  798. }
  799. /**
  800. * A Factory method to lazily create a strategy
  801. * used to convert bean type names into element names.
  802. *
  803. * @return new default NameMapper implementation
  804. * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
  805. * Those who need to vary this should subclass that class instead
  806. */
  807. protected NameMapper createNameMapper() {
  808. return new DefaultNameMapper();
  809. }
  810. /**
  811. * Attempt to lookup the XML descriptor for the given class using the
  812. * classname + ".betwixt" using the same ClassLoader used to load the class
  813. * or return null if it could not be loaded
  814. *
  815. * @param aClass digester .betwixt file for this class
  816. * @return XMLBeanInfo digested from the .betwixt file if one can be found.
  817. * Otherwise null.
  818. */
  819. protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
  820. // trim the package name
  821. String name = aClass.getName();
  822. int idx = name.lastIndexOf( '.' );
  823. if ( idx >= 0 ) {
  824. name = name.substring( idx + 1 );
  825. }
  826. name += ".betwixt";
  827. URL url = aClass.getResource( name );
  828. if ( url != null ) {
  829. try {
  830. String urlText = url.toString();
  831. if ( getLog().isDebugEnabled( )) {
  832. getLog().debug( "Parsing Betwixt XML descriptor: " + urlText );
  833. }
  834. // synchronized method so this digester is only used by
  835. // one thread at once
  836. if ( digester == null ) {
  837. digester = new XMLBeanInfoDigester();
  838. digester.setXMLIntrospector( this );
  839. }
  840. digester.setBeanClass( aClass );
  841. return (XMLBeanInfo) digester.parse( urlText );
  842. } catch (Exception e) {
  843. getLog().warn( "Caught exception trying to parse: " + name, e );
  844. }
  845. }
  846. if ( getLog().isTraceEnabled() ) {
  847. getLog().trace( "Could not find betwixt file " + name );
  848. }
  849. return null;
  850. }
  851. /**
  852. * Loop through properties and process each one
  853. *
  854. * @param beanInfo the BeanInfo whose properties will be processed
  855. * @param elements ElementDescriptor list to which elements will be added
  856. * @param attributes AttributeDescriptor list to which attributes will be added
  857. * @param contents Descriptor list to which mixed content will be added
  858. * @throws IntrospectionException if the bean introspection fails
  859. * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
  860. */
  861. protected void addProperties(
  862. BeanInfo beanInfo,
  863. List elements,
  864. List attributes,
  865. List contents)
  866. throws
  867. IntrospectionException {
  868. PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
  869. if ( descriptors != null ) {
  870. for ( int i = 0, size = descriptors.length; i < size; i++ ) {
  871. addProperty(beanInfo, descriptors[i], elements, attributes, contents);
  872. }
  873. }
  874. if (getLog().isTraceEnabled()) {
  875. getLog().trace(elements);
  876. getLog().trace(attributes);
  877. getLog().trace(contents);
  878. }
  879. }
  880. /**
  881. * Loop through properties and process each one
  882. *
  883. * @param beanProperties the properties to be processed
  884. * @param elements ElementDescriptor list to which elements will be added
  885. * @param attributes AttributeDescriptor list to which attributes will be added
  886. * @param contents Descriptor list to which mixed content will be added
  887. * @since 0.5
  888. */
  889. protected void addProperties(
  890. BeanProperty[] beanProperties,
  891. List elements,
  892. List attributes,
  893. List contents) {
  894. if ( beanProperties != null ) {
  895. if (getLog().isTraceEnabled()) {
  896. getLog().trace(beanProperties.length + " properties to be added");
  897. }
  898. for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
  899. addProperty(beanProperties[i], elements, attributes, contents);
  900. }
  901. }
  902. if (getLog().isTraceEnabled()) {
  903. getLog().trace("After properties have been added (elements, attributes, contents):");
  904. getLog().trace(elements);
  905. getLog().trace(attributes);
  906. getLog().trace(contents);
  907. }
  908. }
  909. /**
  910. * Process a property.
  911. * Go through and work out whether it's a loop property, a primitive or a standard.
  912. * The class property is ignored.
  913. *
  914. * @param beanInfo the BeanInfo whose property is being processed
  915. * @param propertyDescriptor the PropertyDescriptor to process
  916. * @param elements ElementDescriptor list to which elements will be added
  917. * @param attributes AttributeDescriptor list to which attributes will be added
  918. * @param contents Descriptor list to which mixed content will be added
  919. * @throws IntrospectionException if the bean introspection fails
  920. * @deprecated 0.5 BeanInfo is no longer required.
  921. * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
  922. */
  923. protected void addProperty(
  924. BeanInfo beanInfo,
  925. PropertyDescriptor propertyDescriptor,
  926. List elements,
  927. List attributes,
  928. List contents)
  929. throws
  930. IntrospectionException {
  931. addProperty( propertyDescriptor, elements, attributes, contents);
  932. }
  933. /**
  934. * Process a property.
  935. * Go through and work out whether it's a loop property, a primitive or a standard.
  936. * The class property is ignored.
  937. *
  938. * @param propertyDescriptor the PropertyDescriptor to process
  939. * @param elements ElementDescriptor list to which elements will be added
  940. * @param attributes AttributeDescriptor list to which attributes will be added
  941. * @param contents Descriptor list to which mixed content will be added
  942. * @throws IntrospectionException if the bean introspection fails
  943. * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
  944. */
  945. protected void addProperty(
  946. PropertyDescriptor propertyDescriptor,
  947. List elements,
  948. List attributes,
  949. List contents)
  950. throws
  951. IntrospectionException {
  952. addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
  953. }
  954. /**
  955. * Process a property.
  956. * Go through and work out whether it's a loop property, a primitive or a standard.
  957. * The class property is ignored.
  958. *
  959. * @param beanProperty the bean property to process
  960. * @param elements ElementDescriptor list to which elements will be added
  961. * @param attributes AttributeDescriptor list to which attributes will be added
  962. * @param contents Descriptor list to which mixed content will be added
  963. * @since 0.5
  964. */
  965. protected void addProperty(
  966. BeanProperty beanProperty,
  967. List elements,
  968. List attributes,
  969. List contents) {
  970. Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
  971. if (nodeDescriptor == null) {
  972. return;
  973. }
  974. if (nodeDescriptor instanceof ElementDescriptor) {
  975. elements.add(nodeDescriptor);
  976. } else if (nodeDescriptor instanceof AttributeDescriptor) {
  977. attributes.add(nodeDescriptor);
  978. } else {
  979. contents.add(nodeDescriptor);
  980. }
  981. }
  982. /**
  983. * Loop through properties and process each one
  984. *
  985. * @param beanInfo the BeanInfo whose properties will be processed
  986. * @param elements ElementDescriptor list to which elements will be added
  987. * @param attributes AttributeDescriptor list to which attributes will be added
  988. * @throws IntrospectionException if the bean introspection fails
  989. * @deprecated 0.5 this method does not support mixed content.
  990. * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
  991. */
  992. protected void addProperties(
  993. BeanInfo beanInfo,
  994. List elements,
  995. List attributes)
  996. throws
  997. IntrospectionException {
  998. PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
  999. if ( descriptors != null ) {
  1000. for ( int i = 0, size = descriptors.length; i < size; i++ ) {
  1001. addProperty(beanInfo, descriptors[i], elements, attributes);
  1002. }
  1003. }
  1004. if (getLog().isTraceEnabled()) {
  1005. getLog().trace(elements);
  1006. getLog().trace(attributes);
  1007. }
  1008. }
  1009. /**
  1010. * Process a property.
  1011. * Go through and work out whether it's a loop property, a primitive or a standard.
  1012. * The class property is ignored.
  1013. *
  1014. * @param beanInfo the BeanInfo whose property is being processed
  1015. * @param propertyDescriptor the PropertyDescriptor to process
  1016. * @param elements ElementDescriptor list to which elements will be added
  1017. * @param attributes AttributeDescriptor list to which attributes will be added
  1018. * @throws IntrospectionException if the bean introspection fails
  1019. * @deprecated 0.5 this method does not support mixed content.
  1020. * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
  1021. */
  1022. protected void addProperty(
  1023. BeanInfo beanInfo,
  1024. PropertyDescriptor propertyDescriptor,
  1025. List elements,
  1026. List attributes)
  1027. throws
  1028. IntrospectionException {
  1029. NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
  1030. .createDescriptor(propertyDescriptor,
  1031. isAttributesForPrimitives(),
  1032. this);
  1033. if (nodeDescriptor == null) {
  1034. return;
  1035. }
  1036. if (nodeDescriptor instanceof ElementDescriptor) {
  1037. elements.add(nodeDescriptor);
  1038. } else {
  1039. attributes.add(nodeDescriptor);
  1040. }
  1041. }
  1042. /**
  1043. * Factory method to create XMLBeanInfo instances
  1044. *
  1045. * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
  1046. * @return XMLBeanInfo describing the bean-xml mapping
  1047. */
  1048. protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
  1049. XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
  1050. return xmlBeanInfo;
  1051. }
  1052. /**
  1053. * Is this class a loop?
  1054. *
  1055. * @param type the Class to test
  1056. * @return true if the type is a loop type
  1057. */
  1058. public boolean isLoopType(Class type) {
  1059. return XMLIntrospectorHelper.isLoopType(type);
  1060. }
  1061. /**
  1062. * Is this class a primitive?
  1063. * TODO: this method will probably be deprecated when primitive types
  1064. * are subsumed into the simple type concept
  1065. * @param type the Class to test
  1066. * @return true for primitive types
  1067. */
  1068. public boolean isPrimitiveType(Class type) {
  1069. TypeBindingStrategy.BindingType bindingType
  1070. = configuration.getTypeBindingStrategy().bindingType( type ) ;
  1071. boolean result = (bindingType.equals(TypeBindingStrategy.BindingType.PRIMITIVE));
  1072. return result;
  1073. }
  1074. /** Some type of pseudo-bean */
  1075. private abstract class BeanType {
  1076. /**
  1077. * Gets the name for this bean type
  1078. * @return the bean type name, not null
  1079. */
  1080. public abstract String getBeanName();
  1081. /**
  1082. * Gets the type to be used by the associated element
  1083. * @return a Class that is the type not null
  1084. */
  1085. public abstract Class getElementType();
  1086. /**
  1087. * Is this type a primitive?
  1088. * @return true if this type should be treated by betwixt as a primitive
  1089. */
  1090. public abstract boolean isPrimitiveType();
  1091. /**
  1092. * is this type a map?
  1093. * @return true this should be treated as a map.
  1094. */
  1095. public abstract boolean isMapType();
  1096. /**
  1097. * Is this type a loop?
  1098. * @return true if this should be treated as a loop
  1099. */
  1100. public abstract boolean isLoopType();
  1101. /**
  1102. * Gets the properties associated with this bean.
  1103. * @return the BeanProperty's, not null
  1104. */
  1105. public abstract BeanProperty[] getProperties();
  1106. /**
  1107. * Create string representation
  1108. * @return something useful for logging
  1109. */
  1110. public String toString() {
  1111. return "Bean[name=" + getBeanName() + ", type=" + getElementType();
  1112. }
  1113. }
  1114. /** Supports standard Java Beans */
  1115. private class JavaBeanType extends BeanType {
  1116. /** Introspected bean */
  1117. private BeanInfo beanInfo;
  1118. /** Bean class */
  1119. private Class beanClass;
  1120. /** Bean name */
  1121. private String name;
  1122. /** Bean properties */
  1123. private BeanProperty[] properties;
  1124. /**
  1125. * Constructs a BeanType for a standard Java Bean
  1126. * @param beanInfo the BeanInfo describing the standard Java Bean, not null
  1127. */
  1128. public JavaBeanType(BeanInfo beanInfo) {
  1129. this.beanInfo = beanInfo;
  1130. BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
  1131. beanClass = beanDescriptor.getBeanClass();
  1132. name = beanDescriptor.getName();
  1133. // Array's contain a bad character
  1134. if (beanClass.isArray()) {
  1135. // called all array's Array
  1136. name = "Array";
  1137. }
  1138. }
  1139. /** @see BeanType #getElementType */
  1140. public Class getElementType() {
  1141. return beanClass;
  1142. }
  1143. /** @see BeanType#getBeanName */
  1144. public String getBeanName() {
  1145. return name;
  1146. }
  1147. /** @see BeanType#isPrimitiveType */
  1148. public boolean isPrimitiveType() {
  1149. return XMLIntrospector.this.isPrimitiveType( beanClass );
  1150. }
  1151. /** @see BeanType#isLoopType */
  1152. public boolean isLoopType() {
  1153. return XMLIntrospectorHelper.isLoopType( beanClass );
  1154. }
  1155. /** @see BeanType#isMapType */
  1156. public boolean isMapType() {
  1157. return Map.class.isAssignableFrom( beanClass );
  1158. }
  1159. /** @see BeanType#getProperties */
  1160. public BeanProperty[] getProperties() {
  1161. // lazy creation
  1162. if ( properties == null ) {
  1163. ArrayList propertyDescriptors = new ArrayList();
  1164. // add base bean info
  1165. PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
  1166. if ( descriptors != null ) {
  1167. for (int i=0, size=descriptors.length; i<size; i++) {
  1168. propertyDescriptors.add( descriptors[i] );
  1169. }
  1170. }
  1171. // add properties from additional bean infos
  1172. BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
  1173. if ( additionals != null ) {
  1174. for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
  1175. BeanInfo additionalInfo = additionals[i];
  1176. descriptors = beanInfo.getPropertyDescriptors();
  1177. if ( descriptors != null ) {
  1178. for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
  1179. propertyDescriptors.add( descriptors[j] );
  1180. }
  1181. }
  1182. }
  1183. }
  1184. // what happens when size is zero?
  1185. properties = new BeanProperty[ propertyDescriptors.size() ];
  1186. int count = 0;
  1187. for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
  1188. PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
  1189. properties[count] = new BeanProperty( propertyDescriptor );
  1190. }
  1191. }
  1192. return properties;
  1193. }
  1194. }
  1195. /** Implementation for DynaClasses */
  1196. private class DynaClassBeanType extends BeanType {
  1197. /** BeanType for this DynaClass */
  1198. private DynaClass dynaClass;
  1199. /** Properties extracted in constuctor */
  1200. private BeanProperty[] properties;
  1201. /**
  1202. * Constructs a BeanType for a DynaClass
  1203. * @param dynaClass not null
  1204. */
  1205. public DynaClassBeanType(DynaClass dynaClass) {
  1206. this.dynaClass = dynaClass;
  1207. DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
  1208. properties = new BeanProperty[dynaProperties.length];
  1209. for (int i=0, size=dynaProperties.length; i<size; i++) {
  1210. properties[i] = new BeanProperty(dynaProperties[i]);
  1211. }
  1212. }
  1213. /** @see BeanType#getBeanName */
  1214. public String getBeanName() {
  1215. return dynaClass.getName();
  1216. }
  1217. /** @see BeanType#getElementType */
  1218. public Class getElementType() {
  1219. return DynaClass.class;
  1220. }
  1221. /** @see BeanType#isPrimitiveType */
  1222. public boolean isPrimitiveType() {
  1223. return false;
  1224. }
  1225. /** @see BeanType#isMapType */
  1226. public boolean isMapType() {
  1227. return false;
  1228. }
  1229. /** @see BeanType#isLoopType */
  1230. public boolean isLoopType() {
  1231. return false;
  1232. }
  1233. /** @see BeanType#getProperties */
  1234. public BeanProperty[] getProperties() {
  1235. return properties;
  1236. }
  1237. }
  1238. }