1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation.
  3. *
  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. package org.apache.commons.betwixt;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
  20. import org.apache.commons.betwixt.expression.Expression;
  21. /** <p><code>ElementDescriptor</code> describes the XML elements
  22. * to be created for a bean instance.</p>
  23. *
  24. * <p> It contains <code>AttributeDescriptor</code>'s for all it's attributes
  25. * and <code>ElementDescriptor</code>'s for it's child elements.
  26. *
  27. * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
  28. * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
  29. */
  30. public class ElementDescriptor extends NodeDescriptor {
  31. /**
  32. * Descriptors for attributes this element contains.
  33. * <strong>Note:</strong> Constructed lazily on demand from a List.
  34. * {@link #getAttributeDescriptor()} should be called rather than accessing this
  35. * field directly.
  36. */
  37. private AttributeDescriptor[] attributeDescriptors;
  38. /**
  39. * Descriptors for child elements.
  40. * <strong>Note:</strong> Constructed lazily on demand from a List.
  41. * {@link #getElementDescriptor()} should be called rather than accessing this
  42. * field directly.
  43. */
  44. private ElementDescriptor[] elementDescriptors;
  45. /**
  46. * Descriptors for child content.
  47. * <strong>Note:</strong> Constructed lazily on demand from a List.
  48. * {@link #getContentDescriptor()} should be called rather than accessing this
  49. * field directly.
  50. */
  51. private Descriptor[] contentDescriptors;
  52. /**
  53. * The List used on construction. It will be GC'd
  54. * after initilization and the array is lazily constructed
  55. */
  56. private List attributeList;
  57. /**
  58. * The List used on construction. It will be GC'd
  59. * after initilization and the array is lazily constructed
  60. */
  61. private List elementList;
  62. /**
  63. * The list used o construct array. It will be GC'd after
  64. * initialization when the array is lazily constructed.
  65. */
  66. private List contentList;
  67. /** the expression used to evaluate the new context of this node
  68. * or null if the same context is to be used */
  69. private Expression contextExpression;
  70. /** Whether this element refers to a primitive type (or property of a parent object) */
  71. private boolean primitiveType;
  72. /**
  73. * Is this element hollow?
  74. * In other words, is this descriptor a place holder indicating the name
  75. * and update for a root ElementDescriptor for this type obtained by introspection
  76. * TODO: this would probably be better modeled as a separate subclass
  77. */
  78. private boolean isHollow = false;
  79. /**
  80. * Whether this collection element can be used
  81. * as a collection element. Defaults to true
  82. */
  83. private boolean wrapCollectionsInElement = true;
  84. /** specifies a separate implementation class that should be instantiated
  85. * when reading beans
  86. * or null if there is no separate implementation */
  87. private Class implementationClass = null;
  88. /**
  89. * Constructs an <code>ElementDescriptor</code> that refers to a primitive type.
  90. */
  91. public ElementDescriptor() {
  92. }
  93. /**
  94. * Base constructor.
  95. * @param primitiveType if true, this element refers to a primitive type
  96. * @deprecated 0.6 PrimitiveType property has been removed
  97. */
  98. public ElementDescriptor(boolean primitiveType) {
  99. this.primitiveType = primitiveType;
  100. }
  101. /**
  102. * Creates a ElementDescriptor with no namespace URI or prefix.
  103. *
  104. * @param localName the (xml) local name of this node.
  105. * This will be used to set both qualified and local name for this name.
  106. */
  107. public ElementDescriptor(String localName) {
  108. super( localName );
  109. }
  110. /**
  111. * Creates a <code>ElementDescriptor</code> with namespace URI and qualified name
  112. * @param localName the (xml) local name of this node
  113. * @param qualifiedName the (xml) qualified name of this node
  114. * @param uri the (xml) namespace prefix of this node
  115. */
  116. public ElementDescriptor(String localName, String qualifiedName, String uri) {
  117. super(localName, qualifiedName, uri);
  118. }
  119. /**
  120. * Returns true if this element has child <code>ElementDescriptors</code>
  121. * @return true if this element has child elements
  122. * @see #getElementDescriptors
  123. */
  124. public boolean hasChildren() {
  125. return getElementDescriptors().length > 0;
  126. }
  127. /**
  128. * Returns true if this element has <code>AttributeDescriptors</code>
  129. * @return true if this element has attributes
  130. * @see #getAttributeDescriptors
  131. */
  132. public boolean hasAttributes() {
  133. return getAttributeDescriptors().length > 0;
  134. }
  135. /**
  136. * Returns true if this element has child content.
  137. * @return true if this element has either child mixed content or child elements
  138. * @see #getContentDescriptors
  139. * @since 0.5
  140. */
  141. public boolean hasContent() {
  142. return getContentDescriptors().length > 0;
  143. }
  144. /**
  145. * Is this a simple element?
  146. * A simple element is one without child elements or attributes.
  147. * This corresponds to the simple type concept used in XML Schema.
  148. * TODO: need to consider whether it's sufficient to calculate
  149. * which are simple types (and so don't get IDs assigned etc)
  150. * @return true if it is a <code>SimpleType</code> element
  151. */
  152. public boolean isSimple() {
  153. return !(hasAttributes()) && !(hasChildren());
  154. }
  155. /**
  156. * Sets whether <code>Collection</code> bean properties should wrap items in a parent element.
  157. * In other words, should the mapping for bean properties which are <code>Collection</code>s
  158. * enclosed the item elements within a parent element.
  159. * Normally only used when this describes a collection bean property.
  160. *
  161. * @param wrapCollectionsInElement true if the elements for the items in the collection
  162. * should be contained in a parent element
  163. * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
  164. * be done during introspection
  165. */
  166. public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
  167. this.wrapCollectionsInElement = wrapCollectionsInElement;
  168. }
  169. /**
  170. * Returns true if collective bean properties should wrap the items in a parent element.
  171. * In other words, should the mapping for bean properties which are <code>Collection</code>s
  172. * enclosed the item elements within a parent element.
  173. * Normally only used when this describes a collection bean property.
  174. *
  175. * @return true if the elements for the items in the collection should be contained
  176. * in a parent element
  177. * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
  178. * be done during introspection
  179. */
  180. public boolean isWrapCollectionsInElement() {
  181. return this.wrapCollectionsInElement;
  182. }
  183. /**
  184. * Adds an attribute to the element this <code>ElementDescriptor</code> describes
  185. * @param descriptor the <code>AttributeDescriptor</code> that will be added to the
  186. * attributes associated with element this <code>ElementDescriptor</code> describes
  187. */
  188. public void addAttributeDescriptor(AttributeDescriptor descriptor) {
  189. if ( attributeList == null ) {
  190. attributeList = new ArrayList();
  191. }
  192. getAttributeList().add( descriptor );
  193. attributeDescriptors = null;
  194. }
  195. /**
  196. * Returns the attribute descriptors for this element
  197. *
  198. * @return descriptors for the attributes of the element that this
  199. * <code>ElementDescriptor</code> describes
  200. */
  201. public AttributeDescriptor[] getAttributeDescriptors() {
  202. if ( attributeDescriptors == null ) {
  203. if ( attributeList == null ) {
  204. attributeDescriptors = new AttributeDescriptor[0];
  205. } else {
  206. attributeDescriptors = new AttributeDescriptor[ attributeList.size() ];
  207. attributeList.toArray( attributeDescriptors );
  208. // allow GC of List when initialized
  209. attributeList = null;
  210. }
  211. }
  212. return attributeDescriptors;
  213. }
  214. /**
  215. * Sets the <code>AttributesDescriptors</code> for this element.
  216. * This sets descriptors for the attributes of the element describe by the
  217. * <code>ElementDescriptor</code>.
  218. *
  219. * @param attributeDescriptors the <code>AttributeDescriptor</code> describe the attributes
  220. * of the element described by this <code>ElementDescriptor</code>
  221. */
  222. public void setAttributeDescriptors(AttributeDescriptor[] attributeDescriptors) {
  223. this.attributeDescriptors = attributeDescriptors;
  224. this.attributeList = null;
  225. }
  226. /**
  227. * Adds a descriptor for a child element.
  228. *
  229. * @param descriptor the <code>ElementDescriptor</code> describing the child element to add
  230. */
  231. public void addElementDescriptor(ElementDescriptor descriptor) {
  232. if ( elementList == null ) {
  233. elementList = new ArrayList();
  234. }
  235. getElementList().add( descriptor );
  236. elementDescriptors = null;
  237. addContentDescriptor( descriptor );
  238. }
  239. /**
  240. * Returns descriptors for the child elements of the element this describes.
  241. * @return the <code>ElementDescriptor</code> describing the child elements
  242. * of the element that this <code>ElementDescriptor</code> describes
  243. */
  244. public ElementDescriptor[] getElementDescriptors() {
  245. if ( elementDescriptors == null ) {
  246. if ( elementList == null ) {
  247. elementDescriptors = new ElementDescriptor[0];
  248. } else {
  249. elementDescriptors = new ElementDescriptor[ elementList.size() ];
  250. elementList.toArray( elementDescriptors );
  251. // allow GC of List when initialized
  252. elementList = null;
  253. }
  254. }
  255. return elementDescriptors;
  256. }
  257. /**
  258. * Gets a child ElementDescriptor matching the given name if one exists.
  259. * Note that (so long as there are no better matches), a null name
  260. * acts as a wildcard. In other words, an
  261. * <code>ElementDescriptor</code> the first descriptor
  262. * with a null name will match any name
  263. * passed in, unless some other matches the name exactly.
  264. *
  265. * @param name the localname to be matched, not null
  266. * @returns the child ElementDescriptor with the given name if one exists,
  267. * otherwise null
  268. */
  269. public ElementDescriptor getElementDescriptor(String name) {
  270. ElementDescriptor elementDescriptor = null;
  271. ElementDescriptor descriptorWithNullName = null;
  272. ElementDescriptor[] elementDescriptors = getElementDescriptors();
  273. for (int i=0, size=elementDescriptors.length; i<size; i++) {
  274. String elementName = elementDescriptors[i].getQualifiedName();
  275. if (name.equals(elementName)) {
  276. elementDescriptor = elementDescriptors[i];
  277. break;
  278. }
  279. if (descriptorWithNullName == null && elementName == null) {
  280. descriptorWithNullName = elementDescriptors[i];
  281. }
  282. }
  283. if (elementDescriptor == null) {
  284. elementDescriptor = descriptorWithNullName;
  285. }
  286. return elementDescriptor;
  287. }
  288. /**
  289. * Sets the descriptors for the child element of the element this describes.
  290. * Also sets the child content descriptors for this element
  291. *
  292. * @param elementDescriptors the <code>ElementDescriptor</code>s of the element
  293. * that this describes
  294. */
  295. public void setElementDescriptors(ElementDescriptor[] elementDescriptors) {
  296. this.elementDescriptors = elementDescriptors;
  297. this.elementList = null;
  298. setContentDescriptors( elementDescriptors );
  299. }
  300. /**
  301. * Adds a descriptor for child content.
  302. *
  303. * @param descriptor the <code>Descriptor</code> describing the child content to add
  304. * @since 0.5
  305. */
  306. public void addContentDescriptor(Descriptor descriptor) {
  307. if ( contentList == null ) {
  308. contentList = new ArrayList();
  309. }
  310. getContentList().add( descriptor );
  311. contentDescriptors = null;
  312. }
  313. /**
  314. * Returns descriptors for the child content of the element this describes.
  315. * @return the <code>Descriptor</code> describing the child elements
  316. * of the element that this <code>ElementDescriptor</code> describes
  317. * @since 0.5
  318. */
  319. public Descriptor[] getContentDescriptors() {
  320. if ( contentDescriptors == null ) {
  321. if ( contentList == null ) {
  322. contentDescriptors = new Descriptor[0];
  323. } else {
  324. contentDescriptors = new Descriptor[ contentList.size() ];
  325. contentList.toArray( contentDescriptors );
  326. // allow GC of List when initialized
  327. contentList = null;
  328. }
  329. }
  330. return contentDescriptors;
  331. }
  332. /**
  333. * <p>Gets the primary descriptor for body text of this element.
  334. * Betwixt collects all body text for any element together.
  335. * This makes it rounds tripping difficult for beans that write more than one
  336. * mixed content property.
  337. * </p><p>
  338. * The algorithm used in the default implementation is that the first TextDescriptor
  339. * found amongst the descriptors is returned.
  340. *
  341. * @return the primary descriptor or null if this element has no mixed body content
  342. * @since 0.5
  343. */
  344. public TextDescriptor getPrimaryBodyTextDescriptor() {
  345. // todo: this probably isn't the most efficent algorithm
  346. // but should avoid premature optimization
  347. Descriptor[] descriptors = getContentDescriptors();
  348. for (int i=0, size=descriptors.length; i<size; i++) {
  349. if (descriptors[i] instanceof TextDescriptor) {
  350. return (TextDescriptor) descriptors[i];
  351. }
  352. }
  353. // if we haven't found anything, return null.
  354. return null;
  355. }
  356. /**
  357. * Sets the descriptors for the child content of the element this describes.
  358. * @param contentDescriptors the <code>Descriptor</code>s of the element
  359. * that this describes
  360. * @since 0.5
  361. */
  362. public void setContentDescriptors(Descriptor[] contentDescriptors) {
  363. this.contentDescriptors = contentDescriptors;
  364. this.contentList = null;
  365. }
  366. /**
  367. * Returns the expression used to evaluate the new context of this element.
  368. * @return the expression used to evaluate the new context of this element
  369. */
  370. public Expression getContextExpression() {
  371. return contextExpression;
  372. }
  373. /**
  374. * Sets the expression used to evaluate the new context of this element
  375. * @param contextExpression the expression used to evaluate the new context of this element
  376. */
  377. public void setContextExpression(Expression contextExpression) {
  378. this.contextExpression = contextExpression;
  379. }
  380. /**
  381. * Returns true if this element refers to a primitive type property
  382. * @return whether this element refers to a primitive type (or property of a parent object)
  383. * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
  384. * be done during introspection
  385. */
  386. public boolean isPrimitiveType() {
  387. return primitiveType;
  388. }
  389. /**
  390. * Sets whether this element refers to a primitive type (or property of a parent object)
  391. * @param primitiveType true if this element refers to a primitive type
  392. * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
  393. * be done during introspection
  394. */
  395. public void setPrimitiveType(boolean primitiveType) {
  396. this.primitiveType = primitiveType;
  397. }
  398. // Implementation methods
  399. //-------------------------------------------------------------------------
  400. /**
  401. * Lazily creates the mutable List.
  402. * This nullifies the attributeDescriptors array so that
  403. * as items are added to the list the Array is ignored until it is
  404. * explicitly asked for.
  405. *
  406. * @return list of <code>AttributeDescriptors</code>'s describing the attributes
  407. * of the element that this <code>ElementDescriptor</code> describes
  408. */
  409. protected List getAttributeList() {
  410. if ( attributeList == null ) {
  411. if ( attributeDescriptors != null ) {
  412. int size = attributeDescriptors.length;
  413. attributeList = new ArrayList( size );
  414. for ( int i = 0; i < size; i++ ) {
  415. attributeList.add( attributeDescriptors[i] );
  416. }
  417. // force lazy recreation later
  418. attributeDescriptors = null;
  419. } else {
  420. attributeList = new ArrayList();
  421. }
  422. }
  423. return attributeList;
  424. }
  425. /**
  426. * Lazily creates the mutable List of child elements.
  427. * This nullifies the elementDescriptors array so that
  428. * as items are added to the list the Array is ignored until it is
  429. * explicitly asked for.
  430. *
  431. * @return list of <code>ElementDescriptor</code>'s describe the child elements of
  432. * the element that this <code>ElementDescriptor</code> describes
  433. */
  434. protected List getElementList() {
  435. if ( elementList == null ) {
  436. if ( elementDescriptors != null ) {
  437. int size = elementDescriptors.length;
  438. elementList = new ArrayList( size );
  439. for ( int i = 0; i < size; i++ ) {
  440. elementList.add( elementDescriptors[i] );
  441. }
  442. // force lazy recreation later
  443. elementDescriptors = null;
  444. } else {
  445. elementList = new ArrayList();
  446. }
  447. }
  448. return elementList;
  449. }
  450. /**
  451. * Lazily creates the mutable List of child content descriptors.
  452. * This nullifies the contentDescriptors array so that
  453. * as items are added to the list the Array is ignored until it is
  454. * explicitly asked for.
  455. *
  456. * @return list of <code>Descriptor</code>'s describe the child content of
  457. * the element that this <code>Descriptor</code> describes
  458. * @since 0.5
  459. */
  460. protected List getContentList() {
  461. if ( contentList == null ) {
  462. if ( contentDescriptors != null ) {
  463. int size = contentDescriptors.length;
  464. contentList = new ArrayList( size );
  465. for ( int i = 0; i < size; i++ ) {
  466. contentList.add( contentDescriptors[i] );
  467. }
  468. // force lazy recreation later
  469. contentDescriptors = null;
  470. } else {
  471. contentList = new ArrayList();
  472. }
  473. }
  474. return contentList;
  475. }
  476. /**
  477. * Gets the class which should be used for instantiation.
  478. * @return the class which should be used for instantiation of beans
  479. * mapped from this element, null if the standard class should be used
  480. */
  481. public Class getImplementationClass() {
  482. return implementationClass;
  483. }
  484. /**
  485. * Sets the class which should be used for instantiation.
  486. * @param implementationClass the class which should be used for instantiation
  487. * or null to use the mapped type
  488. * @since 0.5
  489. */
  490. public void setImplementationClass(Class implementationClass) {
  491. this.implementationClass = implementationClass;
  492. }
  493. /**
  494. * TODO is this implementation correct?
  495. * maybe this method is unnecessary
  496. */
  497. public boolean isCollective() {
  498. boolean result = false;
  499. Class type = getPropertyType();
  500. if (type != null) {
  501. result = XMLIntrospectorHelper.isLoopType(type);
  502. }
  503. return result;
  504. }
  505. /**
  506. * @todo is this really a good design?
  507. */
  508. public ElementDescriptor findParent(ElementDescriptor elementDescriptor) {
  509. ElementDescriptor result = null;
  510. ElementDescriptor[] elementDescriptors = getElementDescriptors();
  511. for (int i=0, size=elementDescriptors.length; i<size; i++) {
  512. if (elementDescriptors[i].equals(elementDescriptor)) {
  513. result = this;
  514. break;
  515. }
  516. else
  517. {
  518. result = elementDescriptors[i].findParent(elementDescriptor);
  519. if (result != null) {
  520. break;
  521. }
  522. }
  523. }
  524. return result;
  525. }
  526. /**
  527. * Returns something useful for logging.
  528. *
  529. * @return a string useful for logging
  530. */
  531. public String toString() {
  532. return
  533. "ElementDescriptor[qname=" + getQualifiedName() + ",pname=" + getPropertyName()
  534. + ",class=" + getPropertyType() + ",singular=" + getSingularPropertyType()
  535. + ",updater=" + getUpdater() + ",wrap=" + isWrapCollectionsInElement() + "]";
  536. }
  537. /**
  538. * Is this decriptor hollow?
  539. * A hollow descriptor is one which gives only the class that the subgraph
  540. * is mapped to rather than describing the entire subgraph.
  541. * A new <code>XMLBeanInfo</code> should be introspected
  542. * and that used to describe the subgraph.
  543. * A hollow descriptor should not have any child descriptors.
  544. * TODO: consider whether a subclass would be better
  545. * @return true if this is hollow
  546. */
  547. public boolean isHollow() {
  548. return isHollow;
  549. }
  550. /**
  551. * Sets whether this descriptor is hollow.
  552. * A hollow descriptor is one which gives only the class that the subgraph
  553. * is mapped to rather than describing the entire subgraph.
  554. * A new <code>XMLBeanInfo</code> should be introspected
  555. * and that used to describe the subgraph.
  556. * A hollow descriptor should not have any child descriptors.
  557. * TODO: consider whether a subclass would be better
  558. * @param true if this is hollow
  559. */
  560. public void setHollow(boolean isHollow) {
  561. this.isHollow = isHollow;
  562. }
  563. }