XmlReadContext.java

package tools.jackson.dataformat.xml.deser;

import java.util.Set;

import tools.jackson.core.*;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.io.CharTypes;
import tools.jackson.core.io.ContentReference;
import tools.jackson.core.json.DupDetector;

/**
 * Extension of {@link TokenStreamContext}, which implements
 * core methods needed, and adds small amount of additional
 * state data we need.
 *<p>
 * Almost same as standard <code>JsonReaderContext</code>, but
 * custom version needed to be able to keep track of names
 * of properties that need wrapping; this is needed to
 * support wrapped/unwrapped Collection/array values.
 */
public final class XmlReadContext
    extends TokenStreamContext
{
    // // // Configuration

    protected final XmlReadContext _parent;

    /**
     * Object used for checking for duplicate field names, if enabled
     * (null if not enabled)
     */
    protected final DupDetector _dups;

    // // // Location information (minus source reference)

    protected int _lineNr;
    protected int _columnNr;

    protected String _currentName;
    protected Object _currentValue;

    protected Set<String> _namesToWrap;

    /**
     * Name of property that requires wrapping
     */
    protected String _wrappedName;

    /*
    /**********************************************************************
    /* Simple instance reuse slots; speeds up things a bit (10-15%)
    /* for docs with lots of small arrays/objects (for which allocation
    /* was visible in profile stack frames)
    /**********************************************************************
     */

    protected XmlReadContext _child = null;

    /*
    /**********************************************************************
    /* Instance construction, reuse
    /**********************************************************************
     */

    public XmlReadContext(int type, XmlReadContext parent, DupDetector dups,
            int nestingDepth,
            int lineNr, int colNr)
    {
        super();
        _type = type;
        _parent = parent;
        _dups = dups;
        _nestingDepth = nestingDepth;
        _lineNr = lineNr;
        _columnNr = colNr;
        _index = -1;
    }

    @Deprecated // @since 3.0.2
    public XmlReadContext(int type, XmlReadContext parent, int nestingDepth,
            int lineNr, int colNr)
    {
        this(type, parent, null, nestingDepth, lineNr, colNr);
    }

    protected final void reset(int type, int lineNr, int colNr)
    {
        _type = type;
        _index = -1;
        _lineNr = lineNr;
        _columnNr = colNr;
        _currentName = null;
        _currentValue = null;
        _namesToWrap = null;
        // _nestingDepth is fine since reused instance at same nesting level
        if (_dups != null) {
            _dups.reset();
        }
    }

    @Override
    public Object currentValue() {
        return _currentValue;
    }

    @Override
    public void assignCurrentValue(Object v) {
        _currentValue = v;
    }

    /*
    /**********************************************************************
    /* Factory methods
    /**********************************************************************
     */

    public static XmlReadContext createRootContext(DupDetector dups, int lineNr, int colNr) {
        return new XmlReadContext(TYPE_ROOT, null, dups, 0, lineNr, colNr);
    }

    @Deprecated // @since 3.0.2
    public static XmlReadContext createRootContext(int lineNr, int colNr) {
        return createRootContext(null, lineNr, colNr);
    }

    public final XmlReadContext createChildArrayContext(int lineNr, int colNr)
    {
        ++_index; // not needed for Object, but does not hurt so no need to check curr type
        XmlReadContext ctxt = _child;
        if (ctxt == null) {
            _child = ctxt = new XmlReadContext(TYPE_ARRAY, this,
                    (_dups == null) ? null : _dups.child(),
                            _nestingDepth+1, lineNr, colNr);
            return ctxt;
        }
        ctxt.reset(TYPE_ARRAY, lineNr, colNr);
        return ctxt;
    }

    public final XmlReadContext createChildObjectContext(int lineNr, int colNr)
    {
        ++_index; // not needed for Object, but does not hurt so no need to check curr type
        XmlReadContext ctxt = _child;
        if (ctxt == null) {
            _child = ctxt = new XmlReadContext(TYPE_OBJECT, this,
                    (_dups == null) ? null : _dups.child(),
                            _nestingDepth+1, lineNr, colNr);
            return ctxt;
        }
        ctxt.reset(TYPE_OBJECT, lineNr, colNr);
        return ctxt;
    }

    /*
    /**********************************************************************
    /* Abstract method implementation
    /**********************************************************************
     */

    @Override
    public final String currentName() { return _currentName; }

    @Override public boolean hasCurrentName() { return _currentName != null; }

    @Override
    public final XmlReadContext getParent() { return _parent; }

    /**
     * @return Location pointing to the point where the context
     *   start marker was found
     */
    @Override
    public final TokenStreamLocation startLocation(ContentReference srcRef) {
        // We don't keep track of offsets at this level (only reader does)
        long totalChars = -1L;

        return new TokenStreamLocation(srcRef, totalChars, _lineNr, _columnNr);
    }

    /*
    /**********************************************************************
    /* Extended API
    /**********************************************************************
     */

    /**
     * Method called to mark start of new value, mostly to update `index`
     * for Array and Root contexts.
     */
    public final void valueStarted() {
        ++_index;
    }

    public void setCurrentName(String name) throws StreamReadException {
        _currentName = name;
        if (_dups != null) {
            _checkDup(_dups, name);
        }
    }

    private static void _checkDup(DupDetector dd, String name) throws StreamReadException
    {
        if (dd.isDup(name)) {
            Object src = dd.getSource();
            throw new StreamReadException(((src instanceof JsonParser) ? ((JsonParser) src) : null),
                    "Duplicate Object property \""+name+"\"");
        }
    }

    public void setNamesToWrap(Set<String> namesToWrap) {
        _namesToWrap = namesToWrap;
    }

    public boolean shouldWrap(String localName) {
        return (_namesToWrap != null) && _namesToWrap.contains(localName);
    }

    protected void convertToArray() {
        _type = TYPE_ARRAY;
    }

    /*
    /**********************************************************************
    /* Overridden standard methods
    /**********************************************************************
     */

    /**
     * Overridden to provide developer readable "JsonPath" representation
     * of the context.
     */
    @Override
    public final String toString()
    {
        StringBuilder sb = new StringBuilder(64);
        switch (_type) {
        case TYPE_ROOT:
            sb.append("/");
            break;
        case TYPE_ARRAY:
            sb.append('[');
            sb.append(getCurrentIndex());
            sb.append(']');
            break;
        case TYPE_OBJECT:
            sb.append('{');
            if (_currentName != null) {
                sb.append('"');
                CharTypes.appendQuoted(sb, _currentName);
                sb.append('"');
            } else {
                sb.append('?');
            }
            sb.append('}');
            break;
        }
        return sb.toString();
    }
}