/*
Copyright (c) 2002-2008, 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.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IUnmarshaller;
import org.jibx.runtime.IUnmarshallingContext;
import org.jibx.runtime.IXMLReader;
import org.jibx.runtime.JiBXConstrainedParseException;
import org.jibx.runtime.JiBXException;
import org.jibx.runtime.JiBXParseException;
import org.jibx.runtime.Utility;

/**
 * Pull parser wrapper supplying convenience methods for access. Most of
 * these methods are designed for use in code generated by the binding
 * generator.
 *
 * @author Dennis M. Sosnoski
 */
public class UnmarshallingContext implements IUnmarshallingContext
{
    /** Starting size for object stack. */
    private static final int INITIAL_STACK_SIZE = 20;
    
    /** Empty array of strings. */
    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    
    /** Factory for creating XML readers. */
    private static final IXMLReaderFactory s_readerFactory =
        RuntimeSupport.loadFactory();
    
    /** Binding factory used to create this unmarshaller. */
    private IBindingFactory m_factory;
    
    /** Map from fully-qualified class name to index in internal tables. */
    private StringIntHashMap m_classIndexMap;
    
    /** Parser in use. */
    private IXMLReader m_reader;
    
    /** Index past end of last fixed marshaller class. */
    private int m_transientBase;
    
    /** Transient unmarshaller classes for mapping definition (<code>null</code>
     for mappings out of context). */
    private String[] m_transientUnmarshallerClasses;
    
    //
    // Structures organized by mapping index number. Each unmarshaller in the
    // binding definition is assigned a unique index number by the binding
    // compiler. This includes both generated unmarshallers (from non-abstract
    // <mapping> definitions) and user-defined unmarshallers. Instances of
    // the unmarshaller classes are created as needed. The indexes for the
    // unmarshallers corresponding to named mappings always precede those for
    // internal mappings in the list.

    /** Unmarshallers for classes in mapping definition (lazy create of actual
     unmarshaller instances) */
    protected IUnmarshaller[] m_unmarshallers;
    
    //
    // Structures organized by mapping

    /** Namespaces for elements associated with class mappings. */
    protected String[] m_namespaces;

    /** Names for elements associated with class mappings. */
    protected String[] m_names;

    /** ID maps for finding references. */
    protected Map[] m_idMaps;

    /** Class names of referenced types (<code>null</code> unless class-specific
     IDs used). */
    protected String[] m_idClasses;
    
    /** Current unmarshalling stack depth. */
    protected int m_stackDepth;
    
    /** Stack of objects being unmarshalled. */
    protected Object[] m_objectStack;

    /** Mapping from element name to class index for global mappings (cached
     from binding factory). Entries are <code>Integer</code> values if single
     match, <code>int[]</code> if multiple matches, giving the mapping index. */
    protected Map m_unmarshalMap;
    
    /** Mapping from element name to class index for transient mappings. Entries
     are <code>Integer</code> values if single match, <code>ArrayList</code> if
     multiple matches, giving the mapping index. */
    protected Map m_transientUnmarshalMap;

    /** Last IDREF value parsed. */
    protected String m_idref;
    
    /** User context object (not used by JiBX, only for user convenience). */
    protected Object m_userContext;

    /**
     * Constructor. Builds the actual parser and initializes internal data
     * structures.
     *
     * @param nmap number of mapping definitions included
     * @param umcs names of unmarshaller classes for indexes with fixed
     * unmarshallers (as opposed to mapping slots, which may be overridden;
     * reference kept, must be constant)
     * @param nss namespaces for elements of classes with global definitions
     * @param names names for elements of classes with global definitions
     * @param idcs array of class names with IDs (<code>null</code> if no IDs or
     * global IDs)
     * @param ifact binding factory creating this unmarshaller
     */
    public UnmarshallingContext(int nmap, String[] umcs, String[] nss,
        String[] names, String[] idcs, IBindingFactory ifact) {
        
        // initialize internal unmarshaller state
        m_transientBase = nmap - umcs.length;
        m_transientUnmarshallerClasses = new String[nmap-m_transientBase];
        m_unmarshallers = new IUnmarshaller[nmap];
        m_namespaces = new String[nmap];
        System.arraycopy(nss, 0, m_namespaces, 0, nss.length);
        m_names = new String[nmap];
        System.arraycopy(names, 0, m_names, 0, names.length);
        m_idClasses = idcs;
        int size = idcs == null ? 1 : idcs.length;
        m_idMaps = new HashMap[size];
        m_objectStack = new Object[INITIAL_STACK_SIZE];
        m_factory = ifact;
        if (ifact != null) {
            m_classIndexMap = ifact.getClassIndexMap();
        } else {
            m_classIndexMap = new StringIntHashMap();
        }
    }

    /**
     * Default constructor. This can be used for creating a context outside of
     * the generated code for special purposes.
     */
    public UnmarshallingContext() {
        this(0, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY,
            EMPTY_STRING_ARRAY, null);
    }

    /**
     * Build name with optional namespace. Just returns the appropriate
     * name format.
     *
     * @param ns namespace URI of name
     * @param name local name part of name
     * @return formatted name string
     */
    public static String buildNameString(String ns, String name) {
        if (ns == null || "".equals(ns)) {
            return "\"" + name + "\"";
        } else {
            return "\"{" + ns + "}" + name + "\"";
        }
    }

    /**
     * Build current element name, with optional namespace.
     *
     * @return formatted name string
     */
    public String currentNameString() {
        return buildNameString(m_reader.getNamespace(), m_reader.getName());
    }

    /**
     * Build current parse input position description.
     *
     * @return text description of current parse position
     */
    public String buildPositionString() {
        return m_reader.buildPositionString();
    }

    /**
     * Throw exception for expected element start tag not found.
     *
     * @param ns namespace URI of name
     * @param name local name part of name
     * @exception JiBXException always thrown
     */
    public void throwStartTagNameError(String ns, String name)
        throws JiBXException {
        throw new JiBXException("Expected " + buildNameString(ns, name) +
            " start tag, found " + currentNameString() + " start tag " +
            buildPositionString());
    }

    /**
     * Throw exception for expected element end tag not found.
     *
     * @param ns namespace URI of name
     * @param name local name part of name
     * @exception JiBXException always thrown
     */
    public void throwEndTagNameError(String ns, String name)
        throws JiBXException {
        throw new JiBXException("Expected " + buildNameString(ns, name) +
            " end tag, found " + currentNameString() + " end tag " +
            buildPositionString());
    }

    /**
     * Throw exception including a name and position information.
     *
     * @param msg leading message text
     * @param ns namespace URI of name
     * @param name local name part of name
     * @exception JiBXException always thrown
     */
    public void throwNameException(String msg, String ns, String name)
        throws JiBXException {
        throw new JiBXException(msg + buildNameString(ns, name) +
            buildPositionString());
    }

    /**
     * Advance to next parse item. This wraps the base parser call in order to
     * catch and handle exceptions.
     *
     * @exception JiBXException on any error (possibly wrapping other exception)
     */
    private void advance() throws JiBXException {
        m_reader.nextToken();
    }

    /**
     * Verify namespace. This is a simple utility method that allows multiple
     * representations for the empty namespace as a convenience for generated
     * code.
     *
     * @param ns namespace URI expected (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @return <code>true</code> if the current namespace matches that
     * expected, <code>false</code> if not
     */
    private boolean verifyNamespace(String ns) {
        if (ns == null || "".equals(ns)) {
            return m_reader.getNamespace().length() == 0;
        } else {
            return ns.equals(m_reader.getNamespace());
        }
    }

    /**
     * Get attribute value from parser.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute value text, or <code>null</code> if missing
     */
    private String getAttributeValue(String ns, String name) {
        return m_reader.getAttributeValue(ns, name);
    }

    /**
     * Set document to be parsed from stream. This call is not part of the
     * interface definition, but is supplied to allow direct control of the
     * namespace processing by the compiler. The option of disabling namespaces
     * should be considered experimental and may not be supported in the future.
     *
     * @param ins stream supplying document data
     * @param name document name (<code>null</code> if unknown)
     * @param enc document input encoding, or <code>null</code> if to be
     * determined by parser
     * @param nsa enable namespace processing for parser flag
     * @throws JiBXException if error creating parser
     */
    public void setDocument(InputStream ins, String name, String enc,
        boolean nsa) throws JiBXException {
        if (m_reader == null) {
            m_reader = s_readerFactory.createReader(ins, name, enc, nsa);
        } else {
            m_reader = s_readerFactory.recycleReader(m_reader, ins, name, enc);
        }
        reset();
    }

    /**
     * Set document to be parsed from stream.
     *
     * @param ins stream supplying document data
     * @param enc document input encoding, or <code>null</code> if to be
     * determined by parser
     * @throws JiBXException if error creating parser
     */
    public void setDocument(InputStream ins, String enc) throws JiBXException {
        setDocument(ins, null, enc, true);
    }

    /**
     * Set document to be parsed from reader. This call is not part of the
     * interface definition, but is supplied to allow direct control of the
     * namespace processing by the compiler. The option of disabling namespaces
     * should be considered experimental and may not be supported in the future.
     *
     * @param rdr reader supplying document data
     * @param name document name (<code>null</code> if unknown)
     * @param nsa enable namespace processing for parser flag
     * @throws JiBXException if error creating parser
     */
    public void setDocument(Reader rdr, String name, boolean nsa)
        throws JiBXException {
        if (m_reader == null) {
            m_reader = s_readerFactory.createReader(rdr, name, nsa);
        } else {
            m_reader = s_readerFactory.recycleReader(m_reader, rdr, name);
        }
        reset();
    }

    /**
     * Set document to be parsed from reader.
     *
     * @param rdr reader supplying document data
     * @throws JiBXException if error creating parser
     */
    public void setDocument(Reader rdr) throws JiBXException {
        setDocument(rdr, null, true);
    }

