0001: /*
0002: Copyright (c) 2002-2005, Dennis M. Sosnoski.
0003: All rights reserved.
0004:
0005: Redistribution and use in source and binary forms, with or without modification,
0006: are permitted provided that the following conditions are met:
0007:
0008: * Redistributions of source code must retain the above copyright notice, this
0009: list of conditions and the following disclaimer.
0010: * Redistributions in binary form must reproduce the above copyright notice,
0011: this list of conditions and the following disclaimer in the documentation
0012: and/or other materials provided with the distribution.
0013: * Neither the name of JiBX nor the names of its contributors may be used
0014: to endorse or promote products derived from this software without specific
0015: prior written permission.
0016:
0017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
0018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
0019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
0021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
0024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0027: */
0028:
0029: package org.jibx.runtime.impl;
0030:
0031: import java.io.*;
0032: import java.util.*;
0033:
0034: import org.jibx.runtime.*;
0035: import org.jibx.runtime.ICharacterEscaper;
0036: import org.jibx.runtime.IMarshallable;
0037: import org.jibx.runtime.IMarshaller;
0038: import org.jibx.runtime.IMarshallingContext;
0039: import org.jibx.runtime.JiBXException;
0040:
0041: /**
0042: * JiBX serializer supplying convenience methods for marshalling. Most of these
0043: * methods are designed for use in code generated by the binding generator.
0044: *
0045: * @author Dennis M. Sosnoski
0046: * @version 1.0
0047: */
0048:
0049: public class MarshallingContext implements IMarshallingContext {
0050: /** Fixed XML namespace. */
0051: public static final String XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
0052:
0053: /** Starting size for object stack. */
0054: private static final int INITIAL_STACK_SIZE = 20;
0055:
0056: /** Binding factory used to create this unmarshaller. */
0057: private IBindingFactory m_factory;
0058:
0059: /** Names of classes included in mapping definition. */
0060: private String[] m_classes;
0061:
0062: /** Number of classes with global marshallers. */
0063: private int m_globalCount;
0064:
0065: /** Marshaller classes for mapping definition (<code>null</code> for
0066: mappings out of context). */
0067: private String[] m_marshallerClasses;
0068:
0069: /** Marshallers for classes in mapping definition (lazy create of actual
0070: marshaller instances) */
0071: private IMarshaller[] m_marshallers;
0072:
0073: /** URIs for namespaces used in binding. */
0074: private String[] m_uris;
0075:
0076: /** Current marshalling stack depth. */
0077: private int m_stackDepth;
0078:
0079: /** Stack of objects being marshalled. */
0080: private Object[] m_objectStack;
0081:
0082: /** Indent character count per level. */
0083: private int m_indentCount;
0084:
0085: /** Character sequence for end of line. */
0086: private String m_newLine;
0087:
0088: /** Character used for indenting. */
0089: private char m_indentChar;
0090:
0091: /** Shared map from IDs to objects. This is not used directly by the
0092: marshalling code, but is available for user extensions (lazy create). */
0093: private HashMap m_idMap;
0094:
0095: /** Output document handler. */
0096: private IXMLWriter m_writer;
0097:
0098: /** User context object (not used by JiBX, only for user convenience). */
0099: protected Object m_userContext;
0100:
0101: /**
0102: * Constructor.
0103: *
0104: * @param classes ordered array of class names included in mapping
0105: * definition (reference kept, must be constant)
0106: * @param mcs names of marshaller classes for indexes with fixed marshallers
0107: * (as opposed to mapping slots, which may be overridden; reference kept,
0108: * must be constant)
0109: * @param uris ordered array of URIs for namespaces used in binding (must
0110: * be constant; the value in position 0 must always be the empty string "",
0111: * and the value in position 1 must always be the XML namespace
0112: * "http://www.w3.org/XML/1998/namespace")
0113: * @param ifact binding factory creating this unmarshaller
0114: */
0115: public MarshallingContext(String[] classes, String[] mcs,
0116: String[] uris, IBindingFactory ifact) {
0117: m_classes = classes;
0118: m_globalCount = mcs.length;
0119: m_marshallers = new IMarshaller[classes.length];
0120: m_marshallerClasses = new String[classes.length];
0121: System.arraycopy(mcs, 0, m_marshallerClasses, 0, mcs.length);
0122: m_uris = uris;
0123: m_objectStack = new Object[INITIAL_STACK_SIZE];
0124: m_indentCount = -1;
0125: m_indentChar = ' ';
0126: m_newLine = "\n";
0127: m_factory = ifact;
0128: }
0129:
0130: /**
0131: * Create character escaper for encoding.
0132: *
0133: * @param enc document output encoding, or <code>null</code> for default
0134: * @return character escaper for encoding
0135: * @throws JiBXException if error creating setting output
0136: */
0137: private ICharacterEscaper createEscaper(String enc)
0138: throws JiBXException {
0139: if (enc.equalsIgnoreCase("UTF-8")
0140: || enc.equalsIgnoreCase("UTF-16")
0141: || enc.equalsIgnoreCase("UTF-16BE")
0142: || enc.equalsIgnoreCase("UTF-16LE")) {
0143: return UTF8Escaper.getInstance();
0144: } else if (enc.equalsIgnoreCase("ISO-8859-1")) {
0145: return ISO88591Escaper.getInstance();
0146: } else if (enc.equalsIgnoreCase("US-ASCII")) {
0147: return USASCIIEscaper.getInstance();
0148: } else {
0149: throw new JiBXException(
0150: "No character escaper defined for encoding " + enc);
0151: }
0152: }
0153:
0154: /**
0155: * Set output stream with encoding and escaper. This forces handling of the
0156: * output stream to use the Java character encoding support with the
0157: * supplied escaper.
0158: *
0159: * @param outs stream for document data output
0160: * @param enc document output encoding, or <code>null</code> uses UTF-8
0161: * default
0162: * @param esc escaper for writing characters to stream
0163: * @throws JiBXException if error setting output
0164: */
0165: public void setOutput(OutputStream outs, String enc,
0166: ICharacterEscaper esc) throws JiBXException {
0167: try {
0168: if (enc == null) {
0169: enc = "UTF-8";
0170: }
0171:
0172: // handle any other encodings using library support
0173: if (!(m_writer instanceof GenericXMLWriter)) {
0174: m_writer = new GenericXMLWriter(m_uris);
0175: m_writer.setIndentSpaces(m_indentCount, m_newLine,
0176: m_indentChar);
0177: }
0178: Writer writer = new BufferedWriter(new OutputStreamWriter(
0179: outs, enc));
0180: ((GenericXMLWriter) m_writer).setOutput(writer, esc);
0181: reset();
0182:
0183: } catch (IOException ex) {
0184: throw new JiBXException("Error setting output", ex);
0185: }
0186: }
0187:
0188: /**
0189: * Set output stream and encoding.
0190: *
0191: * @param outs stream for document data output
0192: * @param enc document output encoding, or <code>null</code> for default
0193: * @throws JiBXException if error creating setting output
0194: */
0195: public void setOutput(OutputStream outs, String enc)
0196: throws JiBXException {
0197: if (enc == null) {
0198: enc = "UTF-8";
0199: }
0200: if ("UTF-8".equalsIgnoreCase(enc)) {
0201:
0202: // handle UTF-8 output to stream directly
0203: if (!(m_writer instanceof UTF8StreamWriter)) {
0204: m_writer = new UTF8StreamWriter(m_uris);
0205: m_writer.setIndentSpaces(m_indentCount, m_newLine,
0206: m_indentChar);
0207: }
0208: ((UTF8StreamWriter) m_writer).setOutput(outs);
0209: reset();
0210:
0211: } else if ("ISO-8859-1".equalsIgnoreCase(enc)) {
0212:
0213: // handle ISO-8859-1 output to stream directly
0214: if (!(m_writer instanceof ISO88591StreamWriter)) {
0215: m_writer = new ISO88591StreamWriter(m_uris);
0216: m_writer.setIndentSpaces(m_indentCount, m_newLine,
0217: m_indentChar);
0218: }
0219: ((ISO88591StreamWriter) m_writer).setOutput(outs);
0220: reset();
0221:
0222: } else {
0223: setOutput(outs, enc, createEscaper(enc));
0224: }
0225: }
0226:
0227: /**
0228: * Set output writer and escaper.
0229: *
0230: * @param outw writer for document data output
0231: * @param esc escaper for writing characters
0232: */
0233: public void setOutput(Writer outw, ICharacterEscaper esc) {
0234: if (!(m_writer instanceof GenericXMLWriter)) {
0235: m_writer = new GenericXMLWriter(m_uris);
0236: m_writer.setIndentSpaces(m_indentCount, m_newLine,
0237: m_indentChar);
0238: }
0239: ((GenericXMLWriter) m_writer).setOutput(outw, esc);
0240: reset();
0241: }
0242:
0243: /**
0244: * Set output writer.
0245: *
0246: * @param outw writer for document data output
0247: */
0248: public void setOutput(Writer outw) {
0249: setOutput(outw, UTF8Escaper.getInstance());
0250: }
0251:
0252: /**
0253: * Get the writer being used for output.
0254: *
0255: * @return XML writer used for output
0256: */
0257: public IXMLWriter getXmlWriter() {
0258: return m_writer;
0259: }
0260:
0261: /**
0262: * Set the writer being used for output.
0263: *
0264: * @param xwrite XML writer used for output
0265: */
0266: public void setXmlWriter(IXMLWriter xwrite) {
0267: m_writer = xwrite;
0268: }
0269:
0270: /**
0271: * Get current nesting indent spaces. This returns the number of spaces used
0272: * to show indenting, if used.
0273: *
0274: * @return number of spaces indented per level, or negative if indentation
0275: * disabled
0276: */
0277: public int getIndent() {
0278: return m_indentCount;
0279: }
0280:
0281: /**
0282: * Set nesting indent spaces. This is advisory only, and implementations of
0283: * this interface are free to ignore it. The intent is to indicate that the
0284: * generated output should use indenting to illustrate element nesting.
0285: *
0286: * @param count number of spaces to indent per level, or disable
0287: * indentation if negative
0288: */
0289: public void setIndent(int count) {
0290: if (m_writer != null) {
0291: m_writer.setIndentSpaces(count, m_newLine, m_indentChar);
0292: }
0293: m_indentCount = count;
0294: }
0295:
0296: /**
0297: * Set nesting indentation. This is advisory only, and implementations of
0298: * this interface are free to ignore it. The intent is to indicate that the
0299: * generated output should use indenting to illustrate element nesting.
0300: *
0301: * @param count number of character to indent per level, or disable
0302: * indentation if negative (zero means new line only)
0303: * @param newline sequence of characters used for a line ending
0304: * (<code>null</code> means use the single character '\n')
0305: * @param indent whitespace character used for indentation
0306: */
0307: public void setIndent(int count, String newline, char indent) {
0308: if (m_writer != null) {
0309: m_writer.setIndentSpaces(count, newline, indent);
0310: }
0311: m_indentCount = count;
0312: m_newLine = newline;
0313: m_indentChar = indent;
0314: }
0315:
0316: /**
0317: * Initializes the context to use the same marshalled text destination and
0318: * parameters as another marshalling context. This method is designed for
0319: * use when an initial context needs to create and invoke a secondary
0320: * context (generally from a different binding) in the course of an
0321: * marshalling operation. Note that once the secondary context has been used
0322: * it's generally necessary to do a {@link XMLWriterBase#flush()} operation
0323: * on the writer used by the that context before resuming output on the
0324: * parent.
0325: *
0326: * @param parent context supplying target for marshalled document text
0327: * @throws IOException on error writing output
0328: */
0329: public void setFromContext(MarshallingContext parent)
0330: throws IOException {
0331: reset();
0332: m_indentCount = parent.m_indentCount;
0333: m_newLine = parent.m_newLine;
0334: m_indentChar = parent.m_indentChar;
0335: if (parent.m_writer instanceof XMLWriterBase) {
0336: XMLWriterBase base = (XMLWriterBase) parent.m_writer;
0337: base.flagContent();
0338: base.flush();
0339: m_writer = base.createChildWriter(m_uris);
0340: } else if (parent.m_writer instanceof StAXWriter) {
0341: m_writer = ((StAXWriter) parent.m_writer)
0342: .createChildWriter(m_uris);
0343: } else {
0344: m_writer = parent.m_writer;
0345: }
0346: }
0347:
0348: /**
0349: * Reset to initial state for reuse. The context is serially reusable,
0350: * as long as this method is called to clear any retained state information
0351: * between uses. It is automatically called when output is set.
0352: */
0353: public void reset() {
0354: if (m_writer != null) {
0355: m_writer.reset();
0356: }
0357: for (int i = m_globalCount; i < m_marshallers.length; i++) {
0358: m_marshallers[i] = null;
0359: }
0360: for (int i = 0; i < m_objectStack.length; i++) {
0361: m_objectStack[i] = null;
0362: }
0363: m_stackDepth = 0;
0364: }
0365:
0366: /**
0367: * Return the binding factory used to create this unmarshaller.
0368: *
0369: * @return binding factory
0370: */
0371: public IBindingFactory getFactory() {
0372: return m_factory;
0373: }
0374:
0375: /**
0376: * Get namespace URIs for mapping. This gets the full ordered array of
0377: * namespaces known in the binding used for this marshalling, where the
0378: * index number of each namespace URI is the namespace index used to lookup
0379: * the prefix when marshalling a name in that namespace. The returned array
0380: * must not be modified.
0381: *
0382: * @return array of namespaces
0383: */
0384:
0385: public String[] getNamespaces() {
0386: return m_uris;
0387: }
0388:
0389: /**
0390: * Start document. This can only be validly called immediately following
0391: * one of the set output methods; otherwise the output document will be
0392: * corrupt.
0393: *
0394: * @param enc document encoding, <code>null</code> if not specified
0395: * @param alone standalone document flag, <code>null</code> if not
0396: * specified
0397: * @throws JiBXException on any error (possibly wrapping other exception)
0398: */
0399:
0400: public void startDocument(String enc, Boolean alone)
0401: throws JiBXException {
0402: try {
0403: String atext = null;
0404: if (alone != null) {
0405: atext = alone.booleanValue() ? "yes" : "no";
0406: }
0407: m_writer.writeXMLDecl("1.0", enc, atext);
0408: } catch (IOException ex) {
0409: throw new JiBXException(
0410: "Error writing marshalled document", ex);
0411: }
0412: }
0413:
0414: /**
0415: * Start document with output stream and encoding. The effect is the same
0416: * as from first setting the output stream and encoding, then making the
0417: * call to start document.
0418: *
0419: * @param enc document encoding, <code>null</code> if not specified
0420: * @param alone standalone document flag, <code>null</code> if not
0421: * specified
0422: * @param outs stream for document data output
0423: * @throws JiBXException on any error (possibly wrapping other exception)
0424: */
0425:
0426: public void startDocument(String enc, Boolean alone,
0427: OutputStream outs) throws JiBXException {
0428: setOutput(outs, enc);
0429: startDocument(enc, alone);
0430: }
0431:
0432: /**
0433: * Start document with writer. The effect is the same as from first
0434: * setting the writer, then making the call to start document.
0435: *
0436: * @param enc document encoding, <code>null</code> if not specified
0437: * @param alone standalone document flag, <code>null</code> if not
0438: * specified
0439: * @param outw writer for document data output
0440: * @throws JiBXException on any error (possibly wrapping other exception)
0441: */
0442:
0443: public void startDocument(String enc, Boolean alone, Writer outw)
0444: throws JiBXException {
0445: setOutput(outw);
0446: startDocument(enc, alone);
0447: }
0448:
0449: /**
0450: * End document. Finishes all output and closes the document. Note that if
0451: * this is called with an imcomplete marshalling the result will not be
0452: * well-formed XML.
0453: *
0454: * @throws JiBXException on any error (possibly wrapping other exception)
0455: */
0456:
0457: public void endDocument() throws JiBXException {
0458: try {
0459: m_writer.close();
0460: } catch (IOException ex) {
0461: throw new JiBXException(
0462: "Error writing marshalled document", ex);
0463: }
0464: }
0465:
0466: /**
0467: * Build name with optional namespace. Just returns the appropriate
0468: * name format.
0469: *
0470: * @param index namespace URI index number
0471: * @param name local name part of name
0472: * @return formatted name string
0473: */
0474: public String buildNameString(int index, String name) {
0475: String ns = m_writer.getNamespaceUri(index);
0476: if (ns == null || "".equals(ns)) {
0477: return "\"" + name + "\"";
0478: } else {
0479: return "\"{" + ns + "}" + name + "\"";
0480: }
0481: }
0482:
0483: /**
0484: * Generate start tag for element without attributes.
0485: *
0486: * @param index namespace URI index number
0487: * @param name element name
0488: * @return this context (to allow chained calls)
0489: * @throws JiBXException on any error (possibly wrapping other exception)
0490: */
0491:
0492: public MarshallingContext startTag(int index, String name)
0493: throws JiBXException {
0494: try {
0495: m_writer.startTagClosed(index, name);
0496: return this ;
0497: } catch (IOException ex) {
0498: throw new JiBXException(
0499: "Error writing marshalled document", ex);
0500: }
0501: }
0502:
0503: /**
0504: * Generate start tag for element with attributes. This only opens the start
0505: * tag, allowing attributes to be added immediately following this call.
0506: *
0507: * @param index namespace URI index number
0508: * @param name element name
0509: * @return this context (to allow chained calls)
0510: * @throws JiBXException on any error (possibly wrapping other exception)
0511: */
0512:
0513: public MarshallingContext startTagAttributes(int index, String name)
0514: throws JiBXException {
0515: try {
0516: m_writer.startTagOpen(index, name);
0517: return this ;
0518: } catch (IOException ex) {
0519: throw new JiBXException(
0520: "Error writing marshalled document", ex);
0521: }
0522: }
0523:
0524: /**
0525: * Generate text attribute. This can only be used following an open start
0526: * tag with attributes.
0527: *
0528: * @param index namespace URI index number
0529: * @param name attribute name
0530: * @param value text value for attribute (cannot be <code>null</code>)
0531: * @return this context (to allow chained calls)
0532: * @throws JiBXException on any error (possibly wrapping other exception)
0533: */
0534:
0535: public MarshallingContext attribute(int index, String name,
0536: String value) throws JiBXException {
0537: try {
0538: m_writer.addAttribute(index, name, value);
0539: return this ;
0540: } catch (IOException ex) {
0541: throw new JiBXException(
0542: "Error writing marshalled document", ex);
0543: } catch (Exception ex) {
0544: String text = buildNameString(index, name);
0545: if (value == null) {
0546: throw new JiBXException("null value for attribute "
0547: + text + " from object of type "
0548: + getStackTop().getClass().getName());
0549:
0550: } else {
0551: throw new JiBXException(
0552: "Exception while marshalling attribute " + text,
0553: ex);
0554: }
0555: }
0556: }
0557:
0558: /**
0559: * Generate integer attribute. This can only be used following an open start
0560: * tag.
0561: *
0562: * @param index namespace URI index number
0563: * @param name attribute name
0564: * @param value integer value for attribute
0565: * @return this context (to allow chained calls)
0566: * @throws JiBXException on any error (possibly wrapping other exception)
0567: */
0568:
0569: public MarshallingContext attribute(int index, String name,
0570: int value) throws JiBXException {
0571: return attribute(index, name, Integer.toString(value));
0572: }
0573:
0574: /**
0575: * Generate enumeration attribute. The actual text to be written is obtained
0576: * by indexing into the supplied array of values. This can only be used
0577: * following an open start tag.
0578: *
0579: * @param index namespace URI index number
0580: * @param name attribute name
0581: * @param value integer enumeration value (zero-based)
0582: * @param table text values in enumeration
0583: * @return this context (to allow chained calls)
0584: * @throws JiBXException on any error (possibly wrapping other exception)
0585: */
0586:
0587: public MarshallingContext attribute(int index, String name,
0588: int value, String[] table) throws JiBXException {
0589: try {
0590: return attribute(index, name, table[value]);
0591: } catch (ArrayIndexOutOfBoundsException ex) {
0592: throw new JiBXException("Enumeration value of " + value
0593: + " is outside to allowed range of 0 to "
0594: + table.length);
0595: }
0596: }
0597:
0598: /**
0599: * Close start tag with content to follow.
0600: *
0601: * @return this context (to allow chained calls)
0602: * @throws JiBXException on any error (possibly wrapping other exception)
0603: */
0604:
0605: public MarshallingContext closeStartContent() throws JiBXException {
0606: try {
0607: m_writer.closeStartTag();
0608: return this ;
0609: } catch (IOException ex) {
0610: throw new JiBXException(
0611: "Error writing marshalled document", ex);
0612: }
0613: }
0614:
0615: /**
0616: * Close start tag with no content (empty tag).
0617: *
0618: * @return this context (to allow chained calls)
0619: * @throws JiBXException on any error (possibly wrapping other exception)
0620: */
0621:
0622: public MarshallingContext closeStartEmpty() throws JiBXException {
0623: try {
0624: m_writer.closeEmptyTag();
0625: return this ;
0626: } catch (IOException ex) {
0627: throw new JiBXException(
0628: "Error writing marshalled document", ex);
0629: }
0630: }
0631:
0632: /**
0633: * Add text content to current element.
0634: *
0635: * @param value text element content
0636: * @return this context (to allow chained calls)
0637: * @throws JiBXException on any error (possibly wrapping other exception)
0638: */
0639:
0640: public MarshallingContext content(String value)
0641: throws JiBXException {
0642: try {
0643: m_writer.writeTextContent(value);
0644: return this ;
0645: } catch (IOException ex) {
0646: throw new JiBXException(
0647: "Error writing marshalled document", ex);
0648: }
0649: }
0650:
0651: /**
0652: * Add integer content to current element.
0653: *
0654: * @param value integer element content
0655: * @return this context (to allow chained calls)
0656: * @throws JiBXException on any error (possibly wrapping other exception)
0657: */
0658:
0659: public MarshallingContext content(int value) throws JiBXException {
0660: content(Integer.toString(value));
0661: return this ;
0662: }
0663:
0664: /**
0665: * Add enumeration content to current element. The actual text to be
0666: * written is obtained by indexing into the supplied array of values.
0667: *
0668: * @param value integer enumeration value (zero-based)
0669: * @param table text values in enumeration
0670: * @return this context (to allow chained calls)
0671: * @throws JiBXException on any error (possibly wrapping other exception)
0672: */
0673:
0674: public MarshallingContext content(int value, String[] table)
0675: throws JiBXException {
0676: try {
0677: content(table[value]);
0678: return this ;
0679: } catch (ArrayIndexOutOfBoundsException ex) {
0680: throw new JiBXException("Enumeration value of " + value
0681: + " is outside to allowed range of 0 to "
0682: + table.length);
0683: }
0684: }
0685:
0686: /**
0687: * Generate end tag for element.
0688: *
0689: * @param index namespace URI index number
0690: * @param name element name
0691: * @return this context (to allow chained calls)
0692: * @throws JiBXException on any error (possibly wrapping other exception)
0693: */
0694:
0695: public MarshallingContext endTag(int index, String name)
0696: throws JiBXException {
0697: try {
0698: m_writer.endTag(index, name);
0699: return this ;
0700: } catch (IOException ex) {
0701: throw new JiBXException(
0702: "Error writing marshalled document", ex);
0703: }
0704: }
0705:
0706: /**
0707: * Generate complete element with text content.
0708: *
0709: * @param index namespace URI index number
0710: * @param name element name
0711: * @param value text element content
0712: * @return this context (to allow chained calls)
0713: * @throws JiBXException on any error (possibly wrapping other exception)
0714: */
0715:
0716: public MarshallingContext element(int index, String name,
0717: String value) throws JiBXException {
0718: try {
0719: if (value.length() == 0) {
0720: m_writer.startTagOpen(index, name);
0721: m_writer.closeEmptyTag();
0722: } else {
0723: m_writer.startTagClosed(index, name);
0724: m_writer.writeTextContent(value);
0725: m_writer.endTag(index, name);
0726: }
0727: return this ;
0728: } catch (IOException ex) {
0729: throw new JiBXException(
0730: "Error writing marshalled document", ex);
0731: } catch (Exception ex) {
0732: String text = buildNameString(index, name);
0733: if (value == null) {
0734: throw new JiBXException("null value for element "
0735: + text + " from object of type "
0736: + getStackTop().getClass().getName());
0737: } else {
0738: throw new JiBXException(
0739: "Exception while marshalling element " + text,
0740: ex);
0741: }
0742: }
0743: }
0744:
0745: /**
0746: * Generate complete element with integer content.
0747: *
0748: * @param index namespace URI index number
0749: * @param name element name
0750: * @param value integer element content
0751: * @return this context (to allow chained calls)
0752: * @throws JiBXException on any error (possibly wrapping other exception)
0753: */
0754:
0755: public MarshallingContext element(int index, String name, int value)
0756: throws JiBXException {
0757: return element(index, name, Integer.toString(value));
0758: }
0759:
0760: /**
0761: * Generate complete element with enumeration content. The actual text to be
0762: * written is obtained by indexing into the supplied array of values.
0763: *
0764: * @param index namespace URI index number
0765: * @param name element name
0766: * @param value integer enumeration value (zero-based)
0767: * @param table text values in enumeration
0768: * @return this context (to allow chained calls)
0769: * @throws JiBXException on any error (possibly wrapping other exception)
0770: */
0771:
0772: public MarshallingContext element(int index, String name,
0773: int value, String[] table) throws JiBXException {
0774: try {
0775: return element(index, name, table[value]);
0776: } catch (ArrayIndexOutOfBoundsException ex) {
0777: throw new JiBXException("Enumeration value of " + value
0778: + " is outside to allowed range of 0 to "
0779: + table.length);
0780: }
0781: }
0782:
0783: /**
0784: * Write CDATA text to document.
0785: *
0786: * @param text content value text
0787: * @return this context (to allow chained calls)
0788: * @throws IOException on error writing to document
0789: */
0790:
0791: public MarshallingContext writeCData(String text)
0792: throws IOException {
0793: try {
0794: m_writer.writeCData(text);
0795: return this ;
0796: } catch (NullPointerException e) {
0797: if (text == null) {
0798: throw new IOException(
0799: "Null value writing CDATA from object of type "
0800: + getStackTop().getClass().getName());
0801: } else {
0802: throw e;
0803: }
0804: }
0805: }
0806:
0807: /**
0808: * Write content value with character entity substitutions.
0809: *
0810: * @param text content value text
0811: * @return this context (to allow chained calls)
0812: * @throws IOException on error writing to document
0813: */
0814:
0815: public MarshallingContext writeContent(String text)
0816: throws IOException {
0817: try {
0818: m_writer.writeTextContent(text);
0819: return this ;
0820: } catch (NullPointerException e) {
0821: if (text == null) {
0822: throw new IOException(
0823: "Null value writing text content from object "
0824: + getStackTop().getClass().getName());
0825: } else {
0826: throw e;
0827: }
0828: }
0829: }
0830:
0831: /**
0832: * Marshal all items in a collection. This variation is for generic
0833: * collections.
0834: *
0835: * @param col collection of items to be marshalled
0836: * @return this context (to allow chained calls)
0837: * @throws JiBXException on any error (possibly wrapping other exception)
0838: */
0839:
0840: public MarshallingContext marshalCollection(Collection col)
0841: throws JiBXException {
0842: Iterator iter = col.iterator();
0843: while (iter.hasNext()) {
0844: Object obj = iter.next();
0845: if (obj instanceof IMarshallable) {
0846: ((IMarshallable) obj).marshal(this );
0847: } else {
0848: throw new JiBXException(
0849: "Unmarshallable object of class "
0850: + obj.getClass()
0851: + " found in marshalling");
0852: }
0853: }
0854: return this ;
0855: }
0856:
0857: /**
0858: * Marshal all items in a collection. This variation is for ArrayList
0859: * collections.
0860: *
0861: * @param col collection of items to be marshalled
0862: * @return this context (to allow chained calls)
0863: * @throws JiBXException on any error (possibly wrapping other exception)
0864: */
0865:
0866: public MarshallingContext marshalCollection(ArrayList col)
0867: throws JiBXException {
0868: for (int i = 0; i < col.size(); i++) {
0869: Object obj = col.get(i);
0870: if (obj instanceof IMarshallable) {
0871: ((IMarshallable) obj).marshal(this );
0872: } else {
0873: throw new JiBXException(
0874: "Unmarshallable object of class "
0875: + obj.getClass().getName()
0876: + " found in marshalling");
0877: }
0878: }
0879: return this ;
0880: }
0881:
0882: /**
0883: * Marshal all items in a collection. This variation is for Vector
0884: * collections.
0885: *
0886: * @param col collection of items to be marshalled
0887: * @return this context (to allow chained calls)
0888: * @throws JiBXException on any error (possibly wrapping other exception)
0889: */
0890:
0891: public MarshallingContext marshalCollection(Vector col)
0892: throws JiBXException {
0893: for (int i = 0; i < col.size(); i++) {
0894: Object obj = col.elementAt(i);
0895: if (obj instanceof IMarshallable) {
0896: ((IMarshallable) obj).marshal(this );
0897: } else {
0898: throw new JiBXException(
0899: "Unmarshallable object of class "
0900: + obj.getClass().getName()
0901: + " found in marshalling");
0902: }
0903: }
0904: return this ;
0905: }
0906:
0907: /**
0908: * Define marshalling for class. Adds the marshalling definition using fixed
0909: * indexes for each class, allowing direct lookup of the marshaller when
0910: * multiple versions are defined.
0911: *
0912: * @param index class index for marshalling definition
0913: * @param name marshaller class name handling
0914: */
0915:
0916: public void addMarshalling(int index, String name) {
0917: m_marshallerClasses[index] = name;
0918: }
0919:
0920: /**
0921: * Undefine marshalling for element. Removes the marshalling
0922: * definition for a particular class index.
0923: *
0924: * @param index class index for marshalling definition
0925: */
0926:
0927: public void removeMarshalling(int index) {
0928: m_marshallers[index] = null;
0929: }
0930:
0931: /**
0932: * Generate start tag for element with namespaces. This creates the actual
0933: * start tag, along with any necessary namespace declarations. Previously
0934: * active namespace declarations are not duplicated. The tag is
0935: * left incomplete, allowing other attributes to be added.
0936: *
0937: * TODO: Handle nested default namespaces declarations, prefixes for outers
0938: *
0939: * @param index namespace URI index number
0940: * @param name element name
0941: * @param nums array of namespace indexes defined by this element (must
0942: * be constant, reference is kept until end of element)
0943: * @param prefs array of namespace prefixes mapped by this element (no
0944: * <code>null</code> values, use "" for default namespace declaration)
0945: * @return this context (to allow chained calls)
0946: * @throws JiBXException on any error (possibly wrapping other exception)
0947: */
0948:
0949: public MarshallingContext startTagNamespaces(int index,
0950: String name, int[] nums, String[] prefs)
0951: throws JiBXException {
0952: try {
0953: m_writer.startTagNamespaces(index, name, nums, prefs);
0954: return this ;
0955: } catch (IOException ex) {
0956: throw new JiBXException(
0957: "Error writing marshalled document", ex);
0958: }
0959: }
0960:
0961: /**
0962: * Find the marshaller for a particular class index
0963: * in the current context.
0964: * TODO: Eliminate the string passing, since it's not a common enough
0965: * problem to be worth checking (and with abstract mappings can be really
0966: * difficult to set properly)
0967: *
0968: * @param index class index for marshalling definition
0969: * @param name fully qualified name of class to be marshalled (used only
0970: * for validation)
0971: * @return marshalling handler for class
0972: * @throws JiBXException on any error (possibly wrapping other exception)
0973: */
0974:
0975: public IMarshaller getMarshaller(int index, String name)
0976: throws JiBXException {
0977: if (index >= m_classes.length || !name.equals(m_classes[index])) {
0978: throw new JiBXException(
0979: "Marshalling not defined for class " + name);
0980: }
0981: if (m_marshallers[index] == null) {
0982:
0983: // load the marshaller class and create an instance
0984: String mname = m_marshallerClasses[index];
0985: if (mname == null) {
0986: throw new JiBXException(
0987: "No marshaller defined for class " + name);
0988: }
0989: try {
0990:
0991: // first try loading class from binding factory class loader
0992: Class clas = null;
0993: ClassLoader factldr = null;
0994: if (m_factory != null) {
0995: factldr = m_factory.getClass().getClassLoader();
0996: try {
0997: clas = factldr.loadClass(mname);
0998: } catch (ClassNotFoundException e) { /* fall through */
0999: }
1000: }
1001: if (clas == null) {
1002:
1003: // next try the context class loader, if set
1004: ClassLoader ctxldr = Thread.currentThread()
1005: .getContextClassLoader();
1006: if (ctxldr != null) {
1007: try {
1008: clas = ctxldr.loadClass(mname);
1009: } catch (ClassNotFoundException e) { /* fall through */
1010: }
1011: }
1012: if (clas == null) {
1013:
1014: // not found, try the loader that loaded this class
1015: ClassLoader this ldr = MarshallingContext.class
1016: .getClassLoader();
1017: if (this ldr != factldr && this ldr != ctxldr) {
1018: try {
1019: clas = this ldr.loadClass(mname);
1020: } catch (ClassNotFoundException e) { /* fall through */
1021: }
1022: }
1023: }
1024: }
1025: if (clas == null) {
1026: throw new JiBXException(
1027: "Unable to load marshaller class " + mname);
1028: }
1029:
1030: // create an instance of marshaller class
1031: IMarshaller m = (IMarshaller) clas.newInstance();
1032: m_marshallers[index] = m;
1033:
1034: } catch (JiBXException e) {
1035: throw e;
1036: } catch (Exception e) {
1037: throw new JiBXException(
1038: "Unable to create marshaller of class " + mname
1039: + ":", e);
1040: }
1041: }
1042: return m_marshallers[index];
1043: }
1044:
1045: /**
1046: * Marshal document from root object. This internal method just verifies
1047: * that the object is marshallable, then calls the marshal method on the
1048: * object itself.
1049: *
1050: * @param root object at root of structure to be marshalled, which must have
1051: * a top-level mapping in the binding
1052: * @throws JiBXException on any error (possibly wrapping other exception)
1053: */
1054:
1055: protected void marshalRoot(Object root) throws JiBXException {
1056: if (root instanceof IMarshallable) {
1057: boolean valid = false;
1058: IMarshallable mable = (IMarshallable) root;
1059: int index = mable.JiBX_getIndex();
1060: if (index < m_classes.length) {
1061: String cname = m_classes[index];
1062: Class mclass = mable.getClass();
1063: while (!mclass.getName().equals("java.lang.Object")) {
1064: if (mclass.getName().equals(cname)) {
1065: valid = true;
1066: break;
1067: } else {
1068: mclass = mclass.getSuperclass();
1069: }
1070: }
1071: }
1072: if (valid) {
1073: mable.marshal(this );
1074: } else {
1075: throw new JiBXException(
1076: "Supplied root object of class "
1077: + root.getClass().getName()
1078: + " is incompatible with the binding used for this context");
1079: }
1080: } else {
1081: throw new JiBXException("Supplied root object of class "
1082: + root.getClass().getName()
1083: + " cannot be marshalled without top-level mapping");
1084: }
1085: }
1086:
1087: /**
1088: * Marshal document from root object without XML declaration. This can only
1089: * be validly called immediately following one of the set output methods;
1090: * otherwise the output document will be corrupt. The effect of this method
1091: * is the same as the sequence of a call to marshal the root object using
1092: * this context followed by a call to {@link #endDocument}.
1093: *
1094: * @param root object at root of structure to be marshalled, which must have
1095: * a top-level mapping in the binding
1096: * @throws JiBXException on any error (possibly wrapping other exception)
1097: */
1098:
1099: public void marshalDocument(Object root) throws JiBXException {
1100: marshalRoot(root);
1101: endDocument();
1102: }
1103:
1104: /**
1105: * Marshal document from root object. This can only be validly called
1106: * immediately following one of the set output methods; otherwise the output
1107: * document will be corrupt. The effect of this method is the same as the
1108: * sequence of a call to {@link #startDocument}, a call to marshal the root
1109: * object using this context, and finally a call to {@link #endDocument}.
1110: *
1111: * @param root object at root of structure to be marshalled, which must have
1112: * a top-level mapping in the binding
1113: * @param enc document encoding, <code>null</code> if not specified
1114: * @param alone standalone document flag, <code>null</code> if not
1115: * specified
1116: * @throws JiBXException on any error (possibly wrapping other exception)
1117: */
1118:
1119: public void marshalDocument(Object root, String enc, Boolean alone)
1120: throws JiBXException {
1121: startDocument(enc, alone);
1122: marshalRoot(root);
1123: endDocument();
1124: }
1125:
1126: /**
1127: * Marshal document from root object to output stream with encoding. The
1128: * effect of this method is the same as the sequence of a call to {@link
1129: * #startDocument}, a call to marshal the root object using this context,
1130: * and finally a call to {@link #endDocument}.
1131: *
1132: * @param root object at root of structure to be marshalled, which must have
1133: * a top-level mapping in the binding
1134: * @param enc document encoding, <code>null</code> if not specified
1135: * @param alone standalone document flag, <code>null</code> if not
1136: * specified
1137: * @param outs stream for document data output
1138: * @throws JiBXException on any error (possibly wrapping other exception)
1139: */
1140:
1141: public void marshalDocument(Object root, String enc, Boolean alone,
1142: OutputStream outs) throws JiBXException {
1143: startDocument(enc, alone, outs);
1144: marshalRoot(root);
1145: endDocument();
1146: }
1147:
1148: /**
1149: * Marshal document from root object to writer. The effect of this method
1150: * is the same as the sequence of a call to {@link #startDocument}, a call
1151: * to marshal the root object using this context, and finally a call to
1152: * {@link #endDocument}.
1153: *
1154: * @param root object at root of structure to be marshalled, which must have
1155: * a top-level mapping in the binding
1156: * @param enc document encoding, <code>null</code> if not specified
1157: * @param alone standalone document flag, <code>null</code> if not
1158: * specified
1159: * @param outw writer for document data output
1160: * @throws JiBXException on any error (possibly wrapping other exception)
1161: */
1162:
1163: public void marshalDocument(Object root, String enc, Boolean alone,
1164: Writer outw) throws JiBXException {
1165: startDocument(enc, alone, outw);
1166: marshalRoot(root);
1167: endDocument();
1168: }
1169:
1170: /**
1171: * Get shared ID map. The ID map returned is not used directly by the
1172: * marshalling code, but is provided to support user extensions.
1173: *
1174: * @return ID map
1175: */
1176:
1177: public HashMap getIdMap() {
1178: if (m_idMap == null) {
1179: m_idMap = new HashMap();
1180: }
1181: return m_idMap;
1182: }
1183:
1184: /**
1185: * Set a user context object. This context object is not used directly by
1186: * JiBX, but can be accessed by all types of user extension methods. The
1187: * context object is automatically cleared by the {@link #reset()} method,
1188: * so to make use of this you need to first call the appropriate version of
1189: * the <code>setOutput()</code> method, then this method, and finally one of
1190: * the <code>marshalDocument</code> methods which uses the previously-set
1191: * output (not the ones which take a stream or writer as parameter, since
1192: * they call <code>setOutput()</code> themselves).
1193: *
1194: * @param obj user context object, or <code>null</code> if clearing existing
1195: * context object
1196: * @see #getUserContext()
1197: */
1198: public void setUserContext(Object obj) {
1199: m_userContext = obj;
1200: }
1201:
1202: /**
1203: * Get the user context object.
1204: *
1205: * @return user context object, or <code>null</code> if no context object
1206: * set
1207: * @see #setUserContext(Object)
1208: */
1209: public Object getUserContext() {
1210: return m_userContext;
1211: }
1212:
1213: /**
1214: * Push created object to marshalling stack. This must be called before
1215: * beginning the marshalling of the object. It is only called for objects
1216: * with structure, not for those converted directly to and from text.
1217: *
1218: * @param obj object being marshalled
1219: */
1220:
1221: public void pushObject(Object obj) {
1222: int depth = m_stackDepth;
1223: if (depth >= m_objectStack.length) {
1224: Object[] stack = new Object[depth * 2];
1225: System.arraycopy(m_objectStack, 0, stack, 0, depth);
1226: m_objectStack = stack;
1227: }
1228: m_objectStack[depth] = obj;
1229: m_stackDepth++;
1230: }
1231:
1232: /**
1233: * Pop marshalled object from stack.
1234: *
1235: * @throws JiBXException if no object on stack
1236: */
1237:
1238: public void popObject() throws JiBXException {
1239: if (m_stackDepth > 0) {
1240: --m_stackDepth;
1241: } else {
1242: throw new JiBXException("No object on stack");
1243: }
1244: }
1245:
1246: /**
1247: * Get current marshalling object stack depth. This allows tracking
1248: * nested calls to marshal one object while in the process of
1249: * marshalling another object. The bottom item on the stack is always the
1250: * root object being marshalled.
1251: *
1252: * @return number of objects in marshalling stack
1253: */
1254:
1255: public int getStackDepth() {
1256: return m_stackDepth;
1257: }
1258:
1259: /**
1260: * Get object from marshalling stack. This stack allows tracking nested
1261: * calls to marshal one object while in the process of marshalling
1262: * another object. The bottom item on the stack is always the root object
1263: * being marshalled.
1264: *
1265: * @param depth object depth in stack to be retrieved (must be in the range
1266: * of zero to the current depth minus one).
1267: * @return object from marshalling stack
1268: */
1269:
1270: public Object getStackObject(int depth) {
1271: return m_objectStack[m_stackDepth - depth - 1];
1272: }
1273:
1274: /**
1275: * Get top object on marshalling stack. This is safe to call even when no
1276: * objects are on the stack.
1277: *
1278: * @return object from marshalling stack, or <code>null</code> if none
1279: */
1280:
1281: public Object getStackTop() {
1282: if (m_stackDepth > 0) {
1283: return m_objectStack[m_stackDepth - 1];
1284: } else {
1285: return null;
1286: }
1287: }
1288: }
|