0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.commons.betwixt.io;
0018:
0019: import java.beans.IntrospectionException;
0020: import java.io.IOException;
0021: import java.util.ArrayList;
0022: import java.util.Collection;
0023: import java.util.Iterator;
0024:
0025: import org.apache.commons.betwixt.AttributeDescriptor;
0026: import org.apache.commons.betwixt.BindingConfiguration;
0027: import org.apache.commons.betwixt.Descriptor;
0028: import org.apache.commons.betwixt.ElementDescriptor;
0029: import org.apache.commons.betwixt.Options;
0030: import org.apache.commons.betwixt.XMLBeanInfo;
0031: import org.apache.commons.betwixt.XMLIntrospector;
0032: import org.apache.commons.betwixt.expression.Context;
0033: import org.apache.commons.betwixt.expression.Expression;
0034: import org.apache.commons.betwixt.io.id.SequentialIDGenerator;
0035: import org.apache.commons.collections.ArrayStack;
0036: import org.apache.commons.logging.Log;
0037: import org.apache.commons.logging.LogFactory;
0038: import org.xml.sax.Attributes;
0039: import org.xml.sax.InputSource;
0040: import org.xml.sax.SAXException;
0041: import org.xml.sax.helpers.AttributesImpl;
0042:
0043: /**
0044: * <p>Abstract superclass for bean writers.
0045: * This class encapsulates the processing logic.
0046: * Subclasses provide implementations for the actual expression of the xml.</p>
0047: * <h5>SAX Inspired Writing API</h5>
0048: * <p>
0049: * This class is intended to be used by subclassing:
0050: * concrete subclasses perform the actual writing by providing
0051: * suitable implementations for the following methods inspired
0052: * by <a href='http://www.saxproject.org'>SAX</a>:
0053: * </p>
0054: * <ul>
0055: * <li> {@link #start} - called when processing begins</li>
0056: * <li> {@link #startElement(WriteContext, String, String, String, Attributes)}
0057: * - called when the start of an element
0058: * should be written</li>
0059: * <li> {@link #bodyText(WriteContext, String)}
0060: * - called when the start of an element
0061: * should be written</li>
0062: * <li> {@link #endElement(WriteContext, String, String, String)}
0063: * - called when the end of an element
0064: * should be written</li>
0065: * <li> {@link #end} - called when processing has been completed</li>
0066: * </ul>
0067: * <p>
0068: * <strong>Note</strong> that this class contains many deprecated
0069: * versions of the writing API. These will be removed soon so care
0070: * should be taken to use the latest version.
0071: * </p>
0072: * <p>
0073: * <strong>Note</strong> that this class is designed to be used
0074: * in a single threaded environment. When used in multi-threaded
0075: * environments, use of a common <code>XMLIntrospector</code>
0076: * and pooled writer instances should be considered.
0077: * </p>
0078: *
0079: * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
0080: */
0081: public abstract class AbstractBeanWriter {
0082:
0083: /** Introspector used */
0084: private XMLIntrospector introspector = new XMLIntrospector();
0085:
0086: /** Log used for logging (Doh!) */
0087: private Log log = LogFactory.getLog(AbstractBeanWriter.class);
0088: /** Stack containing beans - used to detect cycles */
0089: private ArrayStack beanStack = new ArrayStack();
0090: /** Used to generate ID attribute values*/
0091: private IDGenerator idGenerator = new SequentialIDGenerator();
0092: /** Should empty elements be written out? */
0093: private boolean writeEmptyElements = true;
0094: /** Dynamic binding configuration settings */
0095: private BindingConfiguration bindingConfiguration = new BindingConfiguration();
0096: /** <code>WriteContext</code> implementation reused curing writing */
0097: private WriteContextImpl writeContext = new WriteContextImpl();
0098: /** Collection of namespaces which have already been declared */
0099: private Collection namespacesDeclared = new ArrayList();
0100:
0101: /**
0102: * Marks the start of the bean writing.
0103: * By default doesn't do anything, but can be used
0104: * to do extra start processing
0105: * @throws IOException if an IO problem occurs during writing
0106: * @throws SAXException if an SAX problem occurs during writing
0107: */
0108: public void start() throws IOException, SAXException {
0109: }
0110:
0111: /**
0112: * Marks the start of the bean writing.
0113: * By default doesn't do anything, but can be used
0114: * to do extra end processing
0115: * @throws IOException if an IO problem occurs during writing
0116: * @throws SAXException if an SAX problem occurs during writing
0117: */
0118:
0119: public void end() throws IOException, SAXException {
0120: }
0121:
0122: /**
0123: * <p> Writes the given bean to the current stream using the XML introspector.</p>
0124: *
0125: * <p> This writes an xml fragment representing the bean to the current stream.</p>
0126: *
0127: * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
0128: * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
0129: * setting of the </code>BindingConfiguration</code> is false.</p>
0130: *
0131: * @throws IOException if an IO problem occurs during writing
0132: * @throws SAXException if an SAX problem occurs during writing
0133: * @throws IntrospectionException if a java beans introspection problem occurs
0134: *
0135: * @param bean write out representation of this bean
0136: */
0137: public void write(Object bean) throws IOException, SAXException,
0138: IntrospectionException {
0139: if (log.isDebugEnabled()) {
0140: log.debug("Writing bean graph...");
0141: log.debug(bean);
0142: }
0143: start();
0144: writeBean(null, null, null, bean, makeContext(bean));
0145: end();
0146: if (log.isDebugEnabled()) {
0147: log.debug("Finished writing bean graph.");
0148: }
0149: }
0150:
0151: /**
0152: * <p>Writes the given bean to the current stream
0153: * using the given <code>qualifiedName</code>.</p>
0154: *
0155: * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
0156: * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
0157: * setting of the <code>BindingConfiguration</code> is false.</p>
0158: *
0159: * @param qualifiedName the string naming root element
0160: * @param bean the <code>Object</code> to write out as xml
0161: *
0162: * @throws IOException if an IO problem occurs during writing
0163: * @throws SAXException if an SAX problem occurs during writing
0164: * @throws IntrospectionException if a java beans introspection problem occurs
0165: */
0166: public void write(String qualifiedName, Object bean)
0167: throws IOException, SAXException, IntrospectionException {
0168: start();
0169: writeBean("", qualifiedName, qualifiedName, bean,
0170: makeContext(bean));
0171: end();
0172: }
0173:
0174: /**
0175: * <p>Writes the bean using the mapping specified in the <code>InputSource</code>.
0176: * </p><p>
0177: * <strong>Note:</strong> that the custom mapping will <em>not</em>
0178: * be registered for later use. Please use {@link XMLIntrospector#register}
0179: * to register the custom mapping for the class and then call
0180: * {@link #write(Object)}.
0181: * </p>
0182: * @see #write(Object) since the standard notes also apply
0183: * @since 0.7
0184: * @param bean <code>Object</code> to be written as xml, not null
0185: * @param source <code>InputSource/code> containing an xml document
0186: * specifying the mapping to be used (in the usual way), not null
0187: * @throws IOException
0188: * @throws SAXException
0189: * @throws IntrospectionException
0190: */
0191: public void write(Object bean, InputSource source)
0192: throws IOException, SAXException, IntrospectionException {
0193: writeBean(null, null, null, bean, makeContext(bean),
0194: getXMLIntrospector()
0195: .introspect(bean.getClass(), source));
0196: }
0197:
0198: /**
0199: * <p>Writes the given bean to the current stream
0200: * using the given <code>qualifiedName</code>.</p>
0201: *
0202: * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
0203: * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
0204: * setting of the <code>BindingConfiguration</code> is false.</p>
0205: *
0206: * @param namespaceUri the namespace uri
0207: * @param localName the local name
0208: * @param qualifiedName the string naming root element
0209: * @param introspectedBindType the <code>Class</code> of the bean
0210: * as resolved at introspection time, or null if the type has not been resolved
0211: * @param bean the <code>Object</code> to write out as xml
0212: * @param context not null
0213: *
0214: * @throws IOException if an IO problem occurs during writing
0215: * @throws SAXException if an SAX problem occurs during writing
0216: * @throws IntrospectionException if a java beans introspection problem occurs
0217: */
0218: private void writeBean(String namespaceUri, String localName,
0219: String qualifiedName, Object bean, Context context)
0220: throws IOException, SAXException, IntrospectionException {
0221:
0222: if (log.isTraceEnabled()) {
0223: log.trace("Writing bean graph (qualified name '"
0224: + qualifiedName + "'");
0225: }
0226:
0227: // introspect to obtain bean info
0228: XMLBeanInfo beanInfo = introspector.introspect(bean);
0229: writeBean(namespaceUri, localName, qualifiedName, bean,
0230: context, beanInfo);
0231:
0232: log.trace("Finished writing bean graph.");
0233: }
0234:
0235: private void writeBean(String namespaceUri, String localName,
0236: String qualifiedName, Object bean,
0237: ElementDescriptor parentDescriptor, Context context)
0238: throws IOException, SAXException, IntrospectionException {
0239:
0240: if (log.isTraceEnabled()) {
0241: log.trace("Writing bean graph (qualified name '"
0242: + qualifiedName + "'");
0243: }
0244:
0245: // introspect to obtain bean info
0246: XMLBeanInfo beanInfo = findXMLBeanInfo(bean, parentDescriptor);
0247: writeBean(namespaceUri, localName, qualifiedName, bean,
0248: context, beanInfo);
0249:
0250: log.trace("Finished writing bean graph.");
0251: }
0252:
0253: /**
0254: * Finds the appropriate bean info for the given (hollow) element.
0255: * @param bean
0256: * @param parentDescriptor <code>ElementDescriptor</code>, not null
0257: * @return <code>XMLBeanInfo</code>, not null
0258: * @throws IntrospectionException
0259: */
0260: private XMLBeanInfo findXMLBeanInfo(Object bean,
0261: ElementDescriptor parentDescriptor)
0262: throws IntrospectionException {
0263: XMLBeanInfo beanInfo = null;
0264: Class introspectedBindType = parentDescriptor
0265: .getSingularPropertyType();
0266: if (introspectedBindType == null) {
0267: introspectedBindType = parentDescriptor.getPropertyType();
0268: }
0269: if (parentDescriptor.isUseBindTimeTypeForMapping()
0270: || introspectedBindType == null) {
0271: beanInfo = introspector.introspect(bean);
0272: } else {
0273: beanInfo = introspector.introspect(introspectedBindType);
0274: }
0275: return beanInfo;
0276: }
0277:
0278: /**
0279: * <p>Writes the given bean to the current stream
0280: * using the given mapping.</p>
0281: *
0282: * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
0283: * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
0284: * setting of the <code>BindingConfiguration</code> is false.</p>
0285: *
0286: * @param namespaceUri the namespace uri, or null to use the automatic binding
0287: * @param localName the local name or null to use the automatic binding
0288: * @param qualifiedName the <code>String</code> naming the root element
0289: * or null to use the automatic binding
0290: * @param bean <code>Object</code> to be written, not null
0291: * @param context <code>Context</code>, not null
0292: * @param beanInfo <code>XMLBeanInfo</code>, not null
0293: * @throws IOException
0294: * @throws SAXException
0295: * @throws IntrospectionException
0296: */
0297: private void writeBean(String namespaceUri, String localName,
0298: String qualifiedName, Object bean, Context context,
0299: XMLBeanInfo beanInfo) throws IOException, SAXException,
0300: IntrospectionException {
0301: if (beanInfo != null) {
0302: ElementDescriptor elementDescriptor = beanInfo
0303: .getElementDescriptor();
0304: if (elementDescriptor != null) {
0305:
0306: // Construct the options
0307: Options combinedOptions = new Options();
0308:
0309: // Add options defined by the current bean's element descriptor
0310: combinedOptions.addOptions(elementDescriptor
0311: .getOptions());
0312:
0313: // The parent descriptor may have defined options
0314: // for the current bean. These options take precedence
0315: // over the options of the current class descriptor
0316: if (context.getOptions() != null) {
0317: combinedOptions.addOptions(context.getOptions());
0318: }
0319: context = context.newContext(bean);
0320: context.pushOptions(combinedOptions);
0321:
0322: if (qualifiedName == null) {
0323: qualifiedName = elementDescriptor
0324: .getQualifiedName();
0325: }
0326: if (namespaceUri == null) {
0327: namespaceUri = elementDescriptor.getURI();
0328: }
0329: if (localName == null) {
0330: localName = elementDescriptor.getLocalName();
0331: }
0332:
0333: String ref = null;
0334: String id = null;
0335:
0336: // simple type should not have IDs
0337: if (elementDescriptor.isSimple()) {
0338: // write without an id
0339: writeElement(namespaceUri, localName,
0340: qualifiedName, elementDescriptor, context);
0341:
0342: } else {
0343: pushBean(context.getBean());
0344: if (getBindingConfiguration().getMapIDs()) {
0345: ref = getBindingConfiguration()
0346: .getIdMappingStrategy()
0347: .getReferenceFor(context,
0348: context.getBean());
0349: }
0350: if (ref == null) {
0351: // this is the first time that this bean has be written
0352: AttributeDescriptor idAttribute = beanInfo
0353: .getIDAttribute();
0354: if (idAttribute == null) {
0355: // use a generated id
0356: id = idGenerator.nextId();
0357: getBindingConfiguration()
0358: .getIdMappingStrategy()
0359: .setReference(context, bean, id);
0360:
0361: if (getBindingConfiguration().getMapIDs()) {
0362: // write element with id
0363: writeElement(namespaceUri, localName,
0364: qualifiedName,
0365: elementDescriptor, context,
0366: beanInfo.getIDAttributeName(),
0367: id);
0368:
0369: } else {
0370: // write element without ID
0371: writeElement(namespaceUri, localName,
0372: qualifiedName,
0373: elementDescriptor, context);
0374: }
0375:
0376: } else {
0377: // use id from bean property
0378: // it's up to the user to ensure uniqueness
0379: Expression idExpression = idAttribute
0380: .getTextExpression();
0381: if (idExpression == null) {
0382: throw new IntrospectionException(
0383: "The specified id property wasn't found in the bean ("
0384: + idAttribute + ").");
0385: }
0386: Object exp = idExpression.evaluate(context);
0387: if (exp == null) {
0388: // we'll use a random id
0389: log.debug("Using random id");
0390: id = idGenerator.nextId();
0391:
0392: } else {
0393: // convert to string
0394: id = exp.toString();
0395: }
0396: getBindingConfiguration()
0397: .getIdMappingStrategy()
0398: .setReference(context, bean, id);
0399:
0400: // the ID attribute should be written automatically
0401: writeElement(namespaceUri, localName,
0402: qualifiedName, elementDescriptor,
0403: context);
0404: }
0405: } else {
0406:
0407: if (!ignoreElement(elementDescriptor,
0408: namespaceUri, localName, qualifiedName,
0409: context)) {
0410: // we've already written this bean so write an IDREF
0411: writeIDREFElement(elementDescriptor,
0412: namespaceUri, localName,
0413: qualifiedName, beanInfo
0414: .getIDREFAttributeName(),
0415: ref);
0416: }
0417: }
0418: popBean();
0419: }
0420:
0421: context.popOptions();
0422: }
0423: }
0424: }
0425:
0426: /**
0427: * Get <code>IDGenerator</code> implementation used to
0428: * generate <code>ID</code> attribute values .
0429: *
0430: * @return implementation used for <code>ID</code> attribute generation
0431: */
0432: public IDGenerator getIdGenerator() {
0433: return idGenerator;
0434: }
0435:
0436: /**
0437: * Set <code>IDGenerator</code> implementation
0438: * used to generate <code>ID</code> attribute values.
0439: * This property can be used to customize the algorithm used for generation.
0440: *
0441: * @param idGenerator use this implementation for <code>ID</code> attribute generation
0442: */
0443: public void setIdGenerator(IDGenerator idGenerator) {
0444: this .idGenerator = idGenerator;
0445: }
0446:
0447: /**
0448: * Gets the dynamic configuration setting to be used for bean reading.
0449: * @return the BindingConfiguration settings, not null
0450: * @since 0.5
0451: */
0452: public BindingConfiguration getBindingConfiguration() {
0453: return bindingConfiguration;
0454: }
0455:
0456: /**
0457: * Sets the dynamic configuration setting to be used for bean reading.
0458: * @param bindingConfiguration the BindingConfiguration settings, not null
0459: * @since 0.5
0460: */
0461: public void setBindingConfiguration(
0462: BindingConfiguration bindingConfiguration) {
0463: this .bindingConfiguration = bindingConfiguration;
0464: }
0465:
0466: /**
0467: * <p>Should generated <code>ID</code> attribute values be added to the elements?</p>
0468: *
0469: * <p>If IDs are not being written then if a cycle is encountered in the bean graph,
0470: * then a {@link CyclicReferenceException} will be thrown by the write method.</p>
0471: *
0472: * @return true if <code>ID</code> and <code>IDREF</code> attributes are to be written
0473: * @deprecated 0.5 use {@link BindingConfiguration#getMapIDs}
0474: */
0475: public boolean getWriteIDs() {
0476: return getBindingConfiguration().getMapIDs();
0477: }
0478:
0479: /**
0480: * Set whether generated <code>ID</code> attribute values should be added to the elements
0481: * If this property is set to false, then <code>CyclicReferenceException</code>
0482: * will be thrown whenever a cyclic occurs in the bean graph.
0483: *
0484: * @param writeIDs true if <code>ID</code>'s and <code>IDREF</code>'s should be written
0485: * @deprecated 0.5 use {@link BindingConfiguration#setMapIDs}
0486: */
0487: public void setWriteIDs(boolean writeIDs) {
0488: getBindingConfiguration().setMapIDs(writeIDs);
0489: }
0490:
0491: /**
0492: * <p>Gets whether empty elements should be written into the output.</p>
0493: *
0494: * <p>An empty element is one that has no attributes, no child elements
0495: * and no body text.
0496: * For example, <code><element/></code> is an empty element but
0497: * <code><element attr='value'/></code> is not.</p>
0498: *
0499: * @return true if empty elements will be written into the output
0500: * @since 0.5
0501: */
0502: public boolean getWriteEmptyElements() {
0503: return writeEmptyElements;
0504: }
0505:
0506: /**
0507: * <p>Sets whether empty elements should be written into the output.</p>
0508: *
0509: * <p>An empty element is one that has no attributes, no child elements
0510: * and no body text.
0511: * For example, <code><element/></code> is an empty element but
0512: * <code><element attr='value'/></code> is not.
0513: *
0514: * @param writeEmptyElements true if empty elements should be written into the output
0515: * @since 0.5
0516: */
0517: public void setWriteEmptyElements(boolean writeEmptyElements) {
0518: this .writeEmptyElements = writeEmptyElements;
0519: }
0520:
0521: /**
0522: * <p>Gets the introspector used.</p>
0523: *
0524: * <p>The {@link XMLBeanInfo} used to map each bean is
0525: * created by the <code>XMLIntrospector</code>.
0526: * One way in which the mapping can be customized is
0527: * by altering the <code>XMLIntrospector</code>. </p>
0528: *
0529: * @return the <code>XMLIntrospector</code> used for introspection
0530: */
0531: public XMLIntrospector getXMLIntrospector() {
0532: return introspector;
0533: }
0534:
0535: /**
0536: * <p>Sets the introspector to be used.</p>
0537: *
0538: * <p>The {@link XMLBeanInfo} used to map each bean is
0539: * created by the <code>XMLIntrospector</code>.
0540: * One way in which the mapping can be customized is by
0541: * altering the <code>XMLIntrospector</code>. </p>
0542: *
0543: * @param introspector use this introspector
0544: */
0545: public void setXMLIntrospector(XMLIntrospector introspector) {
0546: this .introspector = introspector;
0547: }
0548:
0549: /**
0550: * <p>Gets the current logging implementation.</p>
0551: *
0552: * @return the <code>Log</code> implementation which this class logs to
0553: */
0554: public final Log getAbstractBeanWriterLog() {
0555: return log;
0556: }
0557:
0558: /**
0559: * <p> Set the current logging implementation. </p>
0560: *
0561: * @param log <code>Log</code> implementation to use
0562: */
0563: public final void setAbstractBeanWriterLog(Log log) {
0564: this .log = log;
0565: }
0566:
0567: // SAX-style methods
0568: //-------------------------------------------------------------------------
0569:
0570: /**
0571: * Writes the start tag for an element.
0572: *
0573: * @param uri the element's namespace uri
0574: * @param localName the element's local name
0575: * @param qName the element's qualified name
0576: * @param attr the element's attributes
0577: *
0578: * @throws IOException if an IO problem occurs during writing
0579: * @throws SAXException if an SAX problem occurs during writing
0580: * @since 0.5
0581: */
0582: protected void startElement(WriteContext context, String uri,
0583: String localName, String qName, Attributes attr)
0584: throws IOException, SAXException {
0585: // for backwards compatbility call older methods
0586: startElement(uri, localName, qName, attr);
0587: }
0588:
0589: /**
0590: * Writes the end tag for an element
0591: *
0592: * @param uri the element's namespace uri
0593: * @param localName the element's local name
0594: * @param qName the element's qualified name
0595: *
0596: * @throws IOException if an IO problem occurs during writing
0597: * @throws SAXException if an SAX problem occurs during writing
0598: * @since 0.5
0599: */
0600: protected void endElement(WriteContext context, String uri,
0601: String localName, String qName) throws IOException,
0602: SAXException {
0603: // for backwards compatibility call older interface
0604: endElement(uri, localName, qName);
0605: }
0606:
0607: /**
0608: * Writes body text
0609: *
0610: * @param text the body text to be written
0611: *
0612: * @throws IOException if an IO problem occurs during writing
0613: * @throws SAXException if an SAX problem occurs during writing
0614: * @since 0.5
0615: */
0616: protected void bodyText(WriteContext context, String text)
0617: throws IOException, SAXException {
0618: // for backwards compatibility call older interface
0619: bodyText(text);
0620: }
0621:
0622: // Older SAX-style methods
0623: //-------------------------------------------------------------------------
0624:
0625: /**
0626: * Writes the start tag for an element.
0627: *
0628: * @param uri the element's namespace uri
0629: * @param localName the element's local name
0630: * @param qName the element's qualified name
0631: * @param attr the element's attributes
0632: *
0633: * @throws IOException if an IO problem occurs during writing
0634: * @throws SAXException if an SAX problem occurs during writing
0635: * @deprecated 0.5 use {@link #startElement(WriteContext, String, String, String, Attributes)}
0636: */
0637: protected void startElement(String uri, String localName,
0638: String qName, Attributes attr) throws IOException,
0639: SAXException {
0640: }
0641:
0642: /**
0643: * Writes the end tag for an element
0644: *
0645: * @param uri the element's namespace uri
0646: * @param localName the element's local name
0647: * @param qName the element's qualified name
0648: *
0649: * @throws IOException if an IO problem occurs during writing
0650: * @throws SAXException if an SAX problem occurs during writing
0651: * @deprecated 0.5 use {@link #endElement(WriteContext, String, String, String)}
0652: */
0653: protected void endElement(String uri, String localName, String qName)
0654: throws IOException, SAXException {
0655: }
0656:
0657: /**
0658: * Writes body text
0659: *
0660: * @param text the body text to be written
0661: *
0662: * @throws IOException if an IO problem occurs during writing
0663: * @throws SAXException if an SAX problem occurs during writing
0664: * @deprecated 0.5 use {@link #bodyText(WriteContext, String)}
0665: */
0666: protected void bodyText(String text) throws IOException,
0667: SAXException {
0668: }
0669:
0670: // Implementation methods
0671: //-------------------------------------------------------------------------
0672:
0673: /**
0674: * Writes the given element
0675: *
0676: * @param namespaceUri the namespace uri
0677: * @param localName the local name
0678: * @param qualifiedName qualified name to use for the element
0679: * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
0680: * @param context the <code>Context</code> to use to evaluate the bean expressions
0681: * @throws IOException if an IO problem occurs during writing
0682: * @throws SAXException if an SAX problem occurs during writing
0683: * @throws IntrospectionException if a java beans introspection problem occurs
0684: */
0685: private void writeElement(String namespaceUri, String localName,
0686: String qualifiedName, ElementDescriptor elementDescriptor,
0687: Context context) throws IOException, SAXException,
0688: IntrospectionException {
0689: if (log.isTraceEnabled()) {
0690: log.trace("Writing: " + qualifiedName + " element: "
0691: + elementDescriptor);
0692: }
0693:
0694: if (!ignoreElement(elementDescriptor, namespaceUri, localName,
0695: qualifiedName, context)) {
0696: if (log.isTraceEnabled()) {
0697: log
0698: .trace("Element " + elementDescriptor
0699: + " is empty.");
0700: }
0701:
0702: Attributes attributes = addNamespaceDeclarations(
0703: new ElementAttributes(elementDescriptor, context),
0704: namespaceUri);
0705: writeContext.setCurrentDescriptor(elementDescriptor);
0706: startElement(writeContext, namespaceUri, localName,
0707: qualifiedName, attributes);
0708:
0709: writeElementContent(elementDescriptor, context);
0710: writeContext.setCurrentDescriptor(elementDescriptor);
0711: endElement(writeContext, namespaceUri, localName,
0712: qualifiedName);
0713: }
0714: }
0715:
0716: /**
0717: * Adds namespace declarations (if any are needed) to the given attributes.
0718: * @param attributes Attributes, not null
0719: * @param elementNamespaceUri the URI for the enclosing element, possibly null
0720: * @return Attributes, not null
0721: */
0722: private Attributes addNamespaceDeclarations(Attributes attributes,
0723: String elementNamespaceUri) {
0724: Attributes result = attributes;
0725: AttributesImpl withDeclarations = null;
0726: for (int i = -1, size = attributes.getLength(); i < size; i++) {
0727: String uri = null;
0728: if (i == -1) {
0729: uri = elementNamespaceUri;
0730: } else {
0731: uri = attributes.getURI(i);
0732: }
0733: if (uri != null && !"".equals(uri)
0734: && !namespacesDeclared.contains(uri)) {
0735: if (withDeclarations == null) {
0736: withDeclarations = new AttributesImpl(attributes);
0737: }
0738: withDeclarations.addAttribute("", "", "xmlns:"
0739: + getXMLIntrospector().getConfiguration()
0740: .getPrefixMapper().getPrefix(uri),
0741: "NOTATION", uri);
0742: namespacesDeclared.add(uri);
0743: }
0744: }
0745:
0746: if (withDeclarations != null) {
0747: result = withDeclarations;
0748: }
0749: return result;
0750: }
0751:
0752: /**
0753: * Writes the given element adding an ID attribute
0754: *
0755: * @param namespaceUri the namespace uri
0756: * @param localName the local name
0757: * @param qualifiedName the qualified name
0758: * @param elementDescriptor the ElementDescriptor describing this element
0759: * @param context the context being evaliated against
0760: * @param idAttribute the qualified name of the <code>ID</code> attribute
0761: * @param idValue the value for the <code>ID</code> attribute
0762: * @throws IOException if an IO problem occurs during writing
0763: * @throws SAXException if an SAX problem occurs during writing
0764: * @throws IntrospectionException if a java beans introspection problem occurs
0765: */
0766: private void writeElement(String namespaceUri, String localName,
0767: String qualifiedName, ElementDescriptor elementDescriptor,
0768: Context context, String idAttribute, String idValue)
0769: throws IOException, SAXException, IntrospectionException {
0770:
0771: if (!ignoreElement(elementDescriptor, namespaceUri, localName,
0772: qualifiedName, context)) {
0773: writeContext.setCurrentDescriptor(elementDescriptor);
0774: Attributes attributes = new IDElementAttributes(
0775: elementDescriptor, context, idAttribute, idValue);
0776: startElement(writeContext, namespaceUri, localName,
0777: qualifiedName, addNamespaceDeclarations(attributes,
0778: namespaceUri));
0779:
0780: writeElementContent(elementDescriptor, context);
0781: writeContext.setCurrentDescriptor(elementDescriptor);
0782: endElement(writeContext, namespaceUri, localName,
0783: qualifiedName);
0784: } else if (log.isTraceEnabled()) {
0785: log.trace("Element " + qualifiedName + " is empty.");
0786: }
0787: }
0788:
0789: /**
0790: * Write attributes, child elements and element end
0791: *
0792: * @param uri the element namespace uri
0793: * @param localName the local name of the element
0794: * @param qualifiedName the qualified name of the element
0795: * @param elementDescriptor the descriptor for this element
0796: * @param context evaluate against this context
0797: * @throws IOException if an IO problem occurs during writing
0798: * @throws SAXException if an SAX problem occurs during writing
0799: * @throws IntrospectionException if a java beans introspection problem occurs
0800: */
0801: private void writeRestOfElement(String uri, String localName,
0802: String qualifiedName, ElementDescriptor elementDescriptor,
0803: Context context) throws IOException, SAXException,
0804: IntrospectionException {
0805:
0806: writeElementContent(elementDescriptor, context);
0807: }
0808:
0809: /**
0810: * Writes an element with a <code>IDREF</code> attribute
0811: *
0812: * @param uri the namespace uri
0813: * @param localName the local name
0814: * @param qualifiedName of the element with <code>IDREF</code> attribute
0815: * @param idrefAttributeName the qualified name of the <code>IDREF</code> attribute
0816: * @param idrefAttributeValue the value for the <code>IDREF</code> attribute
0817: * @throws IOException if an IO problem occurs during writing
0818: * @throws SAXException if an SAX problem occurs during writing
0819: * @throws IntrospectionException if a java beans introspection problem occurs
0820: */
0821: private void writeIDREFElement(ElementDescriptor elementDescriptor,
0822: String uri, String localName, String qualifiedName,
0823: String idrefAttributeName, String idrefAttributeValue)
0824: throws IOException, SAXException, IntrospectionException {
0825:
0826: // write IDREF element
0827: AttributesImpl attributes = new AttributesImpl();
0828: // XXX for the moment, assign IDREF to default namespace
0829: attributes.addAttribute("", idrefAttributeName,
0830: idrefAttributeName, "IDREF", idrefAttributeValue);
0831: writeContext.setCurrentDescriptor(elementDescriptor);
0832: startElement(writeContext, uri, localName, qualifiedName,
0833: addNamespaceDeclarations(attributes, uri));
0834: endElement(writeContext, uri, localName, qualifiedName);
0835: }
0836:
0837: /**
0838: * Writes the element content.
0839: *
0840: * @param elementDescriptor the <code>ElementDescriptor</code> to write as xml
0841: * @param context the <code>Context</code> to use to evaluate the bean expressions
0842: *
0843: * @throws IOException if an IO problem occurs during writing
0844: * @throws SAXException if an SAX problem occurs during writing
0845: * @throws IntrospectionException if a java beans introspection problem occurs
0846: */
0847: private void writeElementContent(
0848: ElementDescriptor elementDescriptor, Context context)
0849: throws IOException, SAXException, IntrospectionException {
0850: writeContext.setCurrentDescriptor(elementDescriptor);
0851: Descriptor[] childDescriptors = elementDescriptor
0852: .getContentDescriptors();
0853: if (childDescriptors != null && childDescriptors.length > 0) {
0854: // process child elements
0855: for (int i = 0, size = childDescriptors.length; i < size; i++) {
0856: if (childDescriptors[i] instanceof ElementDescriptor) {
0857: // Element content
0858: ElementDescriptor childDescriptor = (ElementDescriptor) childDescriptors[i];
0859: Context childContext = context;
0860: childContext.pushOptions(childDescriptor
0861: .getOptions());
0862: Expression childExpression = childDescriptor
0863: .getContextExpression();
0864: if (childExpression != null) {
0865: Object childBean = childExpression
0866: .evaluate(context);
0867: if (childBean != null) {
0868: String qualifiedName = childDescriptor
0869: .getQualifiedName();
0870: String namespaceUri = childDescriptor
0871: .getURI();
0872: String localName = childDescriptor
0873: .getLocalName();
0874: // XXXX: should we handle nulls better
0875: if (childBean instanceof Iterator) {
0876: for (Iterator iter = (Iterator) childBean; iter
0877: .hasNext();) {
0878: Object object = iter.next();
0879: if (object == null) {
0880: continue;
0881: }
0882: writeBean(namespaceUri, localName,
0883: qualifiedName, object,
0884: childDescriptor, context);
0885: }
0886: } else {
0887: writeBean(namespaceUri, localName,
0888: qualifiedName, childBean,
0889: childDescriptor, context);
0890: }
0891: }
0892: } else {
0893: writeElement(childDescriptor.getURI(),
0894: childDescriptor.getLocalName(),
0895: childDescriptor.getQualifiedName(),
0896: childDescriptor, childContext);
0897: }
0898: childContext.popOptions();
0899: } else {
0900: // Mixed text content
0901: // evaluate the body text
0902: Expression expression = childDescriptors[i]
0903: .getTextExpression();
0904: if (expression != null) {
0905: Object value = expression.evaluate(context);
0906: String text = convertToString(value,
0907: childDescriptors[i], context);
0908: if (text != null && text.length() > 0) {
0909: ;
0910: bodyText(writeContext, text);
0911: }
0912: }
0913: }
0914: }
0915: } else {
0916: // evaluate the body text
0917: Expression expression = elementDescriptor
0918: .getTextExpression();
0919: if (expression != null) {
0920: Object value = expression.evaluate(context);
0921: String text = convertToString(value, elementDescriptor,
0922: context);
0923: if (text != null && text.length() > 0) {
0924: bodyText(writeContext, text);
0925: }
0926: }
0927: }
0928: }
0929:
0930: /**
0931: * Pushes the bean onto the ancestry stack.
0932: * If IDs are not being written, then check for cyclic references.
0933: *
0934: * @param bean push this bean onto the ancester stack
0935: */
0936: protected void pushBean(Object bean) {
0937: // check that we don't have a cyclic reference when we're not writing IDs
0938: if (!getBindingConfiguration().getMapIDs()) {
0939: Iterator it = beanStack.iterator();
0940: while (it.hasNext()) {
0941: Object next = it.next();
0942: // use absolute equality rather than equals
0943: // we're only really bothered if objects are actually the same
0944: if (bean == next) {
0945: final String message = "Cyclic reference at bean: "
0946: + bean;
0947: log.error(message);
0948: StringBuffer buffer = new StringBuffer(message);
0949: buffer.append(" Stack: ");
0950: Iterator errorStack = beanStack.iterator();
0951: while (errorStack.hasNext()) {
0952: Object errorObj = errorStack.next();
0953: if (errorObj != null) {
0954: buffer
0955: .append(errorObj.getClass()
0956: .getName());
0957: buffer.append(": ");
0958: }
0959: buffer.append(errorObj);
0960: buffer.append(";");
0961: }
0962: final String debugMessage = buffer.toString();
0963: log.info(debugMessage);
0964: throw new CyclicReferenceException(debugMessage);
0965: }
0966: }
0967: }
0968: if (log.isTraceEnabled()) {
0969: log.trace("Pushing onto object stack: " + bean);
0970: }
0971: beanStack.push(bean);
0972: }
0973:
0974: /**
0975: * Pops the top bean off from the ancestry stack
0976: *
0977: * @return the last object pushed onto the ancester stack
0978: */
0979: protected Object popBean() {
0980: Object bean = beanStack.pop();
0981: if (log.isTraceEnabled()) {
0982: log.trace("Popped from object stack: " + bean);
0983: }
0984: return bean;
0985: }
0986:
0987: /**
0988: * Should this element (and children) be written out?
0989: *
0990: * @param descriptor the <code>ElementDescriptor</code> to evaluate
0991: * @param context the <code>Context</code> against which the element will be evaluated
0992: * @return true if this element should be written out
0993: * @throws IntrospectionException
0994: */
0995: private boolean ignoreElement(ElementDescriptor descriptor,
0996: String namespaceUri, String localName,
0997: String qualifiedName, Context context)
0998: throws IntrospectionException {
0999: if (getBindingConfiguration().getValueSuppressionStrategy()
1000: .suppressElement(descriptor, namespaceUri, localName,
1001: qualifiedName, context.getBean())) {
1002: return true;
1003: }
1004:
1005: if (!getWriteEmptyElements()) {
1006: return isEmptyElement(descriptor, context);
1007: }
1008: return false;
1009: }
1010:
1011: /**
1012: * <p>Will evaluating this element against this context result in an empty element?</p>
1013: *
1014: * <p>An empty element is one that has no attributes, no child elements
1015: * and no body text.
1016: * For example, <code><element/></code> is an empty element but
1017: * <code><element attr='value'/></code> is not.</p>
1018: *
1019: * @param descriptor the <code>ElementDescriptor</code> to evaluate
1020: * @param context the <code>Context</code> against which the element will be evaluated
1021: * @return true if this element is empty on evaluation
1022: * @throws IntrospectionException
1023: */
1024: private boolean isEmptyElement(ElementDescriptor descriptor,
1025: Context context) throws IntrospectionException {
1026: //TODO: this design isn't too good
1027: // to would be much better to render just once
1028: if (log.isTraceEnabled()) {
1029: log.trace("Is " + descriptor + " empty?");
1030: }
1031:
1032: // an element which has attributes is not empty
1033: if (descriptor.hasAttributes()) {
1034: log.trace("Element has attributes.");
1035: return false;
1036: }
1037:
1038: // an element is not empty if it has a non-empty body
1039: Expression expression = descriptor.getTextExpression();
1040: if (expression != null) {
1041: Object value = expression.evaluate(context);
1042: String text = convertToString(value, descriptor, context);
1043: if (text != null && text.length() > 0) {
1044: log.trace("Element has body text which isn't empty.");
1045: return false;
1046: }
1047: }
1048:
1049: // always write out loops - even when they have no elements
1050: if (descriptor.isCollective()) {
1051: log.trace("Loop type so not empty.");
1052: return false;
1053: }
1054:
1055: // now test child elements
1056: // an element is empty if it has no non-empty child elements
1057: if (descriptor.hasChildren()) {
1058: for (int i = 0, size = descriptor.getElementDescriptors().length; i < size; i++) {
1059: if (!isEmptyElement(
1060: descriptor.getElementDescriptors()[i], context)) {
1061: log.trace("Element has child which isn't empty.");
1062: return false;
1063: }
1064: }
1065: }
1066:
1067: if (descriptor.isHollow()) {
1068: Expression contentExpression = descriptor
1069: .getContextExpression();
1070: if (contentExpression != null) {
1071: Object childBean = contentExpression.evaluate(context);
1072: if (childBean != null) {
1073: XMLBeanInfo xmlBeanInfo = findXMLBeanInfo(
1074: childBean, descriptor);
1075: Object currentBean = context.getBean();
1076: context.setBean(childBean);
1077: boolean result = isEmptyElement(xmlBeanInfo
1078: .getElementDescriptor(), context);
1079: context.setBean(currentBean);
1080: return result;
1081: }
1082: }
1083: }
1084:
1085: log.trace("Element is empty.");
1086: return true;
1087: }
1088:
1089: /**
1090: * Attributes backed by attribute descriptors.
1091: * ID/IDREFs not set.
1092: */
1093: private class ElementAttributes implements Attributes {
1094: /** Attribute descriptors backing the <code>Attributes</code> */
1095: private AttributeDescriptor[] attributes;
1096: /** Context to be evaluated when finding values */
1097: private Context context;
1098: /** Cached attribute values */
1099: private String[] values;
1100: /** The number of unsuppressed attributes */
1101: private int length;
1102:
1103: /**
1104: * Construct attributes for element and context.
1105: *
1106: * @param descriptor the <code>ElementDescriptor</code> describing the element
1107: * @param context evaluate against this context
1108: */
1109: ElementAttributes(ElementDescriptor descriptor, Context context) {
1110: this .context = context;
1111: init(descriptor.getAttributeDescriptors());
1112: }
1113:
1114: private void init(AttributeDescriptor[] baseAttributes) {
1115: attributes = new AttributeDescriptor[baseAttributes.length];
1116: values = new String[baseAttributes.length];
1117: int index = 0;
1118: for (int i = 0, size = baseAttributes.length; i < size; i++) {
1119: AttributeDescriptor baseAttribute = baseAttributes[i];
1120: String attributeValue = valueAttribute(baseAttribute);
1121: if (attributeValue != null
1122: && !context.getValueSuppressionStrategy()
1123: .suppressAttribute(baseAttribute,
1124: attributeValue)) {
1125: values[index] = attributeValue;
1126: attributes[index] = baseAttribute;
1127: index++;
1128: }
1129: }
1130: length = index;
1131: }
1132:
1133: private String valueAttribute(AttributeDescriptor attribute) {
1134: Expression expression = attribute.getTextExpression();
1135: if (expression != null) {
1136: Object value = expression.evaluate(context);
1137: return convertToString(value, attribute, context);
1138: }
1139:
1140: return "";
1141: }
1142:
1143: /**
1144: * Gets the index of an attribute by qualified name.
1145: *
1146: * @param qName the qualified name of the attribute
1147: * @return the index of the attribute - or -1 if there is no matching attribute
1148: */
1149: public int getIndex(String qName) {
1150: for (int i = 0; i < attributes.length; i++) {
1151: if (attributes[i].getQualifiedName() != null
1152: && attributes[i].getQualifiedName().equals(
1153: qName)) {
1154: return i;
1155: }
1156: }
1157: return -1;
1158: }
1159:
1160: /**
1161: * Gets the index of an attribute by namespace name.
1162: *
1163: * @param uri the namespace uri of the attribute
1164: * @param localName the local name of the attribute
1165: * @return the index of the attribute - or -1 if there is no matching attribute
1166: */
1167: public int getIndex(String uri, String localName) {
1168: for (int i = 0; i < attributes.length; i++) {
1169: if (attributes[i].getURI() != null
1170: && attributes[i].getURI().equals(uri)
1171: && attributes[i].getLocalName() != null
1172: && attributes[i].getURI().equals(localName)) {
1173: return i;
1174: }
1175: }
1176:
1177: return -1;
1178: }
1179:
1180: /**
1181: * Gets the number of attributes in the list.
1182: *
1183: * @return the number of attributes in this list
1184: */
1185: public int getLength() {
1186: return length;
1187: }
1188:
1189: /**
1190: * Gets the local name by index.
1191: *
1192: * @param index the attribute index (zero based)
1193: * @return the attribute local name - or null if the index is out of range
1194: */
1195: public String getLocalName(int index) {
1196: if (indexInRange(index)) {
1197: return attributes[index].getLocalName();
1198: }
1199:
1200: return null;
1201: }
1202:
1203: /**
1204: * Gets the qualified name by index.
1205: *
1206: * @param index the attribute index (zero based)
1207: * @return the qualified name of the element - or null if the index is our of range
1208: */
1209: public String getQName(int index) {
1210: if (indexInRange(index)) {
1211: return attributes[index].getQualifiedName();
1212: }
1213:
1214: return null;
1215: }
1216:
1217: /**
1218: * Gets the attribute SAX type by namespace name.
1219: *
1220: * @param index the attribute index (zero based)
1221: * @return the attribute type (as a string) or null if the index is out of range
1222: */
1223: public String getType(int index) {
1224: if (indexInRange(index)) {
1225: return "CDATA";
1226: }
1227: return null;
1228: }
1229:
1230: /**
1231: * Gets the attribute SAX type by qualified name.
1232: *
1233: * @param qName the qualified name of the attribute
1234: * @return the attribute type (as a string) or null if the attribute is not in the list
1235: */
1236: public String getType(String qName) {
1237: return getType(getIndex(qName));
1238: }
1239:
1240: /**
1241: * Gets the attribute SAX type by namespace name.
1242: *
1243: * @param uri the namespace uri of the attribute
1244: * @param localName the local name of the attribute
1245: * @return the attribute type (as a string) or null if the attribute is not in the list
1246: */
1247: public String getType(String uri, String localName) {
1248: return getType(getIndex(uri, localName));
1249: }
1250:
1251: /**
1252: * Gets the namespace URI for attribute at the given index.
1253: *
1254: * @param index the attribute index (zero-based)
1255: * @return the namespace URI (empty string if none is available)
1256: * or null if the index is out of range
1257: */
1258: public String getURI(int index) {
1259: if (indexInRange(index)) {
1260: return attributes[index].getURI();
1261: }
1262: return null;
1263: }
1264:
1265: /**
1266: * Gets the value for the attribute at given index.
1267: *
1268: * @param index the attribute index (zero based)
1269: * @return the attribute value or null if the index is out of range
1270: * @todo add value caching
1271: */
1272: public String getValue(int index) {
1273: if (indexInRange(index)) {
1274: return values[index];
1275: }
1276: return null;
1277: }
1278:
1279: /**
1280: * Gets the value for the attribute by qualified name.
1281: *
1282: * @param qName the qualified name
1283: * @return the attribute value or null if there are no attributes
1284: * with the given qualified name
1285: * @todo add value caching
1286: */
1287: public String getValue(String qName) {
1288: return getValue(getIndex(qName));
1289: }
1290:
1291: /**
1292: * Gets the value for the attribute by namespace name.
1293: *
1294: * @param uri the namespace URI of the attribute
1295: * @param localName the local name of the attribute
1296: * @return the attribute value or null if there are not attributes
1297: * with the given namespace and local name
1298: * @todo add value caching
1299: */
1300: public String getValue(String uri, String localName) {
1301: return getValue(getIndex(uri, localName));
1302: }
1303:
1304: /**
1305: * Is the given index within the range of the attribute list
1306: *
1307: * @param index the index whose range will be checked
1308: * @return true if the index with within the range of the attribute list
1309: */
1310: private boolean indexInRange(int index) {
1311: return (index >= 0 && index < getLength());
1312: }
1313: }
1314:
1315: /**
1316: * Attributes with generate ID/IDREF attributes
1317: * //TODO: refactor the ID/REF generation so that it's fixed at introspection
1318: * and the generators are placed into the Context.
1319: * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
1320: * @version $Revision: 438373 $
1321: */
1322: private class IDElementAttributes extends ElementAttributes {
1323: /** ID attribute value */
1324: private String idValue;
1325: /** ID attribute name */
1326: private String idAttributeName;
1327:
1328: private boolean matchingAttribute = false;
1329: private int length;
1330: private int idIndex;
1331:
1332: /**
1333: * Construct attributes for element and context.
1334: *
1335: * @param descriptor the <code>ElementDescriptor</code> describing the element
1336: * @param context evaluate against this context
1337: * @param idAttributeName the name of the id attribute
1338: * @param idValue the ID attribute value
1339: */
1340: IDElementAttributes(ElementDescriptor descriptor,
1341: Context context, String idAttributeName, String idValue) {
1342: super (descriptor, context);
1343: this .idValue = idValue;
1344: this .idAttributeName = idAttributeName;
1345:
1346: // see if we have already have a matching attribute descriptor
1347: AttributeDescriptor[] attributeDescriptors = descriptor
1348: .getAttributeDescriptors();
1349: length = super .getLength();
1350: for (int i = 0; i < length; i++) {
1351: if (idAttributeName.equals(attributeDescriptors[i]
1352: .getQualifiedName())) {
1353: matchingAttribute = true;
1354: idIndex = i;
1355: break;
1356: }
1357: }
1358: if (!matchingAttribute) {
1359: length += 1;
1360: idIndex = length - 1;
1361: }
1362: }
1363:
1364: public int getIndex(String uri, String localName) {
1365: if (localName.equals(idAttributeName)) {
1366: return idIndex;
1367: }
1368:
1369: return super .getIndex(uri, localName);
1370: }
1371:
1372: public int getIndex(String qName) {
1373: if (qName.equals(idAttributeName)) {
1374: return idIndex;
1375: }
1376:
1377: return super .getIndex(qName);
1378: }
1379:
1380: public int getLength() {
1381: return length;
1382: }
1383:
1384: public String getLocalName(int index) {
1385: if (index == idIndex) {
1386: return idAttributeName;
1387: }
1388: return super .getLocalName(index);
1389: }
1390:
1391: public String getQName(int index) {
1392: if (index == idIndex) {
1393: return idAttributeName;
1394: }
1395: return super .getQName(index);
1396: }
1397:
1398: public String getType(int index) {
1399: if (index == idIndex) {
1400: return "ID";
1401: }
1402: return super .getType(index);
1403: }
1404:
1405: public String getType(String uri, String localName) {
1406: return getType(getIndex(uri, localName));
1407: }
1408:
1409: public String getType(String qName) {
1410: return getType(getIndex(qName));
1411: }
1412:
1413: public String getURI(int index) {
1414: //TODO: this is probably wrong
1415: // probably need to move ID management into introspection
1416: // before we can handle this namespace bit correctly
1417: if (index == idIndex) {
1418: return "";
1419: }
1420: return super .getURI(index);
1421: }
1422:
1423: public String getValue(int index) {
1424: if (index == idIndex) {
1425: return idValue;
1426: }
1427: return super .getValue(index);
1428: }
1429:
1430: public String getValue(String uri, String localName) {
1431: return getValue(getIndex(uri, localName));
1432: }
1433:
1434: public String getValue(String qName) {
1435: return getValue(getIndex(qName));
1436: }
1437:
1438: }
1439:
1440: // OLD API (DEPRECATED)
1441: // --------------------------------------------------------------------------------------
1442:
1443: /**
1444: * Get the indentation for the current element.
1445: * Used for pretty priting.
1446: *
1447: * @return the amount that the current element is indented
1448: * @deprecated 0.5 replaced by new SAX inspired API
1449: */
1450: protected int getIndentLevel() {
1451: return 0;
1452: }
1453:
1454: // Expression methods
1455: //-------------------------------------------------------------------------
1456:
1457: /**
1458: * Express an element tag start using given qualified name.
1459: *
1460: * @param qualifiedName the qualified name of the element to be expressed
1461: * @throws IOException if an IO problem occurs during writing
1462: * @throws SAXException if an SAX problem occurs during writing
1463: * @deprecated 0.5 replaced by new SAX inspired API
1464: */
1465: protected void expressElementStart(String qualifiedName)
1466: throws IOException, SAXException {
1467: // do nothing
1468: }
1469:
1470: /**
1471: * Express an element tag start using given qualified name.
1472: *
1473: * @param uri the namespace uri
1474: * @param localName the local name for this element
1475: * @param qualifiedName the qualified name of the element to be expressed
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 expressElementStart(String uri, String localName,
1481: String qualifiedName) throws IOException, SAXException {
1482: expressElementStart(qualifiedName);
1483: }
1484:
1485: /**
1486: * Express a closing tag.
1487: *
1488: * @throws IOException if an IO problem occurs during writing
1489: * @throws SAXException if an SAX problem occurs during writing
1490: * @deprecated 0.5 replaced by new SAX inspired API
1491: */
1492: protected void expressTagClose() throws IOException, SAXException {
1493: }
1494:
1495: /**
1496: * Express an element end tag (with given name)
1497: *
1498: * @param qualifiedName the qualified name for the element to be closed
1499: *
1500: * @throws IOException if an IO problem occurs during writing
1501: * @throws SAXException if an SAX problem occurs during writing
1502: * @deprecated 0.5 replaced by new SAX inspired API
1503: */
1504: protected void expressElementEnd(String qualifiedName)
1505: throws IOException, SAXException {
1506: // do nothing
1507: }
1508:
1509: /**
1510: * Express an element end tag (with given name)
1511: *
1512: * @param uri the namespace uri of the element close tag
1513: * @param localName the local name of the element close tag
1514: * @param qualifiedName the qualified name for the element to be closed
1515: *
1516: * @throws IOException if an IO problem occurs during writing
1517: * @throws SAXException if an SAX problem occurs during writing
1518: * @deprecated 0.5 replaced by new SAX inspired API
1519: */
1520: protected void expressElementEnd(String uri, String localName,
1521: String qualifiedName) throws IOException, SAXException {
1522: expressElementEnd(qualifiedName);
1523: }
1524:
1525: /**
1526: * Express an empty element end.
1527: *
1528: * @throws IOException if an IO problem occurs during writing
1529: * @throws SAXException if an SAX problem occurs during writing
1530: * @deprecated 0.5 replaced by new SAX inspired API
1531: */
1532: protected void expressElementEnd() throws IOException, SAXException {
1533: }
1534:
1535: /**
1536: * Express body text
1537: *
1538: * @param text the string to write out as the body of the current element
1539: *
1540: * @throws IOException if an IO problem occurs during writing
1541: * @throws SAXException if an SAX problem occurs during writing
1542: * @deprecated 0.5 replaced by new SAX inspired API
1543: */
1544: protected void expressBodyText(String text) throws IOException,
1545: SAXException {
1546: }
1547:
1548: /**
1549: * Express an attribute
1550: *
1551: * @param qualifiedName the qualified name of the attribute
1552: * @param value the attribute value
1553: * @throws IOException if an IO problem occurs during writing
1554: * @throws SAXException if an SAX problem occurs during writing
1555: * @deprecated 0.5 replaced by new SAX inspired API
1556: */
1557: protected void expressAttribute(String qualifiedName, String value)
1558: throws IOException, SAXException {
1559: // Do nothing
1560: }
1561:
1562: /**
1563: * Express an attribute
1564: *
1565: * @param namespaceUri the namespace uri
1566: * @param localName the local name
1567: * @param qualifiedName the qualified name of the attribute
1568: * @param value the attribute value
1569: * @throws IOException if an IO problem occurs during writing
1570: * @throws SAXException if an SAX problem occurs during writing
1571: * @deprecated 0.5 replaced by new SAX inspired API
1572: */
1573: protected void expressAttribute(String namespaceUri,
1574: String localName, String qualifiedName, String value)
1575: throws IOException, SAXException {
1576: expressAttribute(qualifiedName, value);
1577: }
1578:
1579: /**
1580: * Writes the given element
1581: *
1582: * @param qualifiedName qualified name to use for the element
1583: * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
1584: * @param context the <code>Context</code> to use to evaluate the bean expressions
1585: * @throws IOException if an IO problem occurs during writing
1586: * @throws SAXException if an SAX problem occurs during writing
1587: * @throws IntrospectionException if a java beans introspection problem occurs
1588: * @deprecated 0.5 replaced by new SAX inspired API
1589: */
1590: protected void write(String qualifiedName,
1591: ElementDescriptor elementDescriptor, Context context)
1592: throws IOException, SAXException, IntrospectionException {
1593: writeElement("", qualifiedName, qualifiedName,
1594: elementDescriptor, context);
1595: }
1596:
1597: /**
1598: * Writes the given element adding an ID attribute
1599: *
1600: * @param qualifiedName qualified name to use for the element
1601: * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
1602: * @param context the <code>Context</code> to use to evaluate the bean expressions
1603: * @param idAttribute the qualified name of the <code>ID</code> attribute
1604: * @param idValue the value for the <code>ID</code> attribute
1605: * @throws IOException if an IO problem occurs during writing
1606: * @throws SAXException if an SAX problem occurs during writing
1607: * @throws IntrospectionException if a java beans introspection problem occurs
1608: * @deprecated 0.5 replaced by new SAX inspired API
1609: */
1610: protected void write(String qualifiedName,
1611: ElementDescriptor elementDescriptor, Context context,
1612: String idAttribute, String idValue) throws IOException,
1613: SAXException, IntrospectionException {
1614: writeElement("", qualifiedName, qualifiedName,
1615: elementDescriptor, context, idAttribute, idValue);
1616: }
1617:
1618: /**
1619: * Write attributes, child elements and element end
1620: *
1621: * @param qualifiedName qualified name to use for the element
1622: * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
1623: * @param context the <code>Context</code> to use to evaluate the bean expressions
1624: * @throws IOException if an IO problem occurs during writing
1625: * @throws SAXException if an SAX problem occurs during writing
1626: * @throws IntrospectionException if a java beans introspection problem occurs
1627: * @deprecated 0.5 replaced by new SAX inspired API
1628: */
1629: protected void writeRestOfElement(String qualifiedName,
1630: ElementDescriptor elementDescriptor, Context context)
1631: throws IOException, SAXException, IntrospectionException {
1632: writeRestOfElement("", qualifiedName, qualifiedName,
1633: elementDescriptor, context);
1634: }
1635:
1636: /**
1637: * Writes an element with a <code>IDREF</code> attribute
1638: *
1639: * @param qualifiedName of the element with <code>IDREF</code> attribute
1640: * @param idrefAttributeName the qualified name of the <code>IDREF</code> attribute
1641: * @param idrefAttributeValue the value for the <code>IDREF</code> attribute
1642: * @throws IOException if an IO problem occurs during writing
1643: * @throws SAXException if an SAX problem occurs during writing
1644: * @throws IntrospectionException if a java beans introspection problem occurs
1645: * @deprecated 0.5 replaced by new SAX inspired API
1646: */
1647: protected void writeIDREFElement(String qualifiedName,
1648: String idrefAttributeName, String idrefAttributeValue)
1649: throws IOException, SAXException, IntrospectionException {
1650: // deprecated
1651: AttributesImpl attributes = new AttributesImpl();
1652: attributes.addAttribute("", idrefAttributeName,
1653: idrefAttributeName, "IDREF", idrefAttributeValue);
1654: startElement("", qualifiedName, qualifiedName, attributes);
1655: endElement("", qualifiedName, qualifiedName);
1656: }
1657:
1658: /**
1659: * Writes the element content.
1660: *
1661: * @param elementDescriptor the <code>ElementDescriptor</code> to write as xml
1662: * @param context the <code>Context</code> to use to evaluate the bean expressions
1663: * @return true if some content was written
1664: * @throws IOException if an IO problem occurs during writing
1665: * @throws SAXException if an SAX problem occurs during writing
1666: * @throws IntrospectionException if a java beans introspection problem occurs
1667: * @deprecated 0.5 replaced by new SAX inspired API
1668: */
1669: protected boolean writeContent(ElementDescriptor elementDescriptor,
1670: Context context) throws IOException, SAXException,
1671: IntrospectionException {
1672: return false;
1673: }
1674:
1675: /**
1676: * Writes the attribute declarations
1677: *
1678: * @param elementDescriptor the <code>ElementDescriptor</code> to be written out as xml
1679: * @param context the <code>Context</code> to use to evaluation bean expressions
1680: * @throws IOException if an IO problem occurs during writing
1681: * @throws SAXException if an SAX problem occurs during writing
1682: * @deprecated 0.5 replaced by new SAX inspired API
1683: */
1684: protected void writeAttributes(ElementDescriptor elementDescriptor,
1685: Context context) throws IOException, SAXException {
1686: if (!elementDescriptor.isWrapCollectionsInElement()) {
1687: return;
1688: }
1689:
1690: AttributeDescriptor[] attributeDescriptors = elementDescriptor
1691: .getAttributeDescriptors();
1692: if (attributeDescriptors != null) {
1693: for (int i = 0, size = attributeDescriptors.length; i < size; i++) {
1694: AttributeDescriptor attributeDescriptor = attributeDescriptors[i];
1695: writeAttribute(attributeDescriptor, context);
1696: }
1697: }
1698: }
1699:
1700: /**
1701: * Writes an attribute declaration
1702: *
1703: * @param attributeDescriptor the <code>AttributeDescriptor</code> to be written as xml
1704: * @param context the <code>Context</code> to use to evaluation bean expressions
1705: * @throws IOException if an IO problem occurs during writing
1706: * @throws SAXException if an SAX problem occurs during writing
1707: * @deprecated 0.5 replaced by new SAX inspired API
1708: */
1709: protected void writeAttribute(
1710: AttributeDescriptor attributeDescriptor, Context context)
1711: throws IOException, SAXException {
1712: Expression expression = attributeDescriptor.getTextExpression();
1713: if (expression != null) {
1714: Object value = expression.evaluate(context);
1715: if (value != null) {
1716: String text = value.toString();
1717: if (text != null && text.length() > 0) {
1718: expressAttribute(attributeDescriptor.getURI(),
1719: attributeDescriptor.getLocalName(),
1720: attributeDescriptor.getQualifiedName(),
1721: text);
1722: }
1723: }
1724: }
1725: }
1726:
1727: /**
1728: * Writes a empty line.
1729: * This implementation does nothing but can be overridden by subclasses.
1730: *
1731: * @throws IOException if the line cannot be written
1732: * @deprecated 0.5 replaced by new SAX inspired API
1733: */
1734: protected void writePrintln() throws IOException {
1735: }
1736:
1737: /**
1738: * Writes an indentation.
1739: * This implementation does nothing but can be overridden by subclasses.
1740: *
1741: * @throws IOException if the indent cannot be written
1742: * @deprecated 0.5 replaced by new BeanWriter API
1743: */
1744: protected void writeIndent() throws IOException {
1745: }
1746:
1747: /**
1748: * Converts an object to a string.
1749: *
1750: * @param value the Object to represent as a String, possibly null
1751: * @param descriptor writing out this descriptor not null
1752: * @param context not null
1753: * @return String representation, not null
1754: */
1755: private String convertToString(Object value, Descriptor descriptor,
1756: Context context) {
1757: return getBindingConfiguration().getObjectStringConverter()
1758: .objectToString(value, descriptor.getPropertyType(),
1759: context);
1760: }
1761:
1762: /**
1763: * Factory method for new contexts.
1764: * Ensure that they are correctly configured.
1765: * @param bean make a new Context for this bean
1766: * @return not null
1767: */
1768: private Context makeContext(Object bean) {
1769: return new Context(bean, log, bindingConfiguration);
1770: }
1771:
1772: /**
1773: * Basic mutable implementation of <code>WriteContext</code>.
1774: */
1775: private static class WriteContextImpl extends WriteContext {
1776:
1777: private ElementDescriptor currentDescriptor;
1778:
1779: /**
1780: * @see org.apache.commons.betwixt.io.WriteContext#getCurrentDescriptor()
1781: */
1782: public ElementDescriptor getCurrentDescriptor() {
1783: return currentDescriptor;
1784: }
1785:
1786: /**
1787: * Sets the descriptor for the current element.
1788: * @param currentDescriptor
1789: */
1790: public void setCurrentDescriptor(
1791: ElementDescriptor currentDescriptor) {
1792: this.currentDescriptor = currentDescriptor;
1793: }
1794:
1795: }
1796: }
|