    /**
     * Set named document to be parsed from stream.
     *
     * @param ins stream supplying document data
     * @param name document name
     * @param enc document input encoding, or <code>null</code> if to be
     * determined by parser
     * @throws JiBXException if error creating parser
     */
    public void setDocument(InputStream ins, String name, String enc)
        throws JiBXException {
        setDocument(ins, name, enc, true);
    }

    /**
     * Set named document to be parsed from reader.
     *
     * @param rdr reader supplying document data
     * @param name document name
     * @throws JiBXException if error creating parser
     */
    public void setDocument(Reader rdr, String name) throws JiBXException {
        setDocument(rdr, name, true);
    }

    /**
     * Set input document parse source directly.
     *
     * @param rdr document parse event reader
     */
    public void setDocument(IXMLReader rdr) {
        m_reader = rdr;
    }

    /**
     * Initializes the context to use the same parser and document as another
     * unmarshalling context. This method is designed for use when an initial
     * context needs to create and invoke a secondary context in the course of
     * an unmarshalling operation.
     *
     * @param parent context supplying parser and document to be unmarshalled
     */
    public void setFromContext(UnmarshallingContext parent) {
        m_reader = parent.m_reader;
    }

    /**
     * Reset unmarshalling information. This releases all references to
     * unmarshalled objects and prepares the context for potential reuse.
     * It is automatically called when input is set.
     */
    public void reset() {
        for (int i = 0; i < m_idMaps.length; i++) {
            m_idMaps[i] = null;
        }
        for (int i = 0; i < m_transientUnmarshallerClasses.length; i++) {
            if (m_transientUnmarshallerClasses[i] != null) {
                m_transientUnmarshallerClasses[i] = null;
                m_namespaces[i+m_transientBase] = null;
                m_names[i+m_transientBase] = null;
            }
            m_unmarshallers[i] = null;
        }
        if (m_transientUnmarshalMap != null) {
            m_transientUnmarshalMap.clear();
        }
        m_idref = null;
        for (int i = 0; i < m_objectStack.length; i++) {
            m_objectStack[i] = null;
        }
        m_stackDepth = 0;
        m_userContext = null;
    }

    /**
     * Parse to start tag. Ignores character data seen prior to a start tag, but
     * throws exception if an end tag or the end of the document is seen before
     * a start tag. Leaves the parser positioned at the start tag.
     *
     * @return element name of start tag found
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public String toStart() throws JiBXException {
        if (m_reader.getEventType() == IXMLReader.START_TAG) {
            return m_reader.getName();
        }
        while (true) {
            m_reader.next();
            switch (m_reader.getEventType()) {

                case IXMLReader.START_TAG:
                    return m_reader.getName();

                case IXMLReader.END_TAG:
                    throw new JiBXException("Expected start tag, " +
                        "found end tag " + currentNameString() +
                        " " + buildPositionString());

                case IXMLReader.END_DOCUMENT:
                    throw new JiBXException("Expected start tag, " +
                        "found end of document " + buildPositionString());

            }
        }
    }

    /**
     * Parse to end tag. Ignores character data seen prior to an end tag, but
     * throws exception if a start tag or the end of the document is seen before
     * an end tag. Leaves the parser positioned at the end tag.
     *
     * @return element name of end tag found
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public String toEnd() throws JiBXException {
        if (m_reader.getEventType() == IXMLReader.END_TAG) {
            return m_reader.getName();
        }
        while (true) {
            m_reader.next();
            switch (m_reader.getEventType()) {

                case IXMLReader.START_TAG:
                    throw new JiBXException("Expected end tag, " +
                        "found start tag " + currentNameString() +
                        " " + buildPositionString());

                case IXMLReader.END_TAG:
                    return m_reader.getName();

                case IXMLReader.END_DOCUMENT:
                    throw new JiBXException("Expected end tag, " +
                        "found end of document " + buildPositionString());

            }
        }
    }

    /**
     * Parse to start or end tag. If not currently positioned at a start or end
     * tag this first advances the parse to the next start or end tag.
     *
     * @return parser event type for start tag or end tag
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public int toTag() throws JiBXException {
        int type = m_reader.getEventType();
        while (type != IXMLReader.START_TAG &&
            type != IXMLReader.END_TAG) {
            type = m_reader.next();
        }
        return m_reader.getEventType();
    }

    /**
     * Check if next tag is start of element. If not currently positioned at a
     * start or end tag this first advances the parse to the next start or end
     * tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name element name expected
     * @return <code>true</code> if at start of element with supplied name,
     * <code>false</code> if not
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public boolean isAt(String ns, String name) throws JiBXException {
        int type = m_reader.getEventType();
        while (type != IXMLReader.START_TAG &&
            type != IXMLReader.END_TAG) {
            type = m_reader.next();
        }
        return m_reader.getEventType() == IXMLReader.START_TAG &&
            m_reader.getName().equals(name) && verifyNamespace(ns);
    }

    /**
     * Check if attribute is present on current start tag. Throws an exception
     * if not currently positioned on a start tag.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return <code>true</code> if named attribute is present,
     * <code>false</code> if not
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public boolean hasAttribute(String ns, String name) throws JiBXException {
        if (m_reader.getEventType() == IXMLReader.START_TAG) {
            return getAttributeValue(ns, name) != null;
        } else {
            throw new JiBXException("Error parsing document " +
                buildPositionString());
        }
    }

    /**
     * Check if any of several attributes is present on current start tag.
     * Throws an exception if not currently positioned on a start tag.
     *
     * @param nss namespace URIs for expected attributes (each may be
     * <code>null</code> or the empty string for the empty namespace)
     * @param names attribute names expected
     * @return <code>true</code> if at least one of the named attributes is
     * present, <code>false</code> if not
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public boolean hasAnyAttribute(String[] nss, String[] names)
        throws JiBXException {
        if (m_reader.getEventType() == IXMLReader.START_TAG) {
            for (int i = 0; i < names.length; i++) {
                if (getAttributeValue(nss[i], names[i]) != null) {
                    return true;
                }
            }
            return false;
        } else {
            throw new JiBXException("Error parsing document " +
                buildPositionString());
        }
    }

    /**
     * Check that only allowed attributes are present on current start tag.
     * Throws an exception if not currently positioned on a start tag, or if
     * an attribute is present which is not in the list.
     *
     * @param nss namespace URIs for allowed attributes (each may be
     * <code>null</code> or the empty string for the empty namespace)
     * @param names alphabetical list of attribute names expected (duplicates
     * names are ordered by namespace URI)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void checkAllowedAttributes(String[] nss, String[] names)
        throws JiBXException {
        if (m_reader.getEventType() == IXMLReader.START_TAG) {
            int count = m_reader.getAttributeCount();
            loop: for (int i = 0; i < count; i++) {
                String name = m_reader.getAttributeName(i);
                String ns = m_reader.getAttributeNamespace(i);
                int base = 0;
                int limit = names.length - 1;
                while (base <= limit) {
                    int cur = (base + limit) >> 1;
                    int diff = name.compareTo(names[cur]);
                    if (diff == 0) {
                        String comp = nss[cur];
                        if (comp == null) {
                            diff = ns.compareTo("");
                        } else {
                            diff = ns.compareTo(comp);
                        }
                        if (diff == 0) {
                            continue loop;
                        }
                    }
                    if (diff < 0) {
                        limit = cur - 1;
                    } else if (diff > 0) {
                        base = cur + 1;
                    }
                }
                throwStartTagException("Illegal attribute " +
                    buildNameString(ns, name));
            }
        } else {
            throw new JiBXException("Error parsing document " +
                buildPositionString());
        }
    }

    /**
     * Internal parse to expected start tag. Ignores character data seen prior
     * to a start tag, but throws exception if an end tag or the end of the
     * document is seen before a start tag. Leaves the parser positioned at the
     * start tag.
     * 
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name element name expected
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    private void matchStart(String ns, String name) throws JiBXException {
        if (toTag() == IXMLReader.START_TAG) {
            if (!m_reader.getName().equals(name) || !verifyNamespace(ns)) {
                throwStartTagNameError(ns, name);
            }
        } else {
            throw new JiBXException("Expected " + buildNameString(ns, name)
                + " start tag, found " + currentNameString() + " end tag "
                + buildPositionString());
        }
    }

    /**
     * Parse to start of element. Ignores character data to next start or end
     * tag, but throws exception if an end tag is seen before a start tag, or if
     * the start tag seen does not match the expected name. Leaves the parse
     * positioned at the start tag.
     * 
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name element name expected
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void parseToStartTag(String ns, String name) throws JiBXException {
        matchStart(ns, name);
    }

    /**
     * Parse past start of element. Ignores character data to next start or end
     * tag, but throws exception if an end tag is seen before a start tag, or if
     * the start tag seen does not match the expected name. Leaves the parse
     * positioned following the start tag.
     * 
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name element name expected
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void parsePastStartTag(String ns, String name) throws JiBXException {
        matchStart(ns, name);
        advance();
    }

    /**
     * Parse past start of expected element. If not currently positioned at a
     * start or end tag this first advances the parser to the next tag. If the
     * expected start tag is found it is skipped and the parse is left
     * positioned following the start tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name element name expected
     * @return <code>true</code> if start tag found, <code>false</code> if not
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public boolean parseIfStartTag(String ns, String name)
        throws JiBXException {
        if (isAt(ns, name)) {
            advance();
            return true;
        } else {
            return false;
        }
    }

    /**
     * Parse past current end of element. Ignores character data to next start
     * or end tag, but throws exception if a start tag is seen before a end tag,
     * or if the end tag seen does not match the expected name. Leaves the parse
     * positioned following the end tag.
     * 
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name element name expected
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void parsePastCurrentEndTag(String ns, String name)
        throws JiBXException {
        
        // move parse to start or end tag
        int event = toTag();
        
        // check for match on expected end tag
        if (event == IXMLReader.END_TAG) {
            if (m_reader.getName().equals(name) && verifyNamespace(ns)) {
                advance();
            } else {
                throwEndTagNameError(ns, name);
            }
        } else {
            throw new JiBXException("Expected " + buildNameString(ns, name)
                + " end tag, found " + currentNameString() + " start tag "
                + buildPositionString());
        }
    }

    /**
     * Parse past end of element. If currently at a start tag parses past that
     * start tag, then ignores character data to next start or end tag, and
     * throws exception if a start tag is seen before a end tag, or if
     * the end tag seen does not match the expected name. Leaves the parse
     * positioned following the end tag.
     * 
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name element name expected
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void parsePastEndTag(String ns, String name) throws JiBXException {
        
        // most past current tag if start
        int event = m_reader.getEventType();
        if (event == IXMLReader.START_TAG) {
            advance();
        }
        
        // handle as current tag
        parsePastCurrentEndTag(ns, name);
    }

    /**
     * Check if next tag is a start tag. If not currently positioned at a
     * start or end tag this first advances the parse to the next start or
     * end tag.
     *
     * @return <code>true</code> if at start of element, <code>false</code> if
     * at end
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public boolean isStart() throws JiBXException {
        int type = m_reader.getEventType();
        while (type != IXMLReader.START_TAG &&
            type != IXMLReader.END_TAG && type != IXMLReader.END_DOCUMENT) {
            type = m_reader.next();
        }
        return m_reader.getEventType() == IXMLReader.START_TAG;
    }

    /**
     * Check if next tag is an end tag. If not currently positioned at a
     * start or end tag this first advances the parse to the next start or
     * end tag.
     *
     * @return <code>true</code> if at end of element, <code>false</code> if
     * at start
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public boolean isEnd() throws JiBXException {
        int type = m_reader.getEventType();
        while (type != IXMLReader.START_TAG &&
            type != IXMLReader.END_TAG) {
            type = m_reader.next();
        }
        return m_reader.getEventType() == IXMLReader.END_TAG;
    }
    
    /**
     * Accumulate text content. This skips past comments and processing
     * instructions, and consolidates text and entities to a single string. Any
     * unexpanded entity references found are treated as errors.
     *
     * @return consolidated text string (empty string if no text components)
     * @exception JiBXException on error in unmarshalling
     */
    public String accumulateText() throws JiBXException {
        String text = null;
        StringBuffer buff = null;
        loop: while (true) {
            switch (m_reader.getEventType()) {

                case IXMLReader.ENTITY_REF:
                    if (m_reader.getText() == null) {
                        throw new JiBXException
                            ("Unexpanded entity reference in text at " +
                            buildPositionString());
                    }
                    // fall through into text accumulation

                case IXMLReader.CDSECT:
                case IXMLReader.TEXT:
                    if (text == null) {
                        text = m_reader.getText();
                    } else {
                        if (buff == null) {
                            buff = new StringBuffer(text);
                        }
                        buff.append(m_reader.getText());
                    }
                    break;

                case IXMLReader.END_TAG:
                case IXMLReader.START_TAG:
                case IXMLReader.END_DOCUMENT:
                    break loop;

                default:
                    break;

            }
            m_reader.nextToken();
        }
        if (buff == null) {
            return (text == null) ? "" : text;
        } else {
            return buff.toString();
        }
    }

