IonWriteContext.java

/*
 * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at:
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */

package tools.jackson.dataformat.ion;

import tools.jackson.core.JsonGenerator;
import tools.jackson.core.TokenStreamContext;
import tools.jackson.core.exc.StreamWriteException;
import tools.jackson.core.json.DupDetector;

/**
 * Extension of TokenStreamContext that recognizes sexps
 * <p>
 * The TokenStreamContext is used by the pretty printer for handling of the whitespace between tokens,
 * and by the generator for verifying whether it's valid to write a given token. The writeStartSexp
 * method in the IonGenerator will enter a "sexp context", so we need a new state in the write
 * context to track that. Sexp handling is modeled after arrays.
 */
public class IonWriteContext extends TokenStreamContext
{
    // Both constants are in the tens instead of the ones to avoid conflict with the native
    // Jackson ones

    // Ion-specific contexts
    protected final static int TYPE_SEXP = 30;

    // Ion-specific statuses
    public final static int STATUS_OK_AFTER_SEXP_SEPARATOR = 60;

    /**
     * Parent context for this context; null for root context.
     */
    protected final IonWriteContext _parent;

    // // // Optional duplicate detection

    protected DupDetector _dups;

    /*
    /**********************************************************************
    /* Simple instance reuse slots; speed up things a bit (10-15%)
    /* for docs with lots of small arrays/objects
    /**********************************************************************
     */

    protected IonWriteContext _child;

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

    /**
     * Name of the Object property of which value is to be written; only
     * used for OBJECT contexts
     */
    protected String _currentName;

    protected Object _currentValue;

    protected boolean _gotPropertyId;

    /*
    /**********************************************************************
    /* Life-cycle
    /**********************************************************************
     */

    protected IonWriteContext(int type, IonWriteContext parent, int nestingDepth,
            DupDetector dups,
            Object currentValue)
    {
        super();
        _type = type;
        _parent = parent;
        _nestingDepth = nestingDepth;
        _dups = dups;
        _index = -1;
        _currentValue = currentValue;
    }

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

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

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

    public static IonWriteContext createRootContext(DupDetector dd) {
        return new IonWriteContext(TYPE_ROOT, null, 0, dd, null);
    }

    private IonWriteContext reset(int type, Object currentValue) {
        _type = type;
        // Due to way reuse works, "_parent" and "_nestingDepth" are fine already
        _index = -1;
        _currentName = null;
        _gotPropertyId = false;
        _currentValue = currentValue;
        if (_dups != null) { _dups.reset(); }
        return this;
    }

    public IonWriteContext createChildSexpContext(Object currentValue) {
        IonWriteContext ctxt = (IonWriteContext) _child;

        if(ctxt == null) {
            // same assignment as in createChildObjectContext, createChildArrayContext
            _child = ctxt = new IonWriteContext(TYPE_SEXP, this, _nestingDepth+1,
                    (_dups == null) ? null : _dups.child(), currentValue);
        }

        // reset returns this, OK to cast
        return ctxt.reset(TYPE_SEXP, currentValue);
    }

    // // Overrides

    // We have to override the two createChild*Context methods to return a IonWriteContext
    // instead of a JsonWriteContext so sexps can be arbitrarily embedded in ion. Otherwise we
    // would only be able to create them as top level values.
    // Two methods below are copied from JsonWriteContext

    public IonWriteContext createChildArrayContext(Object currentValue) {
        IonWriteContext ctxt = (IonWriteContext) _child;

        if (ctxt == null) {
            _child = ctxt = new IonWriteContext(TYPE_ARRAY, this, _nestingDepth+1,
                    (_dups == null) ? null : _dups.child(),
                            currentValue);
            return ctxt;
        }

        return (IonWriteContext) ctxt.reset(TYPE_ARRAY, currentValue);
    }

    public IonWriteContext createChildObjectContext(Object currentValue) {
        IonWriteContext ctxt = (IonWriteContext) _child;

        if (ctxt == null) {
            _child = ctxt = new IonWriteContext(TYPE_OBJECT, this, _nestingDepth+1,
                    (_dups == null) ? null : _dups.child(), currentValue);
            return ctxt;
        }
        return (IonWriteContext) ctxt.reset(TYPE_OBJECT, currentValue);
    }

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

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

    @Override public boolean hasCurrentName() { return _gotPropertyId; }

    public final boolean inSexp() {
        return _type == TYPE_SEXP;
    }

    /*
    /**********************************************************************
    /* State changing (copied verbatim from `SimpleStreamWriteContext`
    /**********************************************************************
     */

    public boolean writeName(String name) throws StreamWriteException {
        if ((_type != TYPE_OBJECT) || _gotPropertyId) {
            return false;
        }
        _gotPropertyId = true;
        _currentName = name;
        if (_dups != null) { _checkDup(_dups, name); }
        return true;
    }

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

    public boolean writeValue() {
        // Main limitation is with OBJECTs:
        if (_type == TYPE_OBJECT) {
            if (!_gotPropertyId) {
                return false;
            }
            _gotPropertyId = false;
        }
        // 17-Feb-2021, tatu: Not sure if this is needed (was in 2.12)
        /*
        else if (_type == TYPE_SEXP) {
            int ix = _index;
            ++_index;
            return (ix < 0) ? STATUS_OK_AS_IS : STATUS_OK_AFTER_SEXP_SEPARATOR;
        }
        */
        ++_index;
        return true;
    }

}