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.io;
  17. import java.beans.IntrospectionException;
  18. import java.io.IOException;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.HashMap;
  22. import java.util.Iterator;
  23. import org.apache.commons.betwixt.AttributeDescriptor;
  24. import org.apache.commons.betwixt.BindingConfiguration;
  25. import org.apache.commons.betwixt.Descriptor;
  26. import org.apache.commons.betwixt.ElementDescriptor;
  27. import org.apache.commons.betwixt.XMLBeanInfo;
  28. import org.apache.commons.betwixt.XMLIntrospector;
  29. import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
  30. import org.apache.commons.betwixt.expression.Context;
  31. import org.apache.commons.betwixt.expression.Expression;
  32. import org.apache.commons.betwixt.io.id.SequentialIDGenerator;
  33. import org.apache.commons.collections.ArrayStack;
  34. import org.apache.commons.logging.Log;
  35. import org.apache.commons.logging.LogFactory;
  36. import org.xml.sax.Attributes;
  37. import org.xml.sax.SAXException;
  38. import org.xml.sax.helpers.AttributesImpl;
  39. // FIX ME!!!
  40. // Logging logic!
  41. // FIX ME!!
  42. // Error handling strategy!
  43. // i'm going to add SAXExceptions everywhere since it's the easiest way to make things work quick
  44. // but this is a poor strategy
  45. /**
  46. * <p>Abstract superclass for bean writers.
  47. * This class encapsulates the processing logic.
  48. * Subclasses provide implementations for the actual expression of the xml.</p>
  49. * <h5>SAX Inspired Writing API</h5>
  50. * <p>
  51. * This class is intended to be used by subclassing:
  52. * concrete subclasses perform the actual writing by providing
  53. * suitable implementations for the following methods inspired
  54. * by <a href='http://www.saxproject.org'>SAX</a>:
  55. * </p>
  56. * <ul>
  57. * <li> {@link #start} - called when processing begins</li>
  58. * <li> {@link #startElement(WriteContext, String, String, String, Attributes)}
  59. * - called when the start of an element
  60. * should be written</li>
  61. * <li> {@link #bodyText(WriteContext, String)}
  62. * - called when the start of an element
  63. * should be written</li>
  64. * <li> {@link #endElement(WriteContext, String, String, String)}
  65. * - called when the end of an element
  66. * should be written</li>
  67. * <li> {@link #end} - called when processing has been completed</li>
  68. * </ul>
  69. * <p>
  70. * <strong>Note</strong> that this class contains many deprecated
  71. * versions of the writing API. These will be removed soon so care
  72. * should be taken to use the latest version.
  73. * </p>
  74. * <p>
  75. * <strong>Note</strong> that this class is designed to be used
  76. * in a single threaded environment. When used in multi-threaded
  77. * environments, use of a common <code>XMLIntrospector</code>
  78. * and pooled writer instances should be considered.
  79. * </p>
  80. *
  81. * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
  82. */
  83. public abstract class AbstractBeanWriter {
  84. /** Introspector used */
  85. private XMLIntrospector introspector = new XMLIntrospector();
  86. /** Log used for logging (Doh!) */
  87. private Log log = LogFactory.getLog( AbstractBeanWriter.class );
  88. /** Map containing ID attribute values for beans */
  89. private HashMap idMap = new HashMap();
  90. /** Stack containing beans - used to detect cycles */
  91. private ArrayStack beanStack = new ArrayStack();
  92. /** Used to generate ID attribute values*/
  93. private IDGenerator idGenerator = new SequentialIDGenerator();
  94. /** Should empty elements be written out? */
  95. private boolean writeEmptyElements = true;
  96. /** Dynamic binding configuration settings */
  97. private BindingConfiguration bindingConfiguration = new BindingConfiguration();
  98. /** <code>WriteContext</code> implementation reused curing writing */
  99. private WriteContextImpl writeContext = new WriteContextImpl();
  100. /** Collection of namespaces which have already been declared */
  101. private Collection namespacesDeclared = new ArrayList();
  102. /**
  103. * Marks the start of the bean writing.
  104. * By default doesn't do anything, but can be used
  105. * to do extra start processing
  106. * @throws IOException if an IO problem occurs during writing
  107. * @throws SAXException if an SAX problem occurs during writing
  108. */
  109. public void start() throws IOException, SAXException {
  110. }
  111. /**
  112. * Marks the start of the bean writing.
  113. * By default doesn't do anything, but can be used
  114. * to do extra end processing
  115. * @throws IOException if an IO problem occurs during writing
  116. * @throws SAXException if an SAX problem occurs during writing
  117. */
  118. public void end() throws IOException, SAXException {
  119. }
  120. /**
  121. * <p> Writes the given bean to the current stream using the XML introspector.</p>
  122. *
  123. * <p> This writes an xml fragment representing the bean to the current stream.</p>
  124. *
  125. * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
  126. * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
  127. * setting of the </code>BindingConfiguration</code> is false.</p>
  128. *
  129. * @throws IOException if an IO problem occurs during writing
  130. * @throws SAXException if an SAX problem occurs during writing
  131. * @throws IntrospectionException if a java beans introspection problem occurs
  132. *
  133. * @param bean write out representation of this bean
  134. */
  135. public void write(Object bean) throws
  136. IOException,
  137. SAXException,
  138. IntrospectionException {
  139. if (log.isDebugEnabled()) {
  140. log.debug( "Writing bean graph..." );
  141. log.debug( bean );
  142. }
  143. start();
  144. writeBean( null, null, null, bean, makeContext( bean ) );
  145. end();
  146. if (log.isDebugEnabled()) {
  147. log.debug( "Finished writing bean graph." );
  148. }
  149. }
  150. /**
  151. * <p>Writes the given bean to the current stream
  152. * using the given <code>qualifiedName</code>.</p>
  153. *
  154. * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
  155. * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
  156. * setting of the <code>BindingConfiguration</code> is false.</p>
  157. *
  158. * @param qualifiedName the string naming root element
  159. * @param bean the <code>Object</code> to write out as xml
  160. *
  161. * @throws IOException if an IO problem occurs during writing
  162. * @throws SAXException if an SAX problem occurs during writing
  163. * @throws IntrospectionException if a java beans introspection problem occurs
  164. */
  165. public void write(
  166. String qualifiedName,
  167. Object bean)
  168. throws
  169. IOException,
  170. SAXException,
  171. IntrospectionException {
  172. start();
  173. writeBean( "", qualifiedName, qualifiedName, bean, makeContext( bean ) );
  174. end();
  175. }
  176. /**
  177. * <p>Writes the given bean to the current stream
  178. * using the given <code>qualifiedName</code>.</p>
  179. *
  180. * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
  181. * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
  182. * setting of the <code>BindingConfiguration</code> is false.</p>
  183. *
  184. * @param namespaceUri the namespace uri
  185. * @param localName the local name
  186. * @param qualifiedName the string naming root element
  187. * @param bean the <code>Object</code> to write out as xml
  188. * @param context not null
  189. *
  190. * @throws IOException if an IO problem occurs during writing
  191. * @throws SAXException if an SAX problem occurs during writing
  192. * @throws IntrospectionException if a java beans introspection problem occurs
  193. */
  194. private void writeBean (
  195. String namespaceUri,
  196. String localName,
  197. String qualifiedName,
  198. Object bean,
  199. Context context)
  200. throws
  201. IOException,
  202. SAXException,
  203. IntrospectionException {
  204. if ( log.isTraceEnabled() ) {
  205. log.trace( "Writing bean graph (qualified name '" + qualifiedName + "'" );
  206. }
  207. // introspect to obtain bean info
  208. XMLBeanInfo beanInfo = introspector.introspect( bean );
  209. if ( beanInfo != null ) {
  210. ElementDescriptor elementDescriptor = beanInfo.getElementDescriptor();
  211. if ( elementDescriptor != null ) {
  212. context = context.newContext( bean );
  213. if ( qualifiedName == null ) {
  214. qualifiedName = elementDescriptor.getQualifiedName();
  215. }
  216. if ( namespaceUri == null ) {
  217. namespaceUri = elementDescriptor.getURI();
  218. }
  219. if ( localName == null ) {
  220. localName = elementDescriptor.getLocalName();
  221. }
  222. String ref = null;
  223. String id = null;
  224. // simple type should not have IDs
  225. if ( elementDescriptor.isSimple() ) {
  226. // write without an id
  227. writeElement(
  228. namespaceUri,
  229. localName,
  230. qualifiedName,
  231. elementDescriptor,
  232. context );
  233. } else {
  234. pushBean ( context.getBean() );
  235. if ( getBindingConfiguration().getMapIDs() ) {
  236. ref = (String) idMap.get( context.getBean() );
  237. }
  238. if ( ref == null ) {
  239. // this is the first time that this bean has be written
  240. AttributeDescriptor idAttribute = beanInfo.getIDAttribute();
  241. if (idAttribute == null) {
  242. // use a generated id
  243. id = idGenerator.nextId();
  244. idMap.put( bean, id );
  245. if ( getBindingConfiguration().getMapIDs() ) {
  246. // write element with id
  247. writeElement(
  248. namespaceUri,
  249. localName,
  250. qualifiedName,
  251. elementDescriptor,
  252. context ,
  253. beanInfo.getIDAttributeName(),
  254. id);
  255. } else {
  256. // write element without ID
  257. writeElement(
  258. namespaceUri,
  259. localName,
  260. qualifiedName,
  261. elementDescriptor,
  262. context );
  263. }
  264. } else {
  265. // use id from bean property
  266. // it's up to the user to ensure uniqueness
  267. // XXX should we trap nulls?
  268. Object exp = idAttribute.getTextExpression().evaluate( context );
  269. if (exp == null) {
  270. // we'll use a random id
  271. log.debug("Using random id");
  272. id = idGenerator.nextId();
  273. } else {
  274. // convert to string
  275. id = exp.toString();
  276. }
  277. idMap.put( bean, id);
  278. // the ID attribute should be written automatically
  279. writeElement(
  280. namespaceUri,
  281. localName,
  282. qualifiedName,
  283. elementDescriptor,
  284. context );
  285. }
  286. } else {
  287. if ( !ignoreElement( elementDescriptor, context )) {
  288. // we've already written this bean so write an IDREF
  289. writeIDREFElement(
  290. elementDescriptor,
  291. namespaceUri,
  292. localName,
  293. qualifiedName,
  294. beanInfo.getIDREFAttributeName(),
  295. ref);
  296. }
  297. }
  298. popBean();
  299. }
  300. }
  301. }
  302. log.trace( "Finished writing bean graph." );
  303. }
  304. /**
  305. * Get <code>IDGenerator</code> implementation used to
  306. * generate <code>ID</code> attribute values .
  307. *
  308. * @return implementation used for <code>ID</code> attribute generation
  309. */
  310. public IDGenerator getIdGenerator() {
  311. return idGenerator;
  312. }
  313. /**
  314. * Set <code>IDGenerator</code> implementation
  315. * used to generate <code>ID</code> attribute values.
  316. * This property can be used to customize the algorithm used for generation.
  317. *
  318. * @param idGenerator use this implementation for <code>ID</code> attribute generation
  319. */
  320. public void setIdGenerator(IDGenerator idGenerator) {
  321. this.idGenerator = idGenerator;
  322. }
  323. /**
  324. * Gets the dynamic configuration setting to be used for bean reading.
  325. * @return the BindingConfiguration settings, not null
  326. * @since 0.5
  327. */
  328. public BindingConfiguration getBindingConfiguration() {
  329. return bindingConfiguration;
  330. }
  331. /**
  332. * Sets the dynamic configuration setting to be used for bean reading.
  333. * @param bindingConfiguration the BindingConfiguration settings, not null
  334. * @since 0.5
  335. */
  336. public void setBindingConfiguration(BindingConfiguration bindingConfiguration) {
  337. this.bindingConfiguration = bindingConfiguration;
  338. }
  339. /**
  340. * <p>Should generated <code>ID</code> attribute values be added to the elements?</p>
  341. *
  342. * <p>If IDs are not being written then if a cycle is encountered in the bean graph,
  343. * then a {@link CyclicReferenceException} will be thrown by the write method.</p>
  344. *
  345. * @return true if <code>ID</code> and <code>IDREF</code> attributes are to be written
  346. * @deprecated 0.5 use {@link BindingConfiguration#getMapIDs}
  347. */
  348. public boolean getWriteIDs() {
  349. return getBindingConfiguration().getMapIDs();
  350. }
  351. /**
  352. * Set whether generated <code>ID</code> attribute values should be added to the elements
  353. * If this property is set to false, then <code>CyclicReferenceException</code>
  354. * will be thrown whenever a cyclic occurs in the bean graph.
  355. *
  356. * @param writeIDs true if <code>ID</code>'s and <code>IDREF</code>'s should be written
  357. * @deprecated 0.5 use {@link BindingConfiguration#setMapIDs}
  358. */
  359. public void setWriteIDs(boolean writeIDs) {
  360. getBindingConfiguration().setMapIDs( writeIDs );
  361. }
  362. /**
  363. * <p>Gets whether empty elements should be written into the output.</p>
  364. *
  365. * <p>An empty element is one that has no attributes, no child elements
  366. * and no body text.
  367. * For example, <code><element/></code> is an empty element but
  368. * <code><element attr='value'/></code> is not.</p>
  369. *
  370. * @return true if empty elements will be written into the output
  371. * @since 0.5
  372. */
  373. public boolean getWriteEmptyElements() {
  374. return writeEmptyElements;
  375. }
  376. /**
  377. * <p>Sets whether empty elements should be written into the output.</p>
  378. *
  379. * <p>An empty element is one that has no attributes, no child elements
  380. * and no body text.
  381. * For example, <code><element/></code> is an empty element but
  382. * <code><element attr='value'/></code> is not.
  383. *
  384. * @param writeEmptyElements true if empty elements should be written into the output
  385. * @since 0.5
  386. */
  387. public void setWriteEmptyElements(boolean writeEmptyElements) {
  388. this.writeEmptyElements = writeEmptyElements;
  389. }
  390. /**
  391. * <p>Gets the introspector used.</p>
  392. *
  393. * <p>The {@link XMLBeanInfo} used to map each bean is
  394. * created by the <code>XMLIntrospector</code>.
  395. * One way in which the mapping can be customized is
  396. * by altering the <code>XMLIntrospector</code>. </p>
  397. *
  398. * @return the <code>XMLIntrospector</code> used for introspection
  399. */
  400. public XMLIntrospector getXMLIntrospector() {
  401. return introspector;
  402. }
  403. /**
  404. * <p>Sets the introspector to be used.</p>
  405. *
  406. * <p>The {@link XMLBeanInfo} used to map each bean is
  407. * created by the <code>XMLIntrospector</code>.
  408. * One way in which the mapping can be customized is by
  409. * altering the <code>XMLIntrospector</code>. </p>
  410. *
  411. * @param introspector use this introspector
  412. */
  413. public void setXMLIntrospector(XMLIntrospector introspector) {
  414. this.introspector = introspector;
  415. }
  416. /**
  417. * <p>Gets the current logging implementation.</p>
  418. *
  419. * @return the <code>Log</code> implementation which this class logs to
  420. */
  421. public final Log getAbstractBeanWriterLog() {
  422. return log;
  423. }
  424. /**
  425. * <p> Set the current logging implementation. </p>
  426. *
  427. * @param log <code>Log</code> implementation to use
  428. */
  429. public final void setAbstractBeanWriterLog(Log log) {
  430. this.log = log;
  431. }
  432. // SAX-style methods
  433. //-------------------------------------------------------------------------
  434. /**
  435. * Writes the start tag for an element.
  436. *
  437. * @param uri the element's namespace uri
  438. * @param localName the element's local name
  439. * @param qName the element's qualified name
  440. * @param attr the element's attributes
  441. *
  442. * @throws IOException if an IO problem occurs during writing
  443. * @throws SAXException if an SAX problem occurs during writing
  444. * @since 0.5
  445. */
  446. protected void startElement(
  447. WriteContext context,
  448. String uri,
  449. String localName,
  450. String qName,
  451. Attributes attr)
  452. throws
  453. IOException,
  454. SAXException {
  455. // for backwards compatbility call older methods
  456. startElement(uri, localName, qName, attr);
  457. }
  458. /**
  459. * Writes the end tag for an element
  460. *
  461. * @param uri the element's namespace uri
  462. * @param localName the element's local name
  463. * @param qName the element's qualified name
  464. *
  465. * @throws IOException if an IO problem occurs during writing
  466. * @throws SAXException if an SAX problem occurs during writing
  467. * @since 0.5
  468. */
  469. protected void endElement(
  470. WriteContext context,
  471. String uri,
  472. String localName,
  473. String qName)
  474. throws
  475. IOException,
  476. SAXException {
  477. // for backwards compatibility call older interface
  478. endElement(uri, localName, qName);
  479. }
  480. /**
  481. * Writes body text
  482. *
  483. * @param text the body text to be written
  484. *
  485. * @throws IOException if an IO problem occurs during writing
  486. * @throws SAXException if an SAX problem occurs during writing
  487. * @since 0.5
  488. */
  489. protected void bodyText(WriteContext context, String text)
  490. throws IOException, SAXException {
  491. // for backwards compatibility call older interface
  492. bodyText(text);
  493. }
  494. // Older SAX-style methods
  495. //-------------------------------------------------------------------------
  496. /**
  497. * Writes the start tag for an element.
  498. *
  499. * @param uri the element's namespace uri
  500. * @param localName the element's local name
  501. * @param qName the element's qualified name
  502. * @param attr the element's attributes
  503. *
  504. * @throws IOException if an IO problem occurs during writing
  505. * @throws SAXException if an SAX problem occurs during writing
  506. * @deprecated 0.5 use {@link #startElement(WriteContext, String, String, String, Attributes)}
  507. */
  508. protected void startElement(
  509. String uri,
  510. String localName,
  511. String qName,
  512. Attributes attr)
  513. throws
  514. IOException,
  515. SAXException {}
  516. /**
  517. * Writes the end tag for an element
  518. *
  519. * @param uri the element's namespace uri
  520. * @param localName the element's local name
  521. * @param qName the element's qualified name
  522. *
  523. * @throws IOException if an IO problem occurs during writing
  524. * @throws SAXException if an SAX problem occurs during writing
  525. * @deprecated 0.5 use {@link #endElement(WriteContext, String, String, String)}
  526. */
  527. protected void endElement(
  528. String uri,
  529. String localName,
  530. String qName)
  531. throws
  532. IOException,
  533. SAXException {}
  534. /**
  535. * Writes body text
  536. *
  537. * @param text the body text to be written
  538. *
  539. * @throws IOException if an IO problem occurs during writing
  540. * @throws SAXException if an SAX problem occurs during writing
  541. * @deprecated 0.5 use {@link #bodyText(WriteContext, String)}
  542. */
  543. protected void bodyText(String text) throws IOException, SAXException {}
  544. // Implementation methods
  545. //-------------------------------------------------------------------------
  546. /**
  547. * Writes the given element
  548. *
  549. * @param namespaceUri the namespace uri
  550. * @param localName the local name
  551. * @param qualifiedName qualified name to use for the element
  552. * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
  553. * @param context the <code>Context</code> to use to evaluate the bean expressions
  554. * @throws IOException if an IO problem occurs during writing
  555. * @throws SAXException if an SAX problem occurs during writing
  556. * @throws IntrospectionException if a java beans introspection problem occurs
  557. */
  558. private void writeElement(
  559. String namespaceUri,
  560. String localName,
  561. String qualifiedName,
  562. ElementDescriptor elementDescriptor,
  563. Context context )
  564. throws
  565. IOException,
  566. SAXException,
  567. IntrospectionException {
  568. if( log.isTraceEnabled() ) {
  569. log.trace( "Writing: " + qualifiedName + " element: " + elementDescriptor );
  570. }
  571. if ( !ignoreElement( elementDescriptor, context )) {
  572. if ( log.isTraceEnabled() ) {
  573. log.trace( "Element " + elementDescriptor + " is empty." );
  574. }
  575. Attributes attributes = addNamespaceDeclarations(
  576. new ElementAttributes( elementDescriptor, context ), namespaceUri);
  577. writeContext.setCurrentDescriptor(elementDescriptor);
  578. startElement(
  579. writeContext,
  580. namespaceUri,
  581. localName,
  582. qualifiedName,
  583. attributes);
  584. writeElementContent( elementDescriptor, context ) ;
  585. writeContext.setCurrentDescriptor(elementDescriptor);
  586. endElement( writeContext, namespaceUri, localName, qualifiedName );
  587. }
  588. }
  589. /**
  590. * Adds namespace declarations (if any are needed) to the given attributes.
  591. * @param attributes Attributes, not null
  592. * @param elementNamespaceUri the URI for the enclosing element, possibly null
  593. * @return Attributes, not null
  594. */
  595. private Attributes addNamespaceDeclarations(Attributes attributes, String elementNamespaceUri) {
  596. Attributes result = attributes;
  597. AttributesImpl withDeclarations = null;
  598. for (int i=-1, size=attributes.getLength(); i<size ; i++) {
  599. String uri = null;
  600. if (i == -1) {
  601. uri = elementNamespaceUri;
  602. } else {
  603. uri = attributes.getURI(i);
  604. }
  605. if (uri != null && !"".equals(uri) && !namespacesDeclared.contains(uri)) {
  606. if (withDeclarations == null) {
  607. withDeclarations = new AttributesImpl(attributes);
  608. }
  609. withDeclarations.addAttribute("", "", "xmlns:"
  610. + getXMLIntrospector().getConfiguration().getPrefixMapper().getPrefix(uri), "NOTATION", uri);
  611. namespacesDeclared.add(uri);
  612. }
  613. }
  614. if (withDeclarations != null) {
  615. result = withDeclarations;
  616. }
  617. return result;
  618. }
  619. /**
  620. * Writes the given element adding an ID attribute
  621. *
  622. * @param namespaceUri the namespace uri
  623. * @param localName the local name
  624. * @param qualifiedName the qualified name
  625. * @param elementDescriptor the ElementDescriptor describing this element
  626. * @param context the context being evaliated against
  627. * @param idAttribute the qualified name of the <code>ID</code> attribute
  628. * @param idValue the value for the <code>ID</code> attribute
  629. * @throws IOException if an IO problem occurs during writing
  630. * @throws SAXException if an SAX problem occurs during writing
  631. * @throws IntrospectionException if a java beans introspection problem occurs
  632. */
  633. private void writeElement(
  634. String namespaceUri,
  635. String localName,
  636. String qualifiedName,
  637. ElementDescriptor elementDescriptor,
  638. Context context,
  639. String idAttribute,
  640. String idValue )
  641. throws
  642. IOException,
  643. SAXException,
  644. IntrospectionException {
  645. if ( !ignoreElement( elementDescriptor, context ) ) {
  646. writeContext.setCurrentDescriptor(elementDescriptor);
  647. Attributes attributes = new IDElementAttributes(
  648. elementDescriptor,
  649. context,
  650. idAttribute,
  651. idValue );
  652. startElement(
  653. writeContext,
  654. namespaceUri,
  655. localName,
  656. qualifiedName,
  657. addNamespaceDeclarations(attributes, namespaceUri));
  658. writeElementContent( elementDescriptor, context ) ;
  659. writeContext.setCurrentDescriptor(elementDescriptor);
  660. endElement( writeContext, namespaceUri, localName, qualifiedName );
  661. } else if ( log.isTraceEnabled() ) {
  662. log.trace( "Element " + qualifiedName + " is empty." );
  663. }
  664. }
  665. /**
  666. * Write attributes, child elements and element end
  667. *
  668. * @param uri the element namespace uri
  669. * @param localName the local name of the element
  670. * @param qualifiedName the qualified name of the element
  671. * @param elementDescriptor the descriptor for this element
  672. * @param context evaluate against this context
  673. * @throws IOException if an IO problem occurs during writing
  674. * @throws SAXException if an SAX problem occurs during writing
  675. * @throws IntrospectionException if a java beans introspection problem occurs
  676. */
  677. private void writeRestOfElement(
  678. String uri,
  679. String localName,
  680. String qualifiedName,
  681. ElementDescriptor elementDescriptor,
  682. Context context )
  683. throws
  684. IOException,
  685. SAXException,
  686. IntrospectionException {
  687. writeElementContent( elementDescriptor, context );
  688. }
  689. /**
  690. * Writes an element with a <code>IDREF</code> attribute
  691. *
  692. * @param uri the namespace uri
  693. * @param localName the local name
  694. * @param qualifiedName of the element with <code>IDREF</code> attribute
  695. * @param idrefAttributeName the qualified name of the <code>IDREF</code> attribute
  696. * @param idrefAttributeValue the value for the <code>IDREF</code> attribute
  697. * @throws IOException if an IO problem occurs during writing
  698. * @throws SAXException if an SAX problem occurs during writing
  699. * @throws IntrospectionException if a java beans introspection problem occurs
  700. */
  701. private void writeIDREFElement(
  702. ElementDescriptor elementDescriptor,
  703. String uri,
  704. String localName,
  705. String qualifiedName,
  706. String idrefAttributeName,
  707. String idrefAttributeValue )
  708. throws
  709. IOException,
  710. SAXException,
  711. IntrospectionException {
  712. // write IDREF element
  713. AttributesImpl attributes = new AttributesImpl();
  714. // XXX for the moment, assign IDREF to default namespace
  715. attributes.addAttribute(
  716. "",
  717. idrefAttributeName,
  718. idrefAttributeName,
  719. "IDREF",
  720. idrefAttributeValue);
  721. writeContext.setCurrentDescriptor(elementDescriptor);
  722. startElement( writeContext, uri, localName, qualifiedName, addNamespaceDeclarations(attributes, uri));
  723. endElement( writeContext, uri, localName, qualifiedName );
  724. }
  725. /**
  726. * Writes the element content.
  727. *
  728. * @param elementDescriptor the <code>ElementDescriptor</code> to write as xml
  729. * @param context the <code>Context</code> to use to evaluate the bean expressions
  730. *
  731. * @throws IOException if an IO problem occurs during writing
  732. * @throws SAXException if an SAX problem occurs during writing
  733. * @throws IntrospectionException if a java beans introspection problem occurs
  734. */
  735. private void writeElementContent(
  736. ElementDescriptor elementDescriptor,
  737. Context context )
  738. throws
  739. IOException,
  740. SAXException,
  741. IntrospectionException {
  742. writeContext.setCurrentDescriptor( elementDescriptor );
  743. Descriptor[] childDescriptors = elementDescriptor.getContentDescriptors();
  744. if ( childDescriptors != null && childDescriptors.length > 0 ) {
  745. // process child elements
  746. for ( int i = 0, size = childDescriptors.length; i < size; i++ ) {
  747. if (childDescriptors[i] instanceof ElementDescriptor) {
  748. // Element content
  749. ElementDescriptor childDescriptor = (ElementDescriptor) childDescriptors[i];
  750. Context childContext = context;
  751. Expression childExpression = childDescriptor.getContextExpression();
  752. if ( childExpression != null ) {
  753. Object childBean = childExpression.evaluate( context );
  754. if ( childBean != null ) {
  755. String qualifiedName = childDescriptor.getQualifiedName();
  756. String namespaceUri = childDescriptor.getURI();
  757. String localName = childDescriptor.getLocalName();
  758. // XXXX: should we handle nulls better
  759. if ( childBean instanceof Iterator ) {
  760. for ( Iterator iter = (Iterator) childBean; iter.hasNext(); ) {
  761. Object object = iter.next();
  762. if (object == null) {
  763. continue;
  764. }
  765. writeBean(
  766. namespaceUri,
  767. localName,
  768. qualifiedName,
  769. object,
  770. context );
  771. }
  772. } else {
  773. writeBean(
  774. namespaceUri,
  775. localName,
  776. qualifiedName,
  777. childBean,
  778. context );
  779. }
  780. }
  781. } else {
  782. writeElement(
  783. childDescriptor.getURI(),
  784. childDescriptor.getLocalName(),
  785. childDescriptor.getQualifiedName(),
  786. childDescriptor,
  787. childContext );
  788. }
  789. } else {
  790. // Mixed text content
  791. // evaluate the body text
  792. Expression expression = childDescriptors[i].getTextExpression();
  793. if ( expression != null ) {
  794. Object value = expression.evaluate( context );
  795. String text = convertToString(
  796. value,
  797. childDescriptors[i],
  798. context );
  799. if ( text != null && text.length() > 0 ) {;
  800. bodyText( writeContext, text );
  801. }
  802. }
  803. }
  804. }
  805. } else {
  806. // evaluate the body text
  807. Expression expression = elementDescriptor.getTextExpression();
  808. if ( expression != null ) {
  809. Object value = expression.evaluate( context );
  810. String text = convertToString( value, elementDescriptor, context );
  811. if ( text != null && text.length() > 0 ) {
  812. bodyText( writeContext, text );
  813. }
  814. }
  815. }
  816. }
  817. /**
  818. * Pushes the bean onto the ancestry stack.
  819. * If IDs are not being written, then check for cyclic references.
  820. *
  821. * @param bean push this bean onto the ancester stack
  822. */
  823. protected void pushBean( Object bean ) {
  824. // check that we don't have a cyclic reference when we're not writing IDs
  825. if ( !getBindingConfiguration().getMapIDs() ) {
  826. Iterator it = beanStack.iterator();
  827. while ( it.hasNext() ) {
  828. Object next = it.next();
  829. // use absolute equality rather than equals
  830. // we're only really bothered if objects are actually the same
  831. if ( bean == next ) {
  832. if ( log.isDebugEnabled() ) {
  833. log.debug("Element stack: ");
  834. Iterator debugStack = beanStack.iterator();
  835. while ( debugStack.hasNext() ) {
  836. log.debug(debugStack.next());
  837. }
  838. }
  839. log.error("Cyclic reference at bean: " + bean);
  840. throw new CyclicReferenceException();
  841. }
  842. }
  843. }
  844. if (log.isTraceEnabled()) {
  845. log.trace( "Pushing onto object stack: " + bean );
  846. }
  847. beanStack.push( bean );
  848. }
  849. /**
  850. * Pops the top bean off from the ancestry stack
  851. *
  852. * @return the last object pushed onto the ancester stack
  853. */
  854. protected Object popBean() {
  855. Object bean = beanStack.pop();
  856. if (log.isTraceEnabled()) {
  857. log.trace( "Popped from object stack: " + bean );
  858. }
  859. return bean;
  860. }
  861. /**
  862. * Should this element (and children) be written out?
  863. *
  864. * @param descriptor the <code>ElementDescriptor</code> to evaluate
  865. * @param context the <code>Context</code> against which the element will be evaluated
  866. * @return true if this element should be written out
  867. */
  868. private boolean ignoreElement( ElementDescriptor descriptor, Context context ) {
  869. if ( ! getWriteEmptyElements() ) {
  870. return isEmptyElement( descriptor, context );
  871. }
  872. return false;
  873. }
  874. /**
  875. * <p>Will evaluating this element against this context result in an empty element?</p>
  876. *
  877. * <p>An empty element is one that has no attributes, no child elements
  878. * and no body text.
  879. * For example, <code><element/></code> is an empty element but
  880. * <code><element attr='value'/></code> is not.</p>
  881. *
  882. * @param descriptor the <code>ElementDescriptor</code> to evaluate
  883. * @param context the <code>Context</code> against which the element will be evaluated
  884. * @return true if this element is empty on evaluation
  885. */
  886. private boolean isEmptyElement( ElementDescriptor descriptor, Context context ) {
  887. if ( log.isTraceEnabled() ) {
  888. log.trace( "Is " + descriptor + " empty?" );
  889. }
  890. // an element which has attributes is not empty
  891. if ( descriptor.hasAttributes() ) {
  892. log.trace( "Element has attributes." );
  893. return false;
  894. }
  895. // an element is not empty if it has a non-empty body
  896. Expression expression = descriptor.getTextExpression();
  897. if ( expression != null ) {
  898. Object value = expression.evaluate( context );
  899. String text = convertToString( value, descriptor, context );
  900. if ( text != null && text.length() > 0 ) {
  901. log.trace( "Element has body text which isn't empty." );
  902. return false;
  903. }
  904. }
  905. // always write out loops - even when they have no elements
  906. if ( XMLIntrospectorHelper.isLoopType( descriptor.getPropertyType() ) ) {
  907. log.trace("Loop type so not empty.");
  908. return false;
  909. }
  910. // now test child elements
  911. // an element is empty if it has no non-empty child elements
  912. if ( descriptor.hasChildren() ) {
  913. for ( int i=0, size=descriptor.getElementDescriptors().length; i<size; i++ ) {
  914. if ( ! isEmptyElement( descriptor.getElementDescriptors()[i], context ) ) {
  915. log.trace( "Element has child which isn't empty." );
  916. return false;
  917. }
  918. }
  919. }
  920. log.trace( "Element is empty." );
  921. return true;
  922. }
  923. /**
  924. * Attributes backed by attribute descriptors.
  925. * ID/IDREFs not set.
  926. */
  927. private class ElementAttributes implements Attributes {
  928. /** Attribute descriptors backing the <code>Attributes</code> */
  929. private AttributeDescriptor[] attributes;
  930. /** Context to be evaluated when finding values */
  931. private Context context;
  932. /**
  933. * Construct attributes for element and context.
  934. *
  935. * @param descriptor the <code>ElementDescriptor</code> describing the element
  936. * @param context evaluate against this context
  937. */
  938. ElementAttributes( ElementDescriptor descriptor, Context context ) {
  939. attributes = descriptor.getAttributeDescriptors();
  940. this.context = context;
  941. }
  942. /**
  943. * Gets the index of an attribute by qualified name.
  944. *
  945. * @param qName the qualified name of the attribute
  946. * @return the index of the attribute - or -1 if there is no matching attribute
  947. */
  948. public int getIndex( String qName ) {
  949. for ( int i=0; i<attributes.length; i++ ) {
  950. if (attributes[i].getQualifiedName() != null
  951. && attributes[i].getQualifiedName().equals( qName )) {
  952. return i;
  953. }
  954. }
  955. return -1;
  956. }
  957. /**
  958. * Gets the index of an attribute by namespace name.
  959. *
  960. * @param uri the namespace uri of the attribute
  961. * @param localName the local name of the attribute
  962. * @return the index of the attribute - or -1 if there is no matching attribute
  963. */
  964. public int getIndex( String uri, String localName ) {
  965. for ( int i=0; i<attributes.length; i++ ) {
  966. if (
  967. attributes[i].getURI() != null
  968. && attributes[i].getURI().equals(uri)
  969. && attributes[i].getLocalName() != null
  970. && attributes[i].getURI().equals(localName)) {
  971. return i;
  972. }
  973. }
  974. return -1;
  975. }
  976. /**
  977. * Gets the number of attributes in the list.
  978. *
  979. * @return the number of attributes in this list
  980. */
  981. public int getLength() {
  982. return attributes.length;
  983. }
  984. /**
  985. * Gets the local name by index.
  986. *
  987. * @param index the attribute index (zero based)
  988. * @return the attribute local name - or null if the index is out of range
  989. */
  990. public String getLocalName( int index ) {
  991. if ( indexInRange( index ) ) {
  992. return attributes[index].getLocalName();
  993. }
  994. return null;
  995. }
  996. /**
  997. * Gets the qualified name by index.
  998. *
  999. * @param index the attribute index (zero based)
  1000. * @return the qualified name of the element - or null if the index is our of range
  1001. */
  1002. public String getQName( int index ) {
  1003. if ( indexInRange( index ) ) {
  1004. return attributes[index].getQualifiedName();
  1005. }
  1006. return null;
  1007. }
  1008. /**
  1009. * Gets the attribute SAX type by namespace name.
  1010. *
  1011. * @param index the attribute index (zero based)
  1012. * @return the attribute type (as a string) or null if the index is out of range
  1013. */
  1014. public String getType( int index ) {
  1015. if ( indexInRange( index ) ) {
  1016. return "CDATA";
  1017. }
  1018. return null;
  1019. }
  1020. /**
  1021. * Gets the attribute SAX type by qualified name.
  1022. *
  1023. * @param qName the qualified name of the attribute
  1024. * @return the attribute type (as a string) or null if the attribute is not in the list
  1025. */
  1026. public String getType( String qName ) {
  1027. return getType( getIndex( qName ) );
  1028. }
  1029. /**
  1030. * Gets the attribute SAX type by namespace name.
  1031. *
  1032. * @param uri the namespace uri of the attribute
  1033. * @param localName the local name of the attribute
  1034. * @return the attribute type (as a string) or null if the attribute is not in the list
  1035. */
  1036. public String getType( String uri, String localName ) {
  1037. return getType( getIndex( uri, localName ));
  1038. }
  1039. /**
  1040. * Gets the namespace URI for attribute at the given index.
  1041. *
  1042. * @param index the attribute index (zero-based)
  1043. * @return the namespace URI (empty string if none is available)
  1044. * or null if the index is out of range
  1045. */
  1046. public String getURI( int index ) {
  1047. if ( indexInRange( index ) ) {
  1048. return attributes[index].getURI();
  1049. }
  1050. return null;
  1051. }
  1052. /**
  1053. * Gets the value for the attribute at given index.
  1054. *
  1055. * @param index the attribute index (zero based)
  1056. * @return the attribute value or null if the index is out of range
  1057. * @todo add value caching
  1058. */
  1059. public String getValue( int index ) {
  1060. if ( indexInRange( index )) {
  1061. Expression expression = attributes[index].getTextExpression();
  1062. if ( expression != null ) {
  1063. Object value = expression.evaluate( context );
  1064. return convertToString( value, attributes[index], context );
  1065. }
  1066. return "";
  1067. }
  1068. return null;
  1069. }
  1070. /**
  1071. * Gets the value for the attribute by qualified name.
  1072. *
  1073. * @param qName the qualified name
  1074. * @return the attribute value or null if there are no attributes
  1075. * with the given qualified name
  1076. * @todo add value caching
  1077. */
  1078. public String getValue( String qName ) {
  1079. return getValue( getIndex( qName ) );
  1080. }
  1081. /**
  1082. * Gets the value for the attribute by namespace name.
  1083. *
  1084. * @param uri the namespace URI of the attribute
  1085. * @param localName the local name of the attribute
  1086. * @return the attribute value or null if there are not attributes
  1087. * with the given namespace and local name
  1088. * @todo add value caching
  1089. */
  1090. public String getValue( String uri, String localName ) {
  1091. return getValue( getIndex( uri, localName ) );
  1092. }
  1093. /**
  1094. * Is the given index within the range of the attribute list
  1095. *
  1096. * @param index the index whose range will be checked
  1097. * @return true if the index with within the range of the attribute list
  1098. */
  1099. private boolean indexInRange( int index ) {
  1100. return ( index >= 0 && index < attributes.length );
  1101. }
  1102. }
  1103. /**
  1104. * Attributes with generate ID/IDREF attributes
  1105. * //TODO: refactor the ID/REF generation so that it's fixed at introspection
  1106. * and the generators are placed into the Context.
  1107. * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
  1108. * @version $Revision: 1.28 $
  1109. */
  1110. private class IDElementAttributes extends ElementAttributes {
  1111. /** ID attribute value */
  1112. private String idValue;
  1113. /** ID attribute name */
  1114. private String idAttributeName;
  1115. private boolean matchingAttribute = false;
  1116. private int length;
  1117. private int idIndex;
  1118. /**
  1119. * Construct attributes for element and context.
  1120. *
  1121. * @param descriptor the <code>ElementDescriptor</code> describing the element
  1122. * @param context evaluate against this context
  1123. * @param idAttributeName the name of the id attribute
  1124. * @param idValue the ID attribute value
  1125. */
  1126. IDElementAttributes(
  1127. ElementDescriptor descriptor,
  1128. Context context,
  1129. String idAttributeName,
  1130. String idValue) {
  1131. super(descriptor, context);
  1132. this.idValue = idValue;
  1133. this.idAttributeName = idAttributeName;
  1134. // see if we have already have a matching attribute descriptor
  1135. AttributeDescriptor[] attributeDescriptors = descriptor.getAttributeDescriptors();
  1136. length = attributeDescriptors.length;
  1137. for (int i=0; i<length; i++) {
  1138. if (idAttributeName.equals(attributeDescriptors[i])) {
  1139. matchingAttribute = true;
  1140. idIndex = i;
  1141. break;
  1142. }
  1143. }
  1144. if (!matchingAttribute) {
  1145. length += 1;
  1146. idIndex = length-1;
  1147. }
  1148. }
  1149. public int getIndex(String uri, String localName) {
  1150. if (localName.equals(idAttributeName)) {
  1151. return idIndex;
  1152. }
  1153. return super.getIndex(uri, localName);
  1154. }
  1155. public int getIndex(String qName) {
  1156. if (qName.equals(idAttributeName)) {
  1157. return idIndex;
  1158. }
  1159. return super.getIndex(qName);
  1160. }
  1161. public int getLength() {
  1162. return length;
  1163. }
  1164. public String getLocalName(int index) {
  1165. if (index == idIndex) {
  1166. return idAttributeName;
  1167. }
  1168. return super.getLocalName(index);
  1169. }
  1170. public String getQName(int index) {
  1171. if (index == idIndex) {
  1172. return idAttributeName;
  1173. }
  1174. return super.getQName(index);
  1175. }
  1176. public String getType(int index) {
  1177. if (index == idIndex) {
  1178. return "ID";
  1179. }
  1180. return super.getType(index);
  1181. }
  1182. public String getType(String uri, String localName) {
  1183. return getType(getIndex(uri, localName));
  1184. }
  1185. public String getType(String qName) {
  1186. return getType(getIndex(qName));
  1187. }
  1188. public String getURI(int index) {
  1189. //TODO: this is probably wrong
  1190. // probably need to move ID management into introspection
  1191. // before we can handle this namespace bit correctly
  1192. if (index == idIndex) {
  1193. return "";
  1194. }
  1195. return super.getURI(index);
  1196. }
  1197. public String getValue(int index) {
  1198. if (index == idIndex) {
  1199. return idValue;
  1200. }
  1201. return super.getValue(index);
  1202. }
  1203. public String getValue(String uri, String localName) {
  1204. return getValue(getIndex(uri, localName));
  1205. }
  1206. public String getValue(String qName) {
  1207. return getValue(getIndex(qName));
  1208. }
  1209. }
  1210. // OLD API (DEPRECATED)
  1211. // --------------------------------------------------------------------------------------
  1212. /**
  1213. * Get the indentation for the current element.
  1214. * Used for pretty priting.
  1215. *
  1216. * @return the amount that the current element is indented
  1217. * @deprecated 0.5 replaced by new SAX inspired API
  1218. */
  1219. protected int getIndentLevel() {
  1220. return 0;
  1221. }
  1222. // Expression methods
  1223. //-------------------------------------------------------------------------
  1224. /**
  1225. * Express an element tag start using given qualified name.
  1226. *
  1227. * @param qualifiedName the qualified name of the element to be expressed
  1228. * @throws IOException if an IO problem occurs during writing
  1229. * @throws SAXException if an SAX problem occurs during writing
  1230. * @deprecated 0.5 replaced by new SAX inspired API
  1231. */
  1232. protected void expressElementStart(String qualifiedName)
  1233. throws IOException, SAXException {
  1234. // do nothing
  1235. }
  1236. /**
  1237. * Express an element tag start using given qualified name.
  1238. *
  1239. * @param uri the namespace uri
  1240. * @param localName the local name for this element
  1241. * @param qualifiedName the qualified name of the element to be expressed
  1242. * @throws IOException if an IO problem occurs during writing
  1243. * @throws SAXException if an SAX problem occurs during writing
  1244. * @deprecated 0.5 replaced by new SAX inspired API
  1245. */
  1246. protected void expressElementStart(String uri, String localName, String qualifiedName)
  1247. throws IOException, SAXException {
  1248. expressElementStart( qualifiedName );
  1249. }
  1250. /**
  1251. * Express a closing tag.
  1252. *
  1253. * @throws IOException if an IO problem occurs during writing
  1254. * @throws SAXException if an SAX problem occurs during writing
  1255. * @deprecated 0.5 replaced by new SAX inspired API
  1256. */
  1257. protected void expressTagClose() throws IOException, SAXException {}
  1258. /**
  1259. * Express an element end tag (with given name)
  1260. *
  1261. * @param qualifiedName the qualified name for the element to be closed
  1262. *
  1263. * @throws IOException if an IO problem occurs during writing
  1264. * @throws SAXException if an SAX problem occurs during writing
  1265. * @deprecated 0.5 replaced by new SAX inspired API
  1266. */
  1267. protected void expressElementEnd(String qualifiedName)
  1268. throws IOException, SAXException {
  1269. // do nothing
  1270. }
  1271. /**
  1272. * Express an element end tag (with given name)
  1273. *
  1274. * @param uri the namespace uri of the element close tag
  1275. * @param localName the local name of the element close tag
  1276. * @param qualifiedName the qualified name for the element to be closed
  1277. *
  1278. * @throws IOException if an IO problem occurs during writing
  1279. * @throws SAXException if an SAX problem occurs during writing
  1280. * @deprecated 0.5 replaced by new SAX inspired API
  1281. */
  1282. protected void expressElementEnd(
  1283. String uri,
  1284. String localName,
  1285. String qualifiedName)
  1286. throws
  1287. IOException,
  1288. SAXException {
  1289. expressElementEnd(qualifiedName);
  1290. }
  1291. /**
  1292. * Express an empty element end.
  1293. *
  1294. * @throws IOException if an IO problem occurs during writing
  1295. * @throws SAXException if an SAX problem occurs during writing
  1296. * @deprecated 0.5 replaced by new SAX inspired API
  1297. */
  1298. protected void expressElementEnd() throws IOException, SAXException {}
  1299. /**
  1300. * Express body text
  1301. *
  1302. * @param text the string to write out as the body of the current element
  1303. *
  1304. * @throws IOException if an IO problem occurs during writing
  1305. * @throws SAXException if an SAX problem occurs during writing
  1306. * @deprecated 0.5 replaced by new SAX inspired API
  1307. */
  1308. protected void expressBodyText(String text) throws IOException, SAXException {}
  1309. /**
  1310. * Express an attribute
  1311. *
  1312. * @param qualifiedName the qualified name of the attribute
  1313. * @param value the attribute value
  1314. * @throws IOException if an IO problem occurs during writing
  1315. * @throws SAXException if an SAX problem occurs during writing
  1316. * @deprecated 0.5 replaced by new SAX inspired API
  1317. */
  1318. protected void expressAttribute(
  1319. String qualifiedName,
  1320. String value)
  1321. throws
  1322. IOException,
  1323. SAXException {
  1324. // Do nothing
  1325. }
  1326. /**
  1327. * Express an attribute
  1328. *
  1329. * @param namespaceUri the namespace uri
  1330. * @param localName the local name
  1331. * @param qualifiedName the qualified name of the attribute
  1332. * @param value the attribute value
  1333. * @throws IOException if an IO problem occurs during writing
  1334. * @throws SAXException if an SAX problem occurs during writing
  1335. * @deprecated 0.5 replaced by new SAX inspired API
  1336. */
  1337. protected void expressAttribute(
  1338. String namespaceUri,
  1339. String localName,
  1340. String qualifiedName,
  1341. String value)
  1342. throws
  1343. IOException,
  1344. SAXException {
  1345. expressAttribute(qualifiedName, value);
  1346. }
  1347. /**
  1348. * Writes the given element
  1349. *
  1350. * @param qualifiedName qualified name to use for the element
  1351. * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
  1352. * @param context the <code>Context</code> to use to evaluate the bean expressions
  1353. * @throws IOException if an IO problem occurs during writing
  1354. * @throws SAXException if an SAX problem occurs during writing
  1355. * @throws IntrospectionException if a java beans introspection problem occurs
  1356. * @deprecated 0.5 replaced by new SAX inspired API
  1357. */
  1358. protected void write(
  1359. String qualifiedName,
  1360. ElementDescriptor elementDescriptor,
  1361. Context context )
  1362. throws
  1363. IOException,
  1364. SAXException,
  1365. IntrospectionException {
  1366. writeElement( "", qualifiedName, qualifiedName, elementDescriptor, context );
  1367. }
  1368. /**
  1369. * Writes the given element adding an ID attribute
  1370. *
  1371. * @param qualifiedName qualified name to use for the element
  1372. * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
  1373. * @param context the <code>Context</code> to use to evaluate the bean expressions
  1374. * @param idAttribute the qualified name of the <code>ID</code> attribute
  1375. * @param idValue the value for the <code>ID</code> attribute
  1376. * @throws IOException if an IO problem occurs during writing
  1377. * @throws SAXException if an SAX problem occurs during writing
  1378. * @throws IntrospectionException if a java beans introspection problem occurs
  1379. * @deprecated 0.5 replaced by new SAX inspired API
  1380. */
  1381. protected void write(
  1382. String qualifiedName,
  1383. ElementDescriptor elementDescriptor,
  1384. Context context,
  1385. String idAttribute,
  1386. String idValue )
  1387. throws
  1388. IOException,
  1389. SAXException,
  1390. IntrospectionException {
  1391. writeElement(
  1392. "",
  1393. qualifiedName,
  1394. qualifiedName,
  1395. elementDescriptor,
  1396. context,
  1397. idAttribute,
  1398. idValue );
  1399. }
  1400. /**
  1401. * Write attributes, child elements and element end
  1402. *
  1403. * @param qualifiedName qualified name to use for the element
  1404. * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
  1405. * @param context the <code>Context</code> to use to evaluate the bean expressions
  1406. * @throws IOException if an IO problem occurs during writing
  1407. * @throws SAXException if an SAX problem occurs during writing
  1408. * @throws IntrospectionException if a java beans introspection problem occurs
  1409. * @deprecated 0.5 replaced by new SAX inspired API
  1410. */
  1411. protected void writeRestOfElement(
  1412. String qualifiedName,
  1413. ElementDescriptor elementDescriptor,
  1414. Context context )
  1415. throws
  1416. IOException,
  1417. SAXException,
  1418. IntrospectionException {
  1419. writeRestOfElement( "", qualifiedName, qualifiedName, elementDescriptor, context );
  1420. }
  1421. /**
  1422. * Writes an element with a <code>IDREF</code> attribute
  1423. *
  1424. * @param qualifiedName of the element with <code>IDREF</code> attribute
  1425. * @param idrefAttributeName the qualified name of the <code>IDREF</code> attribute
  1426. * @param idrefAttributeValue the value for the <code>IDREF</code> attribute
  1427. * @throws IOException if an IO problem occurs during writing
  1428. * @throws SAXException if an SAX problem occurs during writing
  1429. * @throws IntrospectionException if a java beans introspection problem occurs
  1430. * @deprecated 0.5 replaced by new SAX inspired API
  1431. */
  1432. protected void writeIDREFElement(
  1433. String qualifiedName,
  1434. String idrefAttributeName,
  1435. String idrefAttributeValue )
  1436. throws
  1437. IOException,
  1438. SAXException,
  1439. IntrospectionException {
  1440. // deprecated
  1441. AttributesImpl attributes = new AttributesImpl();
  1442. attributes.addAttribute(
  1443. "",
  1444. idrefAttributeName,
  1445. idrefAttributeName,
  1446. "IDREF",
  1447. idrefAttributeValue);
  1448. startElement( "", qualifiedName, qualifiedName, attributes);
  1449. endElement( "", qualifiedName, qualifiedName );
  1450. }
  1451. /**
  1452. * Writes the element content.
  1453. *
  1454. * @param elementDescriptor the <code>ElementDescriptor</code> to write as xml
  1455. * @param context the <code>Context</code> to use to evaluate the bean expressions
  1456. * @return true if some content was written
  1457. * @throws IOException if an IO problem occurs during writing
  1458. * @throws SAXException if an SAX problem occurs during writing
  1459. * @throws IntrospectionException if a java beans introspection problem occurs
  1460. * @deprecated 0.5 replaced by new SAX inspired API
  1461. */
  1462. protected boolean writeContent(
  1463. ElementDescriptor elementDescriptor,
  1464. Context context )
  1465. throws
  1466. IOException,
  1467. SAXException,
  1468. IntrospectionException {
  1469. return false;
  1470. }
  1471. /**
  1472. * Writes the attribute declarations
  1473. *
  1474. * @param elementDescriptor the <code>ElementDescriptor</code> to be written out as xml
  1475. * @param context the <code>Context</code> to use to evaluation bean expressions
  1476. * @throws IOException if an IO problem occurs during writing
  1477. * @throws SAXException if an SAX problem occurs during writing
  1478. * @deprecated 0.5 replaced by new SAX inspired API
  1479. */
  1480. protected void writeAttributes(
  1481. ElementDescriptor elementDescriptor,
  1482. Context context )
  1483. throws
  1484. IOException, SAXException {
  1485. if (!elementDescriptor.isWrapCollectionsInElement()) {
  1486. return;
  1487. }
  1488. AttributeDescriptor[] attributeDescriptors = elementDescriptor.getAttributeDescriptors();
  1489. if ( attributeDescriptors != null ) {
  1490. for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) {
  1491. AttributeDescriptor attributeDescriptor = attributeDescriptors[i];
  1492. writeAttribute( attributeDescriptor, context );
  1493. }
  1494. }
  1495. }
  1496. /**
  1497. * Writes an attribute declaration
  1498. *
  1499. * @param attributeDescriptor the <code>AttributeDescriptor</code> to be written as xml
  1500. * @param context the <code>Context</code> to use to evaluation bean expressions
  1501. * @throws IOException if an IO problem occurs during writing
  1502. * @throws SAXException if an SAX problem occurs during writing
  1503. * @deprecated 0.5 replaced by new SAX inspired API
  1504. */
  1505. protected void writeAttribute(
  1506. AttributeDescriptor attributeDescriptor,
  1507. Context context )
  1508. throws
  1509. IOException, SAXException {
  1510. Expression expression = attributeDescriptor.getTextExpression();
  1511. if ( expression != null ) {
  1512. Object value = expression.evaluate( context );
  1513. if ( value != null ) {
  1514. String text = value.toString();
  1515. if ( text != null && text.length() > 0 ) {
  1516. expressAttribute(
  1517. attributeDescriptor.getURI(),
  1518. attributeDescriptor.getLocalName(),
  1519. attributeDescriptor.getQualifiedName(),
  1520. text);
  1521. }
  1522. }
  1523. }
  1524. }
  1525. /**
  1526. * Writes a empty line.
  1527. * This implementation does nothing but can be overridden by subclasses.
  1528. *
  1529. * @throws IOException if the line cannot be written
  1530. * @deprecated 0.5 replaced by new SAX inspired API
  1531. */
  1532. protected void writePrintln() throws IOException {}
  1533. /**
  1534. * Writes an indentation.
  1535. * This implementation does nothing but can be overridden by subclasses.
  1536. *
  1537. * @throws IOException if the indent cannot be written
  1538. * @deprecated 0.5 replaced by new BeanWriter API
  1539. */
  1540. protected void writeIndent() throws IOException {}
  1541. /**
  1542. * Converts an object to a string.
  1543. *
  1544. * @param value the Object to represent as a String, possibly null
  1545. * @param descriptor writing out this descriptor not null
  1546. * @param context not null
  1547. * @return String representation, not null
  1548. */
  1549. private String convertToString( Object value , Descriptor descriptor, Context context ) {
  1550. return getBindingConfiguration()
  1551. .getObjectStringConverter()
  1552. .objectToString( value, descriptor.getPropertyType(), null, context );
  1553. }
  1554. /**
  1555. * Factory method for new contexts.
  1556. * Ensure that they are correctly configured.
  1557. * @param bean make a new Context for this bean
  1558. * @return not null
  1559. */
  1560. private Context makeContext(Object bean) {
  1561. return new Context( bean, log, bindingConfiguration );
  1562. }
  1563. /**
  1564. * Basic mutable implementation of <code>WriteContext</code>.
  1565. */
  1566. private static class WriteContextImpl extends WriteContext {
  1567. private ElementDescriptor currentDescriptor;
  1568. /**
  1569. * @see org.apache.commons.betwixt.io.WriteContext#getCurrentDescriptor()
  1570. */
  1571. public ElementDescriptor getCurrentDescriptor() {
  1572. return currentDescriptor;
  1573. }
  1574. /**
  1575. * Sets the descriptor for the current element.
  1576. * @param currentDescriptor
  1577. */
  1578. public void setCurrentDescriptor(ElementDescriptor currentDescriptor) {
  1579. this.currentDescriptor = currentDescriptor;
  1580. }
  1581. }
  1582. }