    /**
     * Parse required text content. Assumes the parse is already positioned at
     * the text content, so just returns the text.
     *
     * @return content text found
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public String parseContentText() throws JiBXException {
        return accumulateText();
    }

    /**
     * Parse past end of element, returning optional text content. Assumes
     * you've already parsed past the start tag of the element, so it just looks
     * for text content followed by the end tag, and returns with the parser
     * positioned after the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return content text from element
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public String parseContentText(String ns, String tag)
        throws JiBXException {
        String text = accumulateText();
        switch (m_reader.getEventType()) {

            case IXMLReader.END_TAG:
                if (m_reader.getName().equals(tag) &&
                    verifyNamespace(ns)) {
                    m_reader.nextToken();
                    return text;
                } else {
                    throwEndTagNameError(ns, tag);
                }

            case IXMLReader.START_TAG:
                throw new JiBXException("Expected " +
                    buildNameString(ns, tag) + " end tag, " +
                    "found " + currentNameString() + " start tag " +
                    buildPositionString());

            case IXMLReader.END_DOCUMENT:
                throw new JiBXException("Expected " +
                    buildNameString(ns, tag) + " end tag, " +
                    "found end of document " + buildPositionString());

        }
        return null;
    }

    /**
     * Parse past end of element, returning integer value of
     * content. Assumes you've already parsed past the start tag of the
     * element, so it just looks for text content followed by the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public int parseContentInt(String ns, String tag) throws JiBXException {
        String text = parseContentText(ns, tag);
        try {
            return Utility.parseInt(text);
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ns, tag, ex.getRootCause());
        }
    }

    /**
     * Parse entire element, returning text content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return content text from element
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public String parseElementText(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
        return parseContentText(ns, tag);
    }

    /**
     * Parse entire element, returning optional text content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default text if the element is not found.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default text value
     * @return content text from element
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public String parseElementText(String ns, String tag, String dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
            return parseContentText(ns, tag);
        } else {
            return dflt;
        }
    }

    /**
     * Get text value of attribute from current start tag.
     * Throws an exception if the attribute value is not found in the start
     * tag.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute value text
     * @throws JiBXException if attribute not present
     */
    public String attributeText(String ns, String name) throws JiBXException {
        String value = getAttributeValue(ns, name);
        if (value == null) {
            throw new JiBXException("Missing required attribute " +
                buildNameString(ns, name) + " " + buildPositionString());
        } else {
            return value;
        }
    }

    /**
     * Get text value of optional attribute from current start
     * tag. If the attribute is not present the supplied default value is
     * returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute value text
     */
    public String attributeText(String ns, String name, String dflt) {
        String value = getAttributeValue(ns, name);
        if (value == null) {
            return dflt;
        } else {
            return value;
        }
    }

    /**
     * Find the object corresponding to an ID. This method just handles the
     * lookup and checks the object type.
     *
     * @param id ID text
     * @param index expected reference type index
     * @return object corresponding to IDREF, or <code>null</code> if not
     * yet defined
     * @throws JiBXException on any error
     */
    public Object findID(String id, int index) throws JiBXException {
        Map map = m_idMaps[index];
        if (map != null) {
            Object obj = map.get(id);
            if (obj == null || obj instanceof BackFillHolder) {
                return null;
            } else if (m_idClasses == null ||
                m_idClasses[index].equals(obj.getClass().getName())) {
                return obj;
            } else {
                throwStartTagException
                    ("IDREF element content mapped to wrong type");
            }
        }
        return null;
    }

    /**
     * Find previously defined object corresponding to an ID. This does the
     * lookup and checks that the referenced object has been defined.
     *
     * @param id ID text
     * @param index expected reference type index
     * @return object corresponding to IDREF
     * @throws JiBXException on any error
     */
    public Object findDefinedID(String id, int index) throws JiBXException {
        Object obj = findID(id, index);
        if (obj == null) {
            throwStartTagException("ID " + id + " not defined");
        }
        return obj;
    }

    /**
     * Parse entire element, returning object (if defined yet) corresponding
     * to content interpreted as IDREF. Expects to find the element start tag,
     * text content, and end tag, in that order, and returns with the parser
     * positioned following the end tag. 
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag attribute name expected
     * @param index expected reference type index
     * @return object corresponding to IDREF, or <code>null</code> if not
     * yet defined
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public Object parseElementForwardIDREF(String ns, String tag, int index)
        throws JiBXException {
        parsePastStartTag(ns, tag);
        m_idref = parseContentText(ns, tag);
        return findID(m_idref, index);
    }

    /**
     * Get object (if defined yet) corresponding to IDREF attribute from
     * current start tag.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param index expected reference type index
     * @return object corresponding to IDREF, or <code>null</code> if not
     * yet defined
     * @throws JiBXException if attribute not present, or ID mapped to a
     * different type of object than expected
     */
    public Object attributeForwardIDREF(String ns, String name, int index)
        throws JiBXException {
        m_idref = attributeText(ns, name);
        return findID(m_idref, index);
    }

    /**
     * Parse entire element, returning previously defined object corresponding
     * to content interpreted as IDREF. Expects to find the element start tag,
     * text content, and end tag, in that order, and returns with the parser
     * positioned following the end tag. 
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag attribute name expected
     * @param index expected reference type index
     * @return object corresponding to IDREF
     * @throws JiBXException if attribute not present, ID not defined, or
     * mapped to a different type of object than expected
     */
    public Object parseElementExistingIDREF(String ns, String tag, int index)
        throws JiBXException {
        parsePastStartTag(ns, tag);
        m_idref = parseContentText(ns, tag);
        return findDefinedID(m_idref, index);
    }

    /**
     * Get previously defined object corresponding to IDREF attribute from
     * current start tag.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param index expected reference type index
     * @return object corresponding to IDREF
     * @throws JiBXException if attribute not present, ID not defined, or
     * mapped to a different type of object than expected
     */
    public Object attributeExistingIDREF(String ns, String name, int index)
        throws JiBXException {
        m_idref = attributeText(ns, name);
        return findDefinedID(m_idref, index);
    }

