/* Copyright (c) 2002-2007, Dennis M. Sosnoski. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jibx.runtime.impl; import java.io.*; import java.util.*; import org.jibx.runtime.*; import org.jibx.runtime.ICharacterEscaper; import org.jibx.runtime.IMarshallable; import org.jibx.runtime.IMarshaller; import org.jibx.runtime.IMarshallingContext; import org.jibx.runtime.JiBXException; /** * JiBX serializer supplying convenience methods for marshalling. Most of these * methods are designed for use in code generated by the binding generator. * * @author Dennis M. Sosnoski */ public class MarshallingContext implements IMarshallingContext { /** Fixed XML namespace. */ public static final String XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"; /** Starting size for object stack. */ private static final int INITIAL_STACK_SIZE = 20; /** Binding factory used to create this unmarshaller. */ private IBindingFactory m_factory; /** Names of classes included in mapping definition. */ private String[] m_classes; /** Number of classes with global marshallers. */ private int m_globalCount; /** Marshaller classes for mapping definition (null for mappings out of context). */ private String[] m_marshallerClasses; /** Marshallers for classes in mapping definition (lazy create of actual marshaller instances) */ private IMarshaller[] m_marshallers; /** URIs for namespaces used in binding. */ private String[] m_uris; /** Current marshalling stack depth. */ private int m_stackDepth; /** Stack of objects being marshalled. */ private Object[] m_objectStack; /** Indent character count per level. */ private int m_indentCount; /** Character sequence for end of line. */ private String m_newLine; /** Character used for indenting. */ private char m_indentChar; /** Shared map from IDs to objects. This is not used directly by the marshalling code, but is available for user extensions (lazy create). */ private HashMap m_idMap; /** Output document handler. */ private IXMLWriter m_writer; /** User context object (not used by JiBX, only for user convenience). */ protected Object m_userContext; /** * Constructor. * * @param classes ordered array of class names included in mapping * definition (reference kept, must be constant) * @param mcs names of marshaller classes for indexes with fixed marshallers * (as opposed to mapping slots, which may be overridden; reference kept, * must be constant) * @param uris ordered array of URIs for namespaces used in binding (must * be constant; the value in position 0 must always be the empty string "", * and the value in position 1 must always be the XML namespace * "http://www.w3.org/XML/1998/namespace") * @param ifact binding factory creating this unmarshaller */ public MarshallingContext(String[] classes, String[] mcs, String[] uris, IBindingFactory ifact) { m_classes = classes; m_globalCount = mcs.length; m_marshallers = new IMarshaller[classes.length]; m_marshallerClasses = new String[classes.length]; System.arraycopy(mcs, 0, m_marshallerClasses, 0, mcs.length); m_uris = uris; m_objectStack = new Object[INITIAL_STACK_SIZE]; m_indentCount = -1; m_indentChar = ' '; m_newLine = "\n"; m_factory = ifact; } /** * Create character escaper for encoding. * * @param enc document output encoding, or null for default * @return character escaper for encoding * @throws JiBXException if error creating setting output */ private ICharacterEscaper createEscaper(String enc) throws JiBXException { if (enc.equalsIgnoreCase("UTF-8") || enc.equalsIgnoreCase("UTF-16") || enc.equalsIgnoreCase("UTF-16BE") || enc.equalsIgnoreCase("UTF-16LE")) { return UTF8Escaper.getInstance(); } else if (enc.equalsIgnoreCase("ISO-8859-1")) { return ISO88591Escaper.getInstance(); } else if (enc.equalsIgnoreCase("US-ASCII")) { return USASCIIEscaper.getInstance(); } else { throw new JiBXException ("No character escaper defined for encoding " + enc); } } /** * Set output stream with encoding and escaper. This forces handling of the * output stream to use the Java character encoding support with the * supplied escaper. * * @param outs stream for document data output * @param enc document output encoding, or null uses UTF-8 * default * @param esc escaper for writing characters to stream * @throws JiBXException if error setting output */ public void setOutput(OutputStream outs, String enc, ICharacterEscaper esc) throws JiBXException { try { if (enc == null) { enc = "UTF-8"; } // handle any other encodings using library support if (!(m_writer instanceof GenericXMLWriter)) { m_writer = new GenericXMLWriter(m_uris); m_writer.setIndentSpaces(m_indentCount, m_newLine, m_indentChar); } Writer writer = new BufferedWriter (new OutputStreamWriter(outs, enc)); ((GenericXMLWriter)m_writer).setOutput(writer, esc); reset(); } catch (IOException ex) { throw new JiBXException("Error setting output", ex); } } /** * Set output stream and encoding. * * @param outs stream for document data output * @param enc document output encoding, or null for default * @throws JiBXException if error creating setting output */ public void setOutput(OutputStream outs, String enc) throws JiBXException { if (enc == null) { enc = "UTF-8"; } if ("UTF-8".equalsIgnoreCase(enc)) { // handle UTF-8 output to stream directly if (!(m_writer instanceof UTF8StreamWriter)) { m_writer = new UTF8StreamWriter(m_uris); m_writer.setIndentSpaces(m_indentCount, m_newLine, m_indentChar); } ((UTF8StreamWriter)m_writer).setOutput(outs); reset(); } else if ("ISO-8859-1".equalsIgnoreCase(enc)) { // handle ISO-8859-1 output to stream directly if (!(m_writer instanceof ISO88591StreamWriter)) { m_writer = new ISO88591StreamWriter(m_uris); m_writer.setIndentSpaces(m_indentCount, m_newLine, m_indentChar); } ((ISO88591StreamWriter)m_writer).setOutput(outs); reset(); } else { setOutput(outs, enc, createEscaper(enc)); } } /** * Set output writer and escaper. * * @param outw writer for document data output * @param esc escaper for writing characters */ public void setOutput(Writer outw, ICharacterEscaper esc) { if (!(m_writer instanceof GenericXMLWriter)) { m_writer = new GenericXMLWriter(m_uris); m_writer.setIndentSpaces(m_indentCount, m_newLine, m_indentChar); } ((GenericXMLWriter)m_writer).setOutput(outw, esc); reset(); } /** * Set output writer. * * @param outw writer for document data output */ public void setOutput(Writer outw) { setOutput(outw, UTF8Escaper.getInstance()); } /** * Get the writer being used for output. * * @return XML writer used for output */ public IXMLWriter getXmlWriter() { return m_writer; } /** * Set the writer being used for output. * * @param xwrite XML writer used for output */ public void setXmlWriter(IXMLWriter xwrite) { m_writer = xwrite; } /** * Get current nesting indent spaces. This returns the number of spaces used * to show indenting, if used. * * @return number of spaces indented per level, or negative if indentation * disabled */ public int getIndent() { return m_indentCount; } /** * Set nesting indent spaces. This is advisory only, and implementations of * this interface are free to ignore it. The intent is to indicate that the * generated output should use indenting to illustrate element nesting. * * @param count number of spaces to indent per level, or disable * indentation if negative */ public void setIndent(int count) { if (m_writer != null) { m_writer.setIndentSpaces(count, m_newLine, m_indentChar); } m_indentCount = count; } /** * Set nesting indentation. This is advisory only, and implementations of * this interface are free to ignore it. The intent is to indicate that the * generated output should use indenting to illustrate element nesting. * * @param count number of character to indent per level, or disable * indentation if negative (zero means new line only) * @param newline sequence of characters used for a line ending * (null means use the single character '\n') * @param indent whitespace character used for indentation */ public void setIndent(int count, String newline, char indent) { if (m_writer != null) { m_writer.setIndentSpaces(count, newline, indent); } m_indentCount = count; m_newLine = newline; m_indentChar = indent; } /** * Initializes the context to use the same marshalled text destination and * parameters as another marshalling context. This method is designed for * use when an initial context needs to create and invoke a secondary * context (generally from a different binding) in the course of an * marshalling operation. Note that once the secondary context has been used * it's generally necessary to do a {@link XMLWriterBase#flush()} operation * on the writer used by the that context before resuming output on the * parent. * * @param parent context supplying target for marshalled document text * @throws IOException on error writing output */ public void setFromContext(MarshallingContext parent) throws IOException { reset(); m_indentCount = parent.m_indentCount; m_newLine = parent.m_newLine; m_indentChar = parent.m_indentChar; if (parent.m_writer instanceof IExtensibleWriter) { IExtensibleWriter base = (IExtensibleWriter)parent.m_writer; base.flush(); m_writer = base.createChildWriter(m_uris); } else if (parent.m_writer instanceof StAXWriter) { m_writer = ((StAXWriter)parent.m_writer).createChildWriter(m_uris); } else { m_writer = parent.m_writer; } } /** * Reset to initial state for reuse. The context is serially reusable, * as long as this method is called to clear any retained state information * between uses. It is automatically called when output is set. */ public void reset() { if (m_writer != null) { m_writer.reset(); } for (int i = m_globalCount; i < m_marshallers.length; i++) { m_marshallers[i] = null; } for (int i = 0; i < m_objectStack.length; i++) { m_objectStack[i] = null; } m_stackDepth = 0; } /** * Return the binding factory used to create this unmarshaller. * * @return binding factory */ public IBindingFactory getFactory() { return m_factory; } /** * Get namespace URIs for mapping. This gets the full ordered array of * namespaces known in the binding used for this marshalling, where the * index number of each namespace URI is the namespace index used to lookup * the prefix when marshalling a name in that namespace. The returned array * must not be modified. * * @return array of namespaces */ public String[] getNamespaces() { return m_uris; } /** * Start document. This can only be validly called immediately following * one of the set output methods; otherwise the output document will be * corrupt. * * @param enc document encoding, null if not specified * @param alone standalone document flag, null if not * specified * @throws JiBXException on any error (possibly wrapping other exception) */ public void startDocument(String enc, Boolean alone) throws JiBXException { try { String atext = null; if (alone != null) { atext = alone.booleanValue() ? "yes" : "no"; } m_writer.writeXMLDecl("1.0", enc, atext); } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Start document with output stream and encoding. The effect is the same * as from first setting the output stream and encoding, then making the * call to start document. * * @param enc document encoding, null if not specified * @param alone standalone document flag, null if not * specified * @param outs stream for document data output * @throws JiBXException on any error (possibly wrapping other exception) */ public void startDocument(String enc, Boolean alone, OutputStream outs) throws JiBXException { setOutput(outs, enc); startDocument(enc, alone); } /** * Start document with writer. The effect is the same as from first * setting the writer, then making the call to start document. * * @param enc document encoding, null if not specified * @param alone standalone document flag, null if not * specified * @param outw writer for document data output * @throws JiBXException on any error (possibly wrapping other exception) */ public void startDocument(String enc, Boolean alone, Writer outw) throws JiBXException { setOutput(outw); startDocument(enc, alone); } /** * End document. Finishes all output and closes the document. Note that if * this is called with an imcomplete marshalling the result will not be * well-formed XML. * * @throws JiBXException on any error (possibly wrapping other exception) */ public void endDocument() throws JiBXException { try { m_writer.close(); } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Build name with optional namespace. Just returns the appropriate * name format. * * @param index namespace URI index number * @param name local name part of name * @return formatted name string */ public String buildNameString(int index, String name) { String ns = m_writer.getNamespaceUri(index); if (ns == null || "".equals(ns)) { return "\"" + name + "\""; } else { return "\"{" + ns + "}" + name + "\""; } } /** * Generate start tag for element without attributes. * * @param index namespace URI index number * @param name element name * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext startTag(int index, String name) throws JiBXException { try { m_writer.startTagClosed(index, name); return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Generate start tag for element with attributes. This only opens the start * tag, allowing attributes to be added immediately following this call. * * @param index namespace URI index number * @param name element name * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext startTagAttributes(int index, String name) throws JiBXException { try { m_writer.startTagOpen(index, name); return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Generate text attribute. This can only be used following an open start * tag with attributes. * * @param index namespace URI index number * @param name attribute name * @param value text value for attribute (cannot be null) * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext attribute(int index, String name, String value) throws JiBXException { try { m_writer.addAttribute(index, name, value); return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } catch (Exception ex) { String text = buildNameString(index, name); if (value == null) { throw new JiBXException("null value for attribute " + text + " from object of type " + getStackTop().getClass().getName()); } else { throw new JiBXException ("Exception while marshalling attribute " + text, ex); } } } /** * Generate integer attribute. This can only be used following an open start * tag. * * @param index namespace URI index number * @param name attribute name * @param value integer value for attribute * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext attribute(int index, String name, int value) throws JiBXException { return attribute(index, name, Integer.toString(value)); } /** * Generate enumeration attribute. The actual text to be written is obtained * by indexing into the supplied array of values. This can only be used * following an open start tag. * * @param index namespace URI index number * @param name attribute name * @param value integer enumeration value (zero-based) * @param table text values in enumeration * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext attribute(int index, String name, int value, String[] table) throws JiBXException { try { return attribute(index, name, table[value]); } catch (ArrayIndexOutOfBoundsException ex) { throw new JiBXException("Enumeration value of " + value + " is outside to allowed range of 0 to " + table.length); } } /** * Close start tag with content to follow. * * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext closeStartContent() throws JiBXException { try { m_writer.closeStartTag(); return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Close start tag with no content (empty tag). * * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext closeStartEmpty() throws JiBXException { try { m_writer.closeEmptyTag(); return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Add text content to current element. * * @param value text element content * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext content(String value) throws JiBXException { try { m_writer.writeTextContent(value); return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Add integer content to current element. * * @param value integer element content * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext content(int value) throws JiBXException { content(Integer.toString(value)); return this; } /** * Add enumeration content to current element. The actual text to be * written is obtained by indexing into the supplied array of values. * * @param value integer enumeration value (zero-based) * @param table text values in enumeration * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext content(int value, String[] table) throws JiBXException { try { content(table[value]); return this; } catch (ArrayIndexOutOfBoundsException ex) { throw new JiBXException("Enumeration value of " + value + " is outside to allowed range of 0 to " + table.length); } } /** * Generate end tag for element. * * @param index namespace URI index number * @param name element name * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext endTag(int index, String name) throws JiBXException { try { m_writer.endTag(index, name); return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Generate complete element with text content. * * @param index namespace URI index number * @param name element name * @param value text element content * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext element(int index, String name, String value) throws JiBXException { try { if (value.length() == 0) { m_writer.startTagOpen(index, name); m_writer.closeEmptyTag(); } else { m_writer.startTagClosed(index, name); m_writer.writeTextContent(value); m_writer.endTag(index, name); } return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } catch (Exception ex) { String text = buildNameString(index, name); if (value == null) { throw new JiBXException("null value for element " + text + " from object of type " + getStackTop().getClass().getName()); } else { throw new JiBXException ("Exception while marshalling element " + text, ex); } } } /** * Generate complete element with integer content. * * @param index namespace URI index number * @param name element name * @param value integer element content * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext element(int index, String name, int value) throws JiBXException { return element(index, name, Integer.toString(value)); } /** * Generate complete element with enumeration content. The actual text to be * written is obtained by indexing into the supplied array of values. * * @param index namespace URI index number * @param name element name * @param value integer enumeration value (zero-based) * @param table text values in enumeration * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext element(int index, String name, int value, String[] table) throws JiBXException { try { return element(index, name, table[value]); } catch (ArrayIndexOutOfBoundsException ex) { throw new JiBXException("Enumeration value of " + value + " is outside to allowed range of 0 to " + table.length); } } /** * Write CDATA text to document. * * @param text content value text * @return this context (to allow chained calls) * @throws IOException on error writing to document */ public MarshallingContext writeCData(String text) throws IOException { try { m_writer.writeCData(text); return this; } catch (NullPointerException e) { if (text == null) { throw new IOException ("Null value writing CDATA from object of type " + getStackTop().getClass().getName()); } else { throw e; } } } /** * Write content value with character entity substitutions. * * @param text content value text * @return this context (to allow chained calls) * @throws IOException on error writing to document */ public MarshallingContext writeContent(String text) throws IOException { try { m_writer.writeTextContent(text); return this; } catch (NullPointerException e) { if (text == null) { throw new IOException ("Null value writing text content from object " + getStackTop().getClass().getName()); } else { throw e; } } } /** * Marshal all items in a collection. This variation is for generic * collections. * * @param col collection of items to be marshalled * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext marshalCollection(Collection col) throws JiBXException { Iterator iter = col.iterator(); while (iter.hasNext()) { Object obj = iter.next(); if (obj instanceof IMarshallable) { ((IMarshallable)obj).marshal(this); } else { throw new JiBXException ("Unmarshallable object of class " + obj.getClass() + " found in marshalling"); } } return this; } /** * Marshal all items in a collection. This variation is for ArrayList * collections. * * @param col collection of items to be marshalled * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext marshalCollection(ArrayList col) throws JiBXException { for (int i = 0; i < col.size(); i++) { Object obj = col.get(i); if (obj instanceof IMarshallable) { ((IMarshallable)obj).marshal(this); } else { throw new JiBXException ("Unmarshallable object of class " + obj.getClass().getName() + " found in marshalling"); } } return this; } /** * Marshal all items in a collection. This variation is for Vector * collections. * * @param col collection of items to be marshalled * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext marshalCollection(Vector col) throws JiBXException { for (int i = 0; i < col.size(); i++) { Object obj = col.elementAt(i); if (obj instanceof IMarshallable) { ((IMarshallable)obj).marshal(this); } else { throw new JiBXException ("Unmarshallable object of class " + obj.getClass().getName() + " found in marshalling"); } } return this; } /** * Define marshalling for class. Adds the marshalling definition using fixed * indexes for each class, allowing direct lookup of the marshaller when * multiple versions are defined. * * @param index class index for marshalling definition * @param name marshaller class name handling */ public void addMarshalling(int index, String name) { m_marshallerClasses[index] = name; } /** * Undefine marshalling for element. Removes the marshalling * definition for a particular class index. * * @param index class index for marshalling definition */ public void removeMarshalling(int index) { m_marshallers[index] = null; } /** * Generate start tag for element with namespaces. This creates the actual * start tag, along with any necessary namespace declarations. Previously * active namespace declarations are not duplicated. The tag is * left incomplete, allowing other attributes to be added. * * TODO: Handle nested default namespaces declarations, prefixes for outers * * @param index namespace URI index number * @param name element name * @param nums array of namespace indexes defined by this element (must * be constant, reference is kept until end of element) * @param prefs array of namespace prefixes mapped by this element (no * null values, use "" for default namespace declaration) * @return this context (to allow chained calls) * @throws JiBXException on any error (possibly wrapping other exception) */ public MarshallingContext startTagNamespaces(int index, String name, int[] nums, String[] prefs) throws JiBXException { try { m_writer.startTagNamespaces(index, name, nums, prefs); return this; } catch (IOException ex) { throw new JiBXException("Error writing marshalled document", ex); } } /** * Find the marshaller for a particular class index * in the current context. * TODO: Eliminate the string passing, since it's not a common enough * problem to be worth checking (and with abstract mappings can be really * difficult to set properly) * * @param index class index for marshalling definition * @param name fully qualified name of class to be marshalled (used only * for validation) * @return marshalling handler for class * @throws JiBXException on any error (possibly wrapping other exception) */ public IMarshaller getMarshaller(int index, String name) throws JiBXException { if (index >= m_classes.length || !name.equals(m_classes[index])) { throw new JiBXException("Marshalling not defined for class " + name); } if (m_marshallers[index] == null) { // load the marshaller class and create an instance String mname = m_marshallerClasses[index]; if (mname == null) { throw new JiBXException("No marshaller defined for class " + name); } try { // first try loading class from binding factory class loader Class clas = null; ClassLoader factldr = null; if (m_factory != null) { factldr = m_factory.getClass().getClassLoader(); try { clas = factldr.loadClass(mname); } catch (ClassNotFoundException e) { /* fall through */ } } if (clas == null) { // next try the context class loader, if set ClassLoader ctxldr = Thread.currentThread().getContextClassLoader(); if (ctxldr != null) { try { clas = ctxldr.loadClass(mname); } catch (ClassNotFoundException e) { /* fall through */ } } if (clas == null) { // not found, try the loader that loaded this class ClassLoader thisldr = MarshallingContext.class.getClassLoader(); if (thisldr != factldr && thisldr != ctxldr) { try { clas = thisldr.loadClass(mname); } catch (ClassNotFoundException e) { /* fall through */ } } } } if (clas == null) { throw new JiBXException("Unable to load marshaller class " + mname); } // create an instance of marshaller class IMarshaller m = (IMarshaller)clas.newInstance(); m_marshallers[index] = m; } catch (JiBXException e) { throw e; } catch (Exception e) { throw new JiBXException ("Unable to create marshaller of class " + mname + ":", e); } } return m_marshallers[index]; } /** * Marshal document from root object. This internal method just verifies * that the object is marshallable, then calls the marshal method on the * object itself. * * @param root object at root of structure to be marshalled, which must have * a top-level mapping in the binding * @throws JiBXException on any error (possibly wrapping other exception) */ protected void marshalRoot(Object root) throws JiBXException { if (root instanceof IMarshallable) { boolean valid = false; IMarshallable mable = (IMarshallable)root; int index = mable.JiBX_getIndex(); if (index < m_classes.length) { String cname = m_classes[index]; Class mclass = mable.getClass(); while (!mclass.getName().equals("java.lang.Object")) { if (mclass.getName().equals(cname)) { valid = true; break; } else { mclass = mclass.getSuperclass(); } } } if (valid) { mable.marshal(this); } else { throw new JiBXException("Supplied root object of class " + root.getClass().getName() + " is incompatible with the binding used for this context"); } } else { throw new JiBXException("Supplied root object of class " + root.getClass().getName() + " cannot be marshalled without top-level mapping"); } } /** * Marshal document from root object without XML declaration. This can only * be validly called immediately following one of the set output methods; * otherwise the output document will be corrupt. The effect of this method * is the same as the sequence of a call to marshal the root object using * this context followed by a call to {@link #endDocument}. * * @param root object at root of structure to be marshalled, which must have * a top-level mapping in the binding * @throws JiBXException on any error (possibly wrapping other exception) */ public void marshalDocument(Object root) throws JiBXException { marshalRoot(root); endDocument(); } /** * Marshal document from root object. This can only be validly called * immediately following one of the set output methods; otherwise the output * document will be corrupt. The effect of this method is the same as the * sequence of a call to {@link #startDocument}, a call to marshal the root * object using this context, and finally a call to {@link #endDocument}. * * @param root object at root of structure to be marshalled, which must have * a top-level mapping in the binding * @param enc document encoding, null if not specified * @param alone standalone document flag, null if not * specified * @throws JiBXException on any error (possibly wrapping other exception) */ public void marshalDocument(Object root, String enc, Boolean alone) throws JiBXException { startDocument(enc, alone); marshalRoot(root); endDocument(); } /** * Marshal document from root object to output stream with encoding. The * effect of this method is the same as the sequence of a call to {@link * #startDocument}, a call to marshal the root object using this context, * and finally a call to {@link #endDocument}. * * @param root object at root of structure to be marshalled, which must have * a top-level mapping in the binding * @param enc document encoding, null if not specified * @param alone standalone document flag, null if not * specified * @param outs stream for document data output * @throws JiBXException on any error (possibly wrapping other exception) */ public void marshalDocument(Object root, String enc, Boolean alone, OutputStream outs) throws JiBXException { startDocument(enc, alone, outs); marshalRoot(root); endDocument(); } /** * Marshal document from root object to writer. The effect of this method * is the same as the sequence of a call to {@link #startDocument}, a call * to marshal the root object using this context, and finally a call to * {@link #endDocument}. * * @param root object at root of structure to be marshalled, which must have * a top-level mapping in the binding * @param enc document encoding, null if not specified * @param alone standalone document flag, null if not * specified * @param outw writer for document data output * @throws JiBXException on any error (possibly wrapping other exception) */ public void marshalDocument(Object root, String enc, Boolean alone, Writer outw) throws JiBXException { startDocument(enc, alone, outw); marshalRoot(root); endDocument(); } /** * Get shared ID map. The ID map returned is not used directly by the * marshalling code, but is provided to support user extensions. * * @return ID map */ public HashMap getIdMap() { if (m_idMap == null) { m_idMap = new HashMap(); } return m_idMap; } /** * Set a user context object. This context object is not used directly by * JiBX, but can be accessed by all types of user extension methods. The * context object is automatically cleared by the {@link #reset()} method, * so to make use of this you need to first call the appropriate version of * the setOutput() method, then this method, and finally one of * the marshalDocument methods which uses the previously-set * output (not the ones which take a stream or writer as parameter, since * they call setOutput() themselves). * * @param obj user context object, or null if clearing existing * context object * @see #getUserContext() */ public void setUserContext(Object obj) { m_userContext = obj; } /** * Get the user context object. * * @return user context object, or null if no context object * set * @see #setUserContext(Object) */ public Object getUserContext() { return m_userContext; } /** * Push created object to marshalling stack. This must be called before * beginning the marshalling of the object. It is only called for objects * with structure, not for those converted directly to and from text. * * @param obj object being marshalled */ public void pushObject(Object obj) { int depth = m_stackDepth; if (depth >= m_objectStack.length) { Object[] stack = new Object[depth*2]; System.arraycopy(m_objectStack, 0, stack, 0, depth); m_objectStack = stack; } m_objectStack[depth] = obj; m_stackDepth++; } /** * Pop marshalled object from stack. * * @throws JiBXException if no object on stack */ public void popObject() throws JiBXException { if (m_stackDepth > 0) { --m_stackDepth; } else { throw new JiBXException("No object on stack"); } } /** * Get current marshalling object stack depth. This allows tracking * nested calls to marshal one object while in the process of * marshalling another object. The bottom item on the stack is always the * root object being marshalled. * * @return number of objects in marshalling stack */ public int getStackDepth() { return m_stackDepth; } /** * Get object from marshalling stack. This stack allows tracking nested * calls to marshal one object while in the process of marshalling * another object. The bottom item on the stack is always the root object * being marshalled. * * @param depth object depth in stack to be retrieved (must be in the range * of zero to the current depth minus one). * @return object from marshalling stack */ public Object getStackObject(int depth) { return m_objectStack[m_stackDepth-depth-1]; } /** * Get top object on marshalling stack. This is safe to call even when no * objects are on the stack. * * @return object from marshalling stack, or null if none */ public Object getStackTop() { if (m_stackDepth > 0) { return m_objectStack[m_stackDepth-1]; } else { return null; } } }