    /**
     * Get integer value of attribute from current start tag.
     * Throws an exception if the attribute is not found in the start
     * tag, or if it is not a valid integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute integer value
     * @throws JiBXException if attribute not present or not a valid integer
     * value
     */
    public int attributeInt(String ns, String name) throws JiBXException {
        String text = attributeText(ns, name);
        try {
            return Utility.parseInt(text);
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ns, name, ex.getRootCause());
        }
    }

    /**
     * Get integer value of optional attribute from current
     * start tag. If the attribute is not present the supplied default value
     * is returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute integer value
     * @throws JiBXException if attribute value is not a valid integer
     */
    public int attributeInt(String ns, String name, int dflt)
        throws JiBXException {
        String value = getAttributeValue(ns, name);
        if (value == null) {
            return dflt;
        } else {
            try {
                return Utility.parseInt(value);
            } catch (JiBXException ex) {
                throw new JiBXParseException(ex.getMessage() + ' ' +
                    buildPositionString(), value, ns, name, ex.getRootCause());
            }
        }
    }

    /**
     * Parse entire element, returning integer value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public int parseElementInt(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
        return parseContentInt(ns, tag);
    }

    /**
     * Parse entire optional element, returning integer value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if the element is missing or
     * has no content.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public int parseElementInt(String ns, String tag, int dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
            return parseContentInt(ns, tag);
        } else {
            return dflt;
        }
    }

    /**
     * Find required text value in enumeration. Looks up and returns the
     * enumeration value corresponding to the target text.
     *
     * @param target text to be found in enumeration
     * @param enums ordered array of texts included in enumeration
     * @param vals array of values to be returned for corresponding text match
     * positions (position returned directly if this is <code>null</code>)
     * @return enumeration value for target text
     * @throws JiBXException if target text not found in enumeration
     */
    public int convertEnum(String target, String[] enums, int[] vals)
        throws JiBXException {
        if (target == null) {
            throwStartTagException("Missing required enumeration value");
        }
        try {
            return Utility.enumValue(target, enums, vals);
        } catch (JiBXException ex) {
            throw new JiBXConstrainedParseException(ex.getMessage() + ' ' +
                buildPositionString(), target, enums);
        }
    }

    /**
     * Find optional text value in enumeration. Looks up and returns the
     * enumeration value corresponding to the target text, or the default
     * value if the text is <code>null</code>.
     *
     * @param target text to be found in enumeration (may be <code>null</code>)
     * @param enums ordered array of texts included in enumeration
     * @param vals array of values to be returned for corresponding text match
     * positions (position returned directly if this is <code>null</code>)
     * @param dflt default value returned if target text is <code>null</code>
     * @return enumeration value for target text
     * @throws JiBXException if target text not found in enumeration
     */
    public int convertEnum(String target, String[] enums, int[] vals, int dflt)
        throws JiBXException {
        if (target == null) {
            return dflt;
        }
        try {
            return Utility.enumValue(target, enums, vals);
        } catch (JiBXException ex) {
            throw new JiBXConstrainedParseException(ex.getMessage() + ' ' +
                buildPositionString(), target, enums);
        }
    }

    /**
     * Get enumeration attribute value from current start tag.
     * Throws an exception if the attribute value is not found in the start
     * tag or the text does not match a value defined in the enumeration table.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param enums ordered array of texts included in enumeration
     * @param vals array of values to be returned for corresponding text match
     * positions (position returned directly if this is <code>null</code>)
     * @return enumeration value for target text
     * @throws JiBXException if attribute not present or value not found in
     * enumeration list
     */
    public int attributeEnumeration(String ns, String name, String[] enums,
        int[] vals) throws JiBXException {
    	try {
    		return convertEnum(getAttributeValue(ns, name), enums, vals);
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get optional enumeration attribute value from current start tag.
     * Throws an exception if the attribute value is present but does not match
     * a value defined in the enumeration table.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param enums ordered array of texts included in enumeration
     * @param vals array of values to be returned for corresponding text match
     * positions (position returned directly if this is <code>null</code>)
     * @param dflt default value returned if attribute is not present
     * @return enumeration value for target text
     * @throws JiBXException if attribute not present or value not found in
     * enumeration list
     */
    public int attributeEnumeration(String ns, String name, String[] enums,
        int[] vals, int dflt) throws JiBXException {
    	try {
    		return convertEnum(getAttributeValue(ns, name), enums, vals, dflt);
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Parse past end of element, returning enumeration value of content.
     * Assumes you've already parsed past the start tag of the element, so it
     * just looks for text content followed by the end tag, and returns with the
     * parser positioned after the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param enums ordered array of texts included in enumeration
     * @param vals array of values to be returned for corresponding text match
     * positions (position returned directly if this is <code>null</code>)
     * @return enumeration value for element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public int parseContentEnumeration(String ns, String tag, String[] enums,
        int[] vals) throws JiBXException {
    	try {
    		return convertEnum(parseContentText(ns, tag), enums, vals);
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(tag);
    		throw e;
    	}
    }

    /**
     * Parse entire element, returning enumeration value of optional content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if no content is present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param enums ordered array of texts included in enumeration
     * @param vals array of values to be returned for corresponding text match
     * positions (position returned directly if this is <code>null</code>)
     * @param dflt default value
     * @return enumeration value for element text
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public int parseElementEnumeration(String ns, String tag, String[] enums,
        int[] vals, int dflt) throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
            String text = parseContentText(ns, tag);
            try {
            	return convertEnum(text, enums, vals, dflt);
            } catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}
        } else {
            return dflt;
        }
    }

    /**
     * Convert byte value with exception wrapper. This internal method is used
     * by all the byte unmarshalling calls. It adds position information to
     * any exceptions that occur.
     *
     * @param text text for value to be converted
     * @return converted byte value
     * @throws JiBXException if not a valid byte value
     */
    public byte convertByte(String text) throws JiBXException {
        try {
            return Utility.parseByte(text);
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ex.getRootCause());
        }
    }

    /**
     * Get byte value of attribute from current start tag. Throws an exception
     * if the attribute is not found in the start tag, or if it is not a valid
     * integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute byte value
     * @throws JiBXException if attribute not present or not a valid byte value
     */
    public byte attributeByte(String ns, String name) throws JiBXException {
    	try {
    		return convertByte(attributeText(ns, name));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get byte value of optional attribute from current start tag. If the
     * attribute is not present the supplied default value is returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute byte value
     * @throws JiBXException if attribute value is not a valid byte
     */
    public byte attributeByte(String ns, String name, byte dflt)
        throws JiBXException {
        String text = getAttributeValue(ns, name);
        if (text == null) {
            return dflt;
        } else {
        	try {
        		return convertByte(text);
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(name);
        		throw e;
        	}
        }
    }

    /**
     * Parse past end of element, returning byte value of content. Assumes
     * you've already parsed past the start tag of the element, so it just looks
     * for text content followed by the end tag, and returns with the parser
     * positioned after the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public byte parseContentByte(String ns, String tag) throws JiBXException {
    	try {
    		return convertByte(parseContentText(ns, tag));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(tag);
    		throw e;
    	}
    }

    /**
     * Parse entire element, returning byte value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public byte parseElementByte(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
       	return parseContentByte(ns, tag);
    }

    /**
     * Parse entire element, returning byte value of optional content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if no content is present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public byte parseElementByte(String ns, String tag, byte dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
        	try {
        		return convertByte(parseContentText(ns, tag));
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}        		
        } else {
            return dflt;
        }
    }

    /**
     * Convert short value with exception wrapper. This internal method is used
     * by all the short unmarshalling calls. It adds position information to
     * any exceptions that occur.
     *
     * @param text text for value to be converted
     * @return converted short value
     * @throws JiBXException if not a valid short value
     */
    public short convertShort(String text) throws JiBXException {
        try {
            return Utility.parseShort(text);
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ex.getRootCause());
        }
    }

    /**
     * Get short value of attribute from current start tag. Throws an exception
     * if the attribute is not found in the start tag, or if it is not a valid
     * integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute short value
     * @throws JiBXException if attribute not present or not a valid short value
     */
    public short attributeShort(String ns, String name) throws JiBXException {
    	try	{
    		return convertShort(attributeText(ns, name));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get short value of optional attribute from current start tag. If the
     * attribute is not present the supplied default value is returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute short value
     * @throws JiBXException if attribute value is not a valid short
     */
    public short attributeShort(String ns, String name, short dflt)
        throws JiBXException {
        String text = getAttributeValue(ns, name);
        if (text == null) {
            return dflt;
        } else {
        	try {
        		return convertShort(text);
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(name);
        		throw e;
        	}
        }
    }

    /**
     * Parse past end of element, returning short value of content. Assumes
     * you've already parsed past the start tag of the element, so it just looks
     * for text content followed by the end tag, and returns with the parser
     * positioned after the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public short parseContentShort(String ns, String tag) throws JiBXException {
    	try {
    		return convertShort(parseContentText(ns, tag));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(tag);
    		throw e;
    	}
    }

    /**
     * Parse entire element, returning short value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public short parseElementShort(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
        try {
        	return parseContentShort(ns, tag);
        } catch (JiBXParseException e) {
        	e.setNamespace(ns);
        	e.setTagName(tag);
        	throw e;
        }
    }

    /**
     * Parse entire element, returning short value of optional content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if no content is present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public short parseElementShort(String ns, String tag, short dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
        	try {
        		return convertShort(parseContentText(ns, tag));
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}
        } else {
            return dflt;
        }
    }
    
    /**
     * Convert char value with exception wrapper. This internal method is used
     * by all the char unmarshalling calls. It adds position information to
     * any exceptions that occur.
     *
     * @param text text for value to be converted
     * @return converted char value
     * @throws JiBXException if not a valid char value
     */
    public char convertChar(String text) throws JiBXException {
        try {
            return Utility.parseChar(text);
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ex.getRootCause());
        }
    }

    /**
     * Get char value of attribute from current start tag. Throws an exception
     * if the attribute is not found in the start tag, or if it is not a valid
     * integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute char value
     * @throws JiBXException if attribute not present or not a valid char value
     */
    public char attributeChar(String ns, String name) throws JiBXException {
    	try {
    		return convertChar(attributeText(ns, name));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get char value of optional attribute from current start tag. If the
     * attribute is not present the supplied default value is returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute char value
     * @throws JiBXException if attribute value is not a valid char
     */
    public char attributeChar(String ns, String name, char dflt)
        throws JiBXException {
        String text = getAttributeValue(ns, name);
        if (text == null) {
            return dflt;
        } else {
        	try {
        		return convertChar(text);
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(name);
        		throw e;
        	}
        }
    }

    /**
     * Parse past end of element, returning char value of content. Assumes
     * you've already parsed past the start tag of the element, so it just looks
     * for text content followed by the end tag, and returns with the parser
     * positioned after the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public char parseContentChar(String ns, String tag) throws JiBXException {
    	try {
    		return convertChar(parseContentText(ns, tag));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(tag);
    		throw e;
    	}
    }

    /**
     * Parse entire element, returning char value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public char parseElementChar(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
        try {
        	return parseContentChar(ns, tag);
        } catch (JiBXParseException e) {
        	e.setNamespace(ns);
        	e.setTagName(tag);
        	throw e;
        }
    }

    /**
     * Parse entire element, returning char value of optional content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if the element is not present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public char parseElementChar(String ns, String tag, char dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
        	try {
        		return convertChar(parseContentText(ns, tag));
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}
        } else {
            return dflt;
        }
    }

    /**
     * Convert long value with exception wrapper. This internal method is used
     * by all the long unmarshalling calls. It adds position information to
     * any exceptions that occur.
     *
     * @param text text for value to be converted
     * @return converted long value
     * @throws JiBXException if not a valid long value
     */
    public long convertLong(String text) throws JiBXException {
        try {
            return Utility.parseLong(text);
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ex.getRootCause());
        }
    }

    /**
     * Get long value of attribute from current start tag. Throws an exception
     * if the attribute is not found in the start tag, or if it is not a valid
     * integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute long value
     * @throws JiBXException if attribute not present or not a valid long value
     */
    public long attributeLong(String ns, String name) throws JiBXException {
    	try {
    		return convertLong(attributeText(ns, name));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get long value of optional attribute from current start tag. If the
     * attribute is not present the supplied default value is returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute long value
     * @throws JiBXException if attribute value is not a valid long
     */
    public long attributeLong(String ns, String name, long dflt)
        throws JiBXException {
        String text = getAttributeValue(ns, name);
        if (text == null) {
            return dflt;
        } else {
        	try {
        		return convertLong(text);
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(name);
        		throw e;
        	}
        }
    }

    /**
     * Parse past end of element, returning long value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public long parseElementLong(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
        try {
        	return convertLong(parseContentText(ns, tag));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(tag);
    		throw e;
    	}
    }

    /**
     * Parse entire element, returning long value of optional content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if the element is not present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public long parseElementLong(String ns, String tag, long dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
        	try {
        		return convertLong(parseContentText(ns, tag));
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}        		
        } else {
            return dflt;
        }
    }

    /**
     * Convert boolean value. This internal method is used by all the boolean
     * unmarshalling calls. It accepts "true" or "1" as equivalent, and "false"
     * or "0" as equivalent, and throws exceptions for anything else.
     *
     * @param text text for value to be converted
     * @return converted boolean value
     * @throws JiBXException if not a valid boolean value
     */
    public boolean convertBoolean(String text) throws JiBXException {
        if ("true".equalsIgnoreCase(text) || "1".equals(text)) {
            return true;
        } else if ("false".equalsIgnoreCase(text) || "0".equals(text)) {
            return false;
        }
        throw new JiBXParseException("Invalid boolean value " +
            buildPositionString(), text);
    }

    /**
     * Get boolean value of attribute from current start tag. Throws an
     * exception if the attribute is not found in the start tag, or if it is
     * not a valid integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute boolean value
     * @throws JiBXException if attribute not present or not a valid boolean
     * value
     */
    public boolean attributeBoolean(String ns, String name) 
        throws JiBXException {
    	try    	{
    		return convertBoolean(attributeText(ns, name));
    	} catch (JiBXParseException e)	{
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get boolean value of optional attribute from current start tag. If the
     * attribute is not present the supplied default value is returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute boolean value
     * @throws JiBXException if attribute value is not a valid boolean
     */
    public boolean attributeBoolean(String ns, String name, boolean dflt)
        throws JiBXException {
        String text = getAttributeValue(ns, name);
        if (text == null) {
            return dflt;
        } else {
        	try	{
        		return convertBoolean(text);
        	} catch (JiBXParseException e)	{
        		e.setNamespace(ns);
        		e.setTagName(name);
        		throw e;
        	}
        }
    }

    /**
     * Parse entire element, returning boolean value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public boolean parseElementBoolean(String ns, String tag) 
        throws JiBXException {
        parsePastStartTag(ns, tag);
        try	{
        	return convertBoolean(parseContentText(ns, tag));
        } catch (JiBXParseException e)	{
        	e.setNamespace(ns);
        	e.setTagName(tag);
        	throw e;
        }
    }

    /**
     * Parse entire element, returning boolean value of optional content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if the element is not present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public boolean parseElementBoolean(String ns, String tag, boolean dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
        	try	{
        		return convertBoolean(parseContentText(ns, tag));
        	} catch (JiBXParseException e)	{
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}
        } else {
            return dflt;
        }
    }

    /**
     * Convert float value with exception wrapper. This internal method is used
     * by all the float unmarshalling calls. It adds position information to
     * any exceptions that occur.
     *
     * @param text text for value to be converted
     * @return converted float value
     * @throws JiBXException if not a valid float value
     */
    public float convertFloat(String text) throws JiBXException {
        try {
            return Utility.parseFloat(text);
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ex.getRootCause());
        }
    }

    /**
     * Get float value of attribute from current start tag. Throws an exception
     * if the attribute is not found in the start tag, or if it is not a valid
     * integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute float value
     * @throws JiBXException if attribute not present or not a valid float value
     */
    public float attributeFloat(String ns, String name) throws JiBXException {
    	try {
    		return convertFloat(attributeText(ns, name));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get float value of optional attribute from current start tag. If the
     * attribute is not present the supplied default value is returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute float value
     * @throws JiBXException if attribute value is not a valid float
     */
    public float attributeFloat(String ns, String name, float dflt)
        throws JiBXException {
        String text = getAttributeValue(ns, name);
        if (text == null) {
            return dflt;
        } else {
        	try {
        		return convertFloat(text);
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(name);
        		throw e;
        	}
        }
    }

    /**
     * Parse past end of element, returning float value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public float parseElementFloat(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
        try {
        	return convertFloat(parseContentText(ns, tag));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(tag);
    		throw e;
    	}
    }

    /**
     * Parse entire element, returning float value of optional content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if the element is not present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public float parseElementFloat(String ns, String tag, float dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
        	try {
        		return convertFloat(parseContentText(ns, tag));
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}
        } else {
            return dflt;
        }
    }

    /**
     * Convert double value with exception wrapper. This internal method is used
     * by all the double unmarshalling calls. It adds position information to
     * any exceptions that occur.
     *
     * @param text text for value to be converted
     * @return converted double value
     * @throws JiBXException if not a valid double value
     */
    public double convertDouble(String text) throws JiBXException {
        try {
            return Utility.parseDouble(text);
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ex.getRootCause());
        }
    }

    /**
     * Get double value of attribute from current start tag. Throws an exception
     * if the attribute is not found in the start tag, or if it is not a valid
     * integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute double value
     * @throws JiBXException if attribute not present or not a valid double
     * value
     */
    public double attributeDouble(String ns, String name) throws JiBXException {
    	try {
    		return convertDouble(attributeText(ns, name));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get double value of optional attribute from current start tag. If the
     * attribute is not present the supplied default value is returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute double value
     * @throws JiBXException if attribute value is not a valid double
     */
    public double attributeDouble(String ns, String name, double dflt)
        throws JiBXException {
        String text = getAttributeValue(ns, name);
        if (text == null) {
            return dflt;
        } else {
        	try {
        		return convertDouble(text);
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(name);
        		throw e;
        	}
        }
    }

    /**
     * Parse past end of element, returning double value of content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public double parseElementDouble(String ns, String tag)
        throws JiBXException {
        parsePastStartTag(ns, tag);
        try {
        	return convertDouble(parseContentText(ns, tag));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(tag);
    		throw e;
    	}        	
    }

    /**
     * Parse entire element, returning double value of optional content.
     * Expects to find the element start tag, text content, and end tag,
     * in that order, and returns with the parser positioned following
     * the end tag. Returns the default value if the element is not present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public double parseElementDouble(String ns, String tag, double dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
        	try {
        		return convertDouble(parseContentText(ns, tag));
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}        		
        } else {
            return dflt;
        }
    }

    /**
     * Convert <code>java.util.Date</code> value with exception wrapper. This
     * internal method is used by all the Date unmarshalling calls. It adds
     * position information to any exceptions that occur.
     *
     * @param text text for value to be converted
     * @return converted Date value
     * @throws JiBXException if not a valid Date value
     */
    public Date convertDate(String text) throws JiBXException {
        try {
            return new Date(Utility.parseDateTime(text));
        } catch (JiBXException ex) {
            throw new JiBXParseException(ex.getMessage() + ' ' +
                buildPositionString(), text, ex.getRootCause());
        }
    }

    /**
     * Get <code>java.util.Date</code> value of attribute from current start
     * tag. Throws an exception if the attribute is not found in the start tag,
     * or if it is not a valid integer value.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @return attribute Date value
     * @throws JiBXException if attribute not present or not a valid Date
     * value
     */
    public Date attributeDate(String ns, String name) throws JiBXException {
    	try {
    		return convertDate(attributeText(ns, name));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(name);
    		throw e;
    	}
    }

    /**
     * Get <code>java.util.Date</code> value of optional attribute from current
     * start tag. If the attribute is not present the supplied default value is
     * returned instead.
     *
     * @param ns namespace URI for expected attribute (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name attribute name expected
     * @param dflt value to be returned if attribute is not present
     * @return attribute Date value
     * @throws JiBXException if attribute value is not a valid Date
     */
    public Date attributeDate(String ns, String name, Date dflt)
        throws JiBXException {
        String text = getAttributeValue(ns, name);
        if (text == null) {
            return dflt;
        } else {
        	try {
        		return convertDate(text);
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(name);
        		throw e;
        	}
        }
    }

    /**
     * Parse past end of element, returning <code>java.util.Date</code> value
     * of content. Expects to find the element start tag, text content,
     * and end tag, in that order, and returns with the parser positioned
     * following the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @return converted value from element text
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public Date parseElementDate(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
        try {
        	return convertDate(parseContentText(ns, tag));
    	} catch (JiBXParseException e) {
    		e.setNamespace(ns);
    		e.setTagName(tag);
    		throw e;
    	}
    }

    /**
     * Parse entire element, returning <code>java.util.Date</code> value of
     * optional content. Expects to find the element start tag, text content,
     * and end tag, in that order, and returns with the parser positioned
     * following the end tag. Returns the default value if the element is not
     * present.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @param dflt default value
     * @return content text from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public Date parseElementDate(String ns, String tag, Date dflt)
        throws JiBXException {
        if (parseIfStartTag(ns, tag)) {
        	try {
        		return convertDate(parseContentText(ns, tag));
        	} catch (JiBXParseException e) {
        		e.setNamespace(ns);
        		e.setTagName(tag);
        		throw e;
        	}
        } else {
            return dflt;
        }
    }

    /**
     * Register back fill item for undefined ID value. This adds a holder to
     * the mapping table if not already present, then adds the back fill item
     * to the holder.
     *
     * @param id target undefined ID value
     * @param index target reference type index
     * @param fill back fill item
     * @throws JiBXException if attribute not present, or ID already defined
     */
    public void registerBackFill(String id, int index, BackFillReference fill)
        throws JiBXException {
        Map map = m_idMaps[index];
        if (map == null) {
            m_idMaps[index] = map = new HashMap();
        }
        Object obj = map.get(id);
        if (obj == null) {
            String xclass = (m_idClasses == null) ? null : m_idClasses[index];
            BackFillHolder holder = new BackFillHolder(xclass);
            map.put(id, holder);
            holder.addBackFill(fill);
        } else if (obj instanceof BackFillHolder) {
            ((BackFillHolder)obj).addBackFill(fill);
        } else {
            throw new JiBXException
                ("Internal operation error (back fill error) " +
                buildPositionString());
        }
    }

    /**
     * Register back fill item for last parsed ID value. This adds a holder to
     * the mapping table if not already present, then adds the back fill item
     * to the holder. This form of call always applies to the last IDREF value
     * parsed (from either an element or an attribute).
     *
     * @param index target reference type index
     * @param fill back fill item
     * @throws JiBXException if attribute not present, or ID already defined
     */
    public void registerBackFill(int index, BackFillReference fill)
        throws JiBXException {
        registerBackFill(m_idref, index, fill);
    }

    /**
     * Define object for ID. Adds the owning object to a map with the ID
     * value as key. Throws an exception if the object class does not match
     * that expected from forward references, or if another object has
     * previously been registered with the same ID.
     *
     * @param id text ID value
     * @param index ID class index number
     * @param obj object corresponding to element
     * @throws JiBXException if duplicate ID or wrong class
     */
    public void defineID(String id, int index, Object obj)
        throws JiBXException {
        Map map = m_idMaps[index];
        if (map == null) {
            m_idMaps[index] = map = new HashMap();
        }
        Object prior = map.put(id, obj);
        if (prior instanceof BackFillHolder) {
            BackFillHolder holder = (BackFillHolder)prior;
            String xclass = holder.getExpectedClass();
            if (xclass == null || xclass.equals(obj.getClass().getName())) {
                holder.defineValue(obj);
            } else {
                throw new JiBXException("ID object has wrong type " +
                    buildPositionString());
            }
        } else if (prior != null) {
            throw new JiBXException("Duplicate ID definition " +
                buildPositionString());
        }
    }

    /**
     * Map unmarshalling for element. Adds the entry for a particular class
     * index to the unmarshalling map.
     *
     * @param index class index for unmarshalling definition to be added
     */
    protected void mapUnmarshalling(int index) {
        Integer add = new Integer(index);
        Object value = m_transientUnmarshalMap.get(m_names[index]);
        if (value instanceof Integer) {
            ArrayList list = new ArrayList();
            list.add(value);
            list.add(add);
            m_transientUnmarshalMap.put(m_names[index], list);
        } else if (value instanceof ArrayList) {
            ArrayList list = (ArrayList)value;
            list.add(add);
        } else {
            m_transientUnmarshalMap.put(m_names[index], add);
        }
    }

    /**
     * Define unmarshalling for element. Enables the unmarshalling definition
     * linking an element name (including namespace) with a handler.
     *
     * @param mapname mapping name associated with unmarshaller
     * @param ns namespace for element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name name for element
     * @param cname unmarshaller class name
     * @throws JiBXException if unknown mapping name
     */
    public void addUnmarshalling(String mapname, String ns, String name,
        String cname) throws JiBXException {
        int index = m_classIndexMap.get(mapname);
        if (index < 0) {
            throw new JiBXException("No unmarshal mapping defined for class " + mapname);
        }
        m_namespaces[index] = ns;
        m_names[index] = name;
        m_transientUnmarshallerClasses[index-m_transientBase] = cname;
        if (m_transientUnmarshalMap != null && name != null) {
            mapUnmarshalling(index);
        }
    }

    /**
     * Undefine unmarshalling for element. Disables the unmarshalling
     * definition for a particular mapping name.
     *
     * @param mapname mapping name associated with unmarshaller
     * @throws JiBXException if unknown mapping name
     */
    public void removeUnmarshalling(String mapname) throws JiBXException {
        int index = m_classIndexMap.get(mapname);
        if (index < 0) {
            throw new JiBXException("No unmarshal mapping defined for class " + mapname);
        }
        if (m_transientUnmarshalMap != null && m_names[index] != null) {
            Object value = m_transientUnmarshalMap.get(m_names[index]);
            if (value instanceof Integer) {
                m_transientUnmarshalMap.remove(m_names[index]);
            } else if (value instanceof ArrayList) {
                ArrayList list = (ArrayList)value;
                for (int i = 0; i < list.size(); i++) {
                    Integer item = (Integer)list.get(i);
                    if (item.intValue() == index) {
                        list.remove(i);
                    }
                }
            }
        }
        m_namespaces[index] = null;
        m_names[index] = null;
        m_unmarshallers[index] = null;
        m_transientUnmarshallerClasses[index-m_transientBase] = null;
    }

    /**
     * Find the unmarshaller for a particular class index in the current
     * context.
     *
     * @param index class index for unmarshalling definition
     * @return unmarshalling handler for class
     * @throws JiBXException if unable to create unmarshaller
     */
    private IUnmarshaller getUnmarshaller(int index) throws JiBXException {
        if (m_unmarshallers[index] == null) {
            
            // load the unmarshaller class and create an instance
            Class clas;
            String mname = m_factory.getUnmarshallerClasses()[index];
            if (mname != null) {
                
                // get global unmarshaller class through factory
                clas = m_factory.getUnmarshallerClass(index);
                
            } else {
                
                // load transient unmarshaller class directly
                mname = m_transientUnmarshallerClasses[index-m_transientBase];
                if (mname == null) {
                    throw new JiBXException("No unmarshaller defined for class " + m_factory.getMappedClasses()[index]);
                }
                clas = m_factory.loadClass(mname);
                
            }
            try {
                
                // make sure we have a class
                if (clas == null) {
                    throw new JiBXException("Unable to load unmarshaller class " + mname);
                }
                
                // create and cache an instance of the class
                IUnmarshaller m = (IUnmarshaller)clas.newInstance();
                m_unmarshallers[index] = m;
                
            } catch (JiBXException e) {
                throw e;
            } catch (Exception e) {
                throw new JiBXException("Unable to create unmarshaller of class " + mname + ":", e);
            }

        }
        return m_unmarshallers[index];
    }

    /**
     * Find the unmarshaller for a particular class in the current context.
     *
     * @param mapname unmarshaller mapping name (generally the class name to be
     * handled, or abstract mapping type name)
     * @return unmarshalling handler for class
     * @throws JiBXException if unable to create unmarshaller
     */
    public IUnmarshaller getUnmarshaller(String mapname) throws JiBXException {
        int index = m_classIndexMap.get(mapname);
        if (index >= 0) {
            return getUnmarshaller(index);
        } else {
            throw new JiBXException("No unmarshal mapping defined for class " + mapname);
        }
    }

    /**
     * Find the unmarshaller for a particular element name (including
     * namespace) in the current context.
     *
     * @param ns namespace for element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param name name for element
     * @return unmarshalling handler for element, or <code>null</code> if none
     * found
     * @throws JiBXException if unable to create unmarshaller
     */
    public IUnmarshaller getUnmarshaller(String ns, String name)
        throws JiBXException {
        
        // check if unmarshal name map needs to be initialized
        if (m_unmarshalMap == null) {
            
            // get cached map for globals and create map for transients
            m_unmarshalMap = m_factory.getUnmarshalMap();
            m_transientUnmarshalMap = new HashMap();
            for (int i = 0; i < m_names.length; i++) {
                if (m_transientUnmarshallerClasses[i-m_transientBase] != null &&
                    m_names[i] != null) {
                    mapUnmarshalling(i);
                }
            }
        }
        
        // first check for name match in transient unmarshal map
        Object value = m_transientUnmarshalMap.get(name);
        if (value != null) {
            int index = -1;
            if (value instanceof Integer) {
                int i = ((Integer)value).intValue();
                String mns = m_namespaces[i];
                if (ns == mns || (ns == null && mns.length() == 0) ||
                    (mns == null && ns.length() == 0) ||
                    (ns != null && ns.equals(mns))) {
                    index = i;
                }
            } else if (value instanceof List) {
                List list = (List)value;
                for (int i = 0; i < list.size(); i++) {
                    int j = ((Integer)list.get(i)).intValue();
                    String mns = m_namespaces[j];
                    if (ns == mns || (ns == null && mns.length() == 0) ||
                        (mns == null && ns.length() == 0) ||
                        (ns != null && ns.equals(mns))) {
                        index = j;
                        break;
                    }
                }
            }
            if (index >= 0) {
                return getUnmarshaller(index);
            }
        }
        
        // next check for match in global unmarshal map
        value = m_unmarshalMap.get(name);
        if (value != null) {
            int index = -1;
            if (value instanceof Integer) {
                int i = ((Integer)value).intValue();
                String mns = m_namespaces[i];
                if (ns == mns || (ns == null && mns.length() == 0) ||
                    (mns == null && ns.length() == 0) ||
                    (ns != null && ns.equals(mns))) {
                    index = i;
                }
            } else if (value instanceof int[]) {
                int[] ints = (int[])value;
                for (int i = 0; i < ints.length; i++) {
                    String mns = m_namespaces[ints[i]];
                    if (ns == mns || (ns == null && mns.length() == 0) ||
                        (mns == null && ns.length() == 0) ||
                        (ns != null && ns.equals(mns))) {
                        index = ints[i];
                        break;
                    }
                }
            }
            if (index >= 0) {
                return getUnmarshaller(index);
            }
        }
        return null;
    }

    /**
     * Unmarshal optional element. If not currently positioned at a start or
     * end tag this first advances the parse to the next start or end tag.
     *
     * @return unmarshalled object from element, or <code>null</code> if end tag
     * rather than start tag seen
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public Object unmarshalOptionalElement() throws JiBXException {
        int type = toTag();
        if (type == IXMLReader.START_TAG) {
            IUnmarshaller unmarshal =
                getUnmarshaller(m_reader.getNamespace(), m_reader.getName());
            if (unmarshal != null) {
                return unmarshal.unmarshal(null, this);
            }
        }
        return null;
    }

    /**
     * Unmarshal required element of specified type. If not currently positioned
     * at a start or end tag this first advances the parse to the next start or
     * end tag. The returned object will always be assignable to the specified
     * type.
     *
     * @param clas expected class of unmarshalled object
     * @return unmarshalled object from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public Object unmarshalElement(Class clas) throws JiBXException {
        String name = toStart();
        IUnmarshaller unmarshal =
            getUnmarshaller(m_reader.getNamespace(), name);
        if (unmarshal == null) {
            throw new JiBXException("No unmarshaller for element " +
                currentNameString() + " " + buildPositionString());
        } else {
            Object obj = unmarshal.unmarshal(null, this);
            if (!clas.isInstance(obj)) {
                throw new JiBXException("Element " + name +
                    " not compatible with expected type " + clas.getName() +
                    " " + buildPositionString());
            }
            return obj;
        }
    }

    /**
     * Unmarshal required element. If not currently positioned at a start or
     * end tag this first advances the parse to the next start or end tag.
     *
     * @return unmarshalled object from element
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public Object unmarshalElement() throws JiBXException {
        String name = toStart();
        IUnmarshaller unmarshal =
            getUnmarshaller(m_reader.getNamespace(), name);
        if (unmarshal == null) {
            throw new JiBXException("No unmarshaller for element " +
                currentNameString() + " " + buildPositionString());
        } else {
            return unmarshal.unmarshal(null, this);
        }
    }

    /**
     * Parse past element, ignoring all content. This may be used while
     * positioned either before or on the element start tag. It checks if
     * currently positioned at the element start tag, and if so advances to the
     * next parse event. Then looks for the next end tag, ignoring character
     * data and skipping child elements. Leaves the parse positioned following
     * the end tag.
     *
     * @param ns namespace URI for expected element (may be <code>null</code>
     * or the empty string for the empty namespace)
     * @param tag element name expected
     * @throws JiBXException on any error (possible wrapping other exception)
     */
    public void parsePastElement(String ns, String tag) throws JiBXException {
        parsePastStartTag(ns, tag);
        int depth = 0;
        while (true) {
            switch (m_reader.getEventType()) {

                case IXMLReader.END_TAG:
                    if (depth == 0) {
                        if (m_reader.getName().equals(tag) &&
                            verifyNamespace(ns)) {
                            m_reader.nextToken();
                            return;
                        } else {
                            throwEndTagNameError(ns, tag);
                        }
                    } else {
                        depth--;
                    }
                    break;

                case IXMLReader.START_TAG:
                    depth++;
                    break;
                
                default:
                    break;

            }
            m_reader.nextToken();
        }
    }

    /**
     * Returns current element name.
     *
     * @return local name part of name, or <code>null</code> if not at a start
     * or end tag
     * @throws JiBXException if error from parser
     */
    public String getElementName() throws JiBXException {
        int type = m_reader.getEventType();
        if (type == IXMLReader.START_TAG ||
            type == IXMLReader.END_TAG) {
            return m_reader.getName();
        } else {
            return null;
        }
    }

    /**
     * Returns current element namespace URI.
     *
     * @return namespace URI of name, or <code>null</code> if not at a start
     * or end tag
     * @throws JiBXException if error from parser
     */
    public String getElementNamespace() throws JiBXException {
        int type = m_reader.getEventType();
        if (type == IXMLReader.START_TAG ||
            type == IXMLReader.END_TAG) {
            return m_reader.getNamespace();
        } else {
            return null;
        }
    }

    /**
     * Throw exception with start tag and position information.
     *
     * @param msg exception message text
     * @exception JiBXException always thrown
     */
    public void throwStartTagException(String msg) throws JiBXException {
        throw new JiBXException(msg + " at tag " + currentNameString() +
            buildPositionString());
    }

    /**
     * Throw exception with start tag, position information, and nested
     * exception.
     *
     * @param msg exception message text
     * @param ex nested exception
     * @exception JiBXException always thrown
     */
    public void throwStartTagException(String msg, Exception ex)
        throws JiBXException {
        throw new JiBXException(msg + " at tag " + currentNameString() +
            buildPositionString(), ex);
    }

    /**
     * Throw exception with position information.
     *
     * @param msg exception message text
     * @exception JiBXException always thrown
     */
    public void throwException(String msg) throws JiBXException {
        throw new JiBXException(msg + " " + buildPositionString());
    }

    /**
     * Throw exception with position information and nested exception.
     *
     * @param msg exception message text
     * @param ex nested exception
     * @exception JiBXException always thrown
     */
    public void throwException(String msg, Exception ex) throws JiBXException {
        throw new JiBXException(msg + " " + buildPositionString(), ex);
    }

    /**
     * Unmarshal document from stream to object. The effect of this is the same
     * as if {@link #setDocument} were called, followed by {@link
     * #unmarshalElement}
     *
     * @param ins stream supplying document data
     * @param enc document input encoding, or <code>null</code> if to be
     * determined by parser
     * @return unmarshalled object
     * @throws JiBXException if error creating parser
     */
    public Object unmarshalDocument(InputStream ins, String enc)
        throws JiBXException {
        setDocument(ins, enc);
        return unmarshalElement();
    }

    /**
     * Unmarshal document from reader to object. The effect of this is the same
     * as if {@link #setDocument} were called, followed by {@link
     * #unmarshalElement}
     *
     * @param rdr reader supplying document data
     * @return unmarshalled object
     * @throws JiBXException if error creating parser
     */
    public Object unmarshalDocument(Reader rdr) throws JiBXException {
        setDocument(rdr);
        return unmarshalElement();
    }

    /**
     * Unmarshal named document from stream to object. The effect of this is the
     * same as if {@link #setDocument} were called, followed by {@link
     * #unmarshalElement}
     *
     * @param ins stream supplying document data
     * @param name document name
     * @param enc document input encoding, or <code>null</code> if to be
     * determined by parser
     * @return unmarshalled object
     * @throws JiBXException if error creating parser
     */
    public Object unmarshalDocument(InputStream ins, String name, String enc)
        throws JiBXException {
        setDocument(ins, name, enc);
        return unmarshalElement();
    }

    /**
     * Unmarshal named document from reader to object. The effect of this is the
     * same as if {@link #setDocument} were called, followed by {@link
     * #unmarshalElement}
     *
     * @param rdr reader supplying document data
     * @param name document name
     * @return unmarshalled object
     * @throws JiBXException if error creating parser
     */
    public Object unmarshalDocument(Reader rdr, String name)
        throws JiBXException {
        setDocument(rdr, name);
        return unmarshalElement();
    }

    /**
     * Return the binding factory used to create this unmarshaller.
     *
     * @return binding factory
     */
    public IBindingFactory getFactory() {
        return m_factory;
    }

    /**
     * Return the supplied document name.
     *
     * @return supplied document name (<code>null</code> if none)
     */
    public String getDocumentName() {
        return m_reader.getDocumentName();
    }

    /**
     * Return the input encoding, if known. This is only valid after parsing of
     * a document has been started.
     *
     * @return input encoding (<code>null</code> if unknown)
     */
    public String getInputEncoding() {
        return m_reader.getInputEncoding();
    }
    
    /**
     * 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 <code>setDocument()</code> method, then this method, and finally the
     * {@link #unmarshalElement} method.
     * 
     * @param obj user context object, or <code>null</code> 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 <code>null</code> if no context object
     * set
     * @see #setUserContext(Object)
     */
    public Object getUserContext() {
        return m_userContext;
    }

    /**
     * Push created object to unmarshalling stack. This must be called before
     * beginning the unmarshalling of the object. It is only called for objects
     * with structure, not for those converted directly to and from text.
     *
     * @param obj object being unmarshalled
     */
    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++;
    }

    /**
     * Set position tracking information for object, if supported.
     *
     * @param obj object being tracked
     */
    public void trackObject(Object obj) {
        if (obj instanceof ITrackSourceImpl) {
            ((ITrackSourceImpl)obj).jibx_setSource(m_reader.getDocumentName(),
                m_reader.getLineNumber(), m_reader.getColumnNumber());
        }
    }

    /**
     * Push created object to unmarshalling stack with position tracking. If the
     * object supports setting source location information, the location is also
     * set by this method.
     *
     * @param obj object being unmarshalled
     */
    public void pushTrackedObject(Object obj) {
        pushObject(obj);
        trackObject(obj);
    }

    /**
     * Pop unmarshalled 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 unmarshalling object stack depth. This allows tracking
     * nested calls to unmarshal one object while in the process of
     * unmarshalling another object. The bottom item on the stack is always the
     * root object being unmarshalled.
     *
     * @return number of objects in unmarshalling stack
     */
    public int getStackDepth() {
        return m_stackDepth;
    }
    
    /**
     * Get object from unmarshalling stack. This stack allows tracking nested
     * calls to unmarshal one object while in the process of unmarshalling
     * another object. The bottom item on the stack is always the root object
     * being unmarshalled.
     *
     * @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 unmarshalling stack
     */
    public Object getStackObject(int depth) {
        if (depth >= 0 && depth < m_stackDepth) {
            return m_objectStack[m_stackDepth-depth-1];
        } else {
            throw new ArrayIndexOutOfBoundsException("Depth " + depth +
                " is out of range");
        }
    }
    
    /**
     * Get top object on unmarshalling stack. This is safe to call even when no
     * objects are on the stack.
     *
     * @return object from unmarshalling stack, or <code>null</code> if none
     */
    public Object getStackTop() {
        if (m_stackDepth > 0) {
            return m_objectStack[m_stackDepth-1];
        } else {
            return null;
        }
    }

    /**
     * Get count of active namespaces.
     *
     * @return number of active namespaces in stack
     */
    public int getActiveNamespaceCount() {
        try {
            return m_reader.getNamespaceCount(m_reader.getNestingDepth());
        } catch (IllegalArgumentException e) {
            throw new IllegalStateException("Internal error: " +
                e.getMessage());
        }
    }

    /**
     * Get URI for an active namespace.
     *
     * @param index index number of namespace to be returned
     * @return URI for namespace at position
     * @throws IllegalArgumentException if invalid index
     */
    public String getActiveNamespaceUri(int index) {
        return m_reader.getNamespaceUri(index);
    }

    /**
     * Get prefix for an active namespace.
     *
     * @param index stack position of namespace to be returned
     * @return prefix for namespace at position
     * @throws IllegalArgumentException if invalid index
     */
    public String getActiveNamespacePrefix(int index) {
        return m_reader.getNamespacePrefix(index);
    }
    
    /**
     * Skip past current element.
     *
     * @exception JiBXException on any error (possibly wrapping other exception)
     */
    public void skipElement() throws JiBXException {
        
        // check positioned at start tag
        if (!isEnd()) {
            
            // skip past the start tag
            next();
            
            // loop until end tag reached
            int depth = 1;
            while (depth > 0) {
                if (isEnd()) {
                    depth--;
                } else {
                    depth++;
                }
                next();
            }
            
        }
    }

    /**
     * Advance to next major parse event. This wraps the base parser call in
     * order to catch and handle exceptions, and to preserve a reasonable level
     * of parser independence.
     *
     * @return event type for next major parse event (START_TAG, TEXT, END_TAG,
     * or END_DOCUMENT)
     * @exception JiBXException on any error (possibly wrapping other exception)
     */
    public int next() throws JiBXException {
        return m_reader.next();
    }

    /**
     * Advance to next parse event. This wraps the base parser call in order to
     * catch and handle exceptions, and to preserve a reasonable level of parser
     * independence.
     *
     * @return event type for next parse event
     * @exception JiBXException on any error (possibly wrapping other exception)
     */
    public int nextToken() throws JiBXException {
        return m_reader.nextToken();
    }

    /**
     * Get the current parse event type. This wraps the base parser call in
     * order to catch and handle exceptions, and to preserve a reasonable level
     * of parser independence.
     *
     * @return event type for current parse event
     * @exception JiBXException on any error (possibly wrapping other exception)
     */
    public int currentEvent() throws JiBXException {
        return m_reader.getEventType();
    }

    /**
     * Get name associated with current parse event.
     *
     * @return name text for name associated with event (START_ELEMENT,
     * END_ELEMENT, or ENTITY_REF only)
     * @throws IllegalStateException if not at a start or end tag (optional)
     */
    public String getName() {
        return m_reader.getName();
    }

    /**
     * Get namespace associated with current parse event.
     *
     * @return URI for namespace associated with event (START_ELEMENT or
     * END_ELEMENT only), empty string if none
     * @throws IllegalStateException if not at a start or end tag (optional)
     */
    public String getNamespace() {
        return m_reader.getNamespace();
    }

    /**
     * Get namespace prefix associated with current parse event.
     *
     * @return prefix for namespace associated with event (START_ELEMENT or
     * END_ELEMENT only), <code>null</code> if none
     * @throws IllegalStateException if not at a start or end tag (optional)
     */
    public String getPrefix() {
        return m_reader.getPrefix();
    }

    /**
     * Get number of attributes for current START_ELEMENT event. The results are
     * undefined if called when not at a START_ELEMENT event.
     *
     * @return number of attributes, or <code>-1</code> if not at START_ELEMENT
     * @throws IllegalStateException if not at a start tag (optional)
     */
    public int getAttributeCount() {
        return m_reader.getAttributeCount();
    }

    /**
     * Get attribute name for current START_ELEMENT event. The results are
     * undefined if called when not at a START_ELEMENT event.
     *
     * @param index index number of attribute to be returned
     * @return name of attribute at position
     * @throws IllegalStateException if not at a start tag or invalid index
     */
    public String getAttributeName(int index) {
        return m_reader.getAttributeName(index);
    }

    /**
     * Get attribute namespace for current START_ELEMENT event. The results are
     * undefined if called when not at a START_ELEMENT event.
     *
     * @param index index number of attribute to be returned
     * @return namespace URI of attribute at position, empty string if none
     * @throws IllegalStateException if not at a start tag or invalid index
     */
    public String getAttributeNamespace(int index) {
        return m_reader.getAttributeNamespace(index);
    }

    /**
     * Get attribute namespace prefix for current START_ELEMENT event. The
     * results are undefined if called when not at a START_ELEMENT event.
     *
     * @param index index number of attribute to be returned
     * @return prefix for namespace of attribute at position, <code>null</code>
     * if none
     * @throws IllegalStateException if not at a start tag or invalid index
     */
    public String getAttributePrefix(int index) {
        return m_reader.getAttributePrefix(index);
    }

    /**
     * Get attribute value for current START_ELEMENT event. The results are
     * undefined if called when not at a START_ELEMENT event.
     *
     * @param index index number of attribute to be returned
     * @return value of attribute at position
     * @throws IllegalStateException if not at a start tag or invalid index
     */
    public String getAttributeValue(int index) {
        return m_reader.getAttributeValue(index);
    }

    /**
     * Get number of namespace declarations for current START_ELEMENT event. The
     * results are undefined if called when not at a START_ELEMENT event.
     *
     * @return number of namespace declarations, or <code>-1</code> if not at
     * START_ELEMENT
     */
    public int getNamespaceCount() {
		try {
            int level = m_reader.getNestingDepth();
            return m_reader.getNamespaceCount(level)-
                m_reader.getNamespaceCount(level-1);
        } catch (IllegalArgumentException e) {
            throw new IllegalStateException("Internal error: " +
                e.getMessage());
        }
    }

    /**
     * Get namespace URI for namespace declaration on current START_ELEMENT
     * event. The results are undefined if called when not at a START_ELEMENT
     * event.
     *
     * @param index index number of declaration to be returned
     * @return namespace URI for declaration at position
     * @throws IllegalArgumentException if invalid index
     */
    public String getNamespaceUri(int index) {
        int base = m_reader.getNamespaceCount(m_reader.getNestingDepth()-1);
		return m_reader.getNamespaceUri(base + index);
    }

    /**
     * Get namespace prefix for namespace declaration on current START_ELEMENT
     * event. The results are undefined if called when not at a START_ELEMENT
     * event.
     *
     * @param index index number of declaration to be returned
     * @return namespace prefix for declaration at position, 
     * @throws IllegalArgumentException if invalid index
     */
    public String getNamespacePrefix(int index) {
        int base = m_reader.getNamespaceCount(m_reader.getNestingDepth()-1);
        return m_reader.getNamespacePrefix(base + index);
    }

    /**
     * Get namespace URI matching prefix.
     *
     * @param prefix namespace prefix to be matched (<code>null</code> for
     * default namespace)
     * @return namespace URI for prefix
     */
    public String getNamespaceUri(String prefix) {
        return m_reader.getNamespace(prefix);
    }

    /**
     * Get text value for current event.
     *
     * @return text value for event
     */
    public String getText() {
        return m_reader.getText();
    }
}