All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazon.ion.impl.IonWriterSystemText Maven / Gradle / Ivy

There is a newer version: 1.11.9
Show newest version
/*
 * Copyright 2007-2019 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://www.apache.org/licenses/LICENSE-2.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 com.amazon.ion.impl;

import static com.amazon.ion.SystemSymbols.SYMBOLS;
import static com.amazon.ion.impl._Private_IonConstants.tidList;
import static com.amazon.ion.impl._Private_IonConstants.tidSexp;
import static com.amazon.ion.impl._Private_IonConstants.tidStruct;

import com.amazon.ion.IonException;
import com.amazon.ion.IonReader;
import com.amazon.ion.IonType;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.SymbolToken;
import com.amazon.ion.Timestamp;
import com.amazon.ion.system.IonTextWriterBuilder.LstMinimizing;
import com.amazon.ion.util.IonTextUtils;
import com.amazon.ion.util.IonTextUtils.SymbolVariant;
import com.amazon.ion.util._Private_FastAppendable;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;


class IonWriterSystemText
    extends IonWriterSystem
{
    /** Not null. */
    private final _Private_IonTextWriterBuilder _options;
    /** At least one. */
    private final int _long_string_threshold;

    private final _Private_IonTextAppender _output;

    /** Ensure we don't use a closed {@link #output} stream. */
    private boolean _closed;

    /**
     * True when the current container is a struct, so we write field names.
     */
    boolean     _in_struct;
    boolean     _pending_separator;

    /**
     * Indicates whether we're currently writing an IVM.
     */
    private boolean _is_writing_ivm;

    /**
     * True when the last data written was a triple-quoted string, meaning we
     * cannot write another long string lest it be incorrectly concatenated.
     */
    private boolean _following_long_string;

    CharSequence _separator_character;

    int         _top;
    int []      _stack_parent_type = new int[10];
    boolean[]   _stack_pending_comma = new boolean[10];


    /**
     * @throws NullPointerException if any parameter is null.
     */
    protected IonWriterSystemText(SymbolTable defaultSystemSymtab,
                                  _Private_IonTextWriterBuilder options,
                                  _Private_FastAppendable out)
    {
        super(defaultSystemSymtab,
              options.getInitialIvmHandling(),
              options.getIvmMinimizing());

        _output =
            _Private_IonTextAppender.forFastAppendable(out,
                                                       options.getCharset());
        _options = options;

        _separator_character = _options.topLevelSeparator();

        int threshold = _options.getLongStringThreshold();
        if (threshold < 1) threshold = Integer.MAX_VALUE;
        _long_string_threshold = threshold;
    }


    _Private_IonTextWriterBuilder getBuilder()
    {
        return _options;
    }

    @Override
    public int getDepth()
    {
        return _top;
    }
    public boolean isInStruct() {
        return _in_struct;
    }
    protected IonType getContainer()
    {
        IonType container;

        if (_top < 1) {
            container = IonType.DATAGRAM;
        }
        else {
            switch(_stack_parent_type[_top-1]) {
            case _Private_IonConstants.tidDATAGRAM:
                container = IonType.DATAGRAM;
                break;
            case _Private_IonConstants.tidSexp:
                container = IonType.SEXP;
                break;
            case _Private_IonConstants.tidList:
                container = IonType.LIST;
                break;
            case _Private_IonConstants.tidStruct:
                container = IonType.STRUCT;
                break;
            default:
                throw new IonException("unexpected container in parent stack: "+_stack_parent_type[_top-1]);
            }
        }
        return container;
    }
    void push(int typeid)
    {
        if (_top + 1 == _stack_parent_type.length) {
            growStack();
        }
        _stack_parent_type[_top] = typeid;
        _stack_pending_comma[_top] = _pending_separator;
        switch (typeid) {
        case _Private_IonConstants.tidSexp:
            _separator_character = " ";
            break;
        case _Private_IonConstants.tidList:
        case _Private_IonConstants.tidStruct:
            _separator_character = ",";
            break;
        default:
            _separator_character = _options.lineSeparator();
        break;
        }
        _top++;
    }
    void growStack() {
        int oldlen = _stack_parent_type.length;
        int newlen = oldlen * 2;
        int[] temp1 = new int[newlen];
        boolean[] temp3 = new boolean[newlen];

        System.arraycopy(_stack_parent_type, 0, temp1, 0, oldlen);
        System.arraycopy(_stack_pending_comma, 0, temp3, 0, oldlen);

        _stack_parent_type = temp1;
        _stack_pending_comma = temp3;
    }
    int pop() {
        _top--;
        int typeid = _stack_parent_type[_top];  // popped parent

        int parentid = (_top > 0) ? _stack_parent_type[_top - 1] : -1;
        switch (parentid) {
        case -1:
            _in_struct = false;
            _separator_character = _options.topLevelSeparator();
            break;
        case _Private_IonConstants.tidSexp:
            _in_struct = false;
            _separator_character = " ";
            break;
        case _Private_IonConstants.tidList:
            _in_struct = false;
            _separator_character = ",";
            break;
        case _Private_IonConstants.tidStruct:
            _in_struct = true;
            _separator_character = ",";
            break;
        default:
            _separator_character = _options.lineSeparator();
            break;
        }

        return typeid;
    }

    /**
     * @return a tid
     * @throws ArrayIndexOutOfBoundsException if _top < 1
     */
    int topType() {
        return _stack_parent_type[_top - 1];
    }

    boolean topPendingComma() {
        if (_top == 0) return false;
        return _stack_pending_comma[_top - 1];
    }

    private boolean containerIsSexp()
    {
        if (_top == 0) return false;
        int topType = topType();
        return (topType == tidSexp);
    }

    void printLeadingWhiteSpace() throws IOException {
        for (int ii=0; ii<_top; ii++) {
            _output.appendAscii(' ');
            _output.appendAscii(' ');
        }
    }
    void closeCollection(char closeChar) throws IOException {
       if (_options.isPrettyPrintOn()) {
           _output.appendAscii(_options.lineSeparator());
           printLeadingWhiteSpace();
       }
       _output.appendAscii(closeChar);
    }


    private void writeSidLiteral(int sid)
        throws IOException
    {
        assert sid >= 0;

        // No extra handling needed for JSON strings, this is already legal.

        boolean asString = _options._symbol_as_string;
        if (asString) _output.appendAscii('"');

        _output.appendAscii('$');
        _output.printInt(sid);

        if (asString) _output.appendAscii('"');
    }


    /**
     * @param value must not be null.
     */
    private void writeSymbolToken(String value) throws IOException
    {
        if (_options._symbol_as_string)
        {
            if (_options._string_as_json)
            {
                _output.printJsonString(value);
            }
            else
            {
                _output.printString(value);
            }
        }
        else
        {
            SymbolVariant variant = IonTextUtils.symbolVariant(value);
            switch (variant)
            {
                case IDENTIFIER:
                {
                    _output.appendAscii(value);
                    break;
                }
                case OPERATOR:
                {
                    if (containerIsSexp())
                    {
                        _output.appendAscii(value);
                        break;
                    }
                    // else fall through...
                }
                case QUOTED:
                {
                    _output.printQuotedSymbol(value);
                    break;
                }
            }
        }
    }

    void writeFieldNameToken(SymbolToken sym)
        throws IOException
    {
        String name = sym.getText();
        if (name == null) {
            int sid = sym.getSid();
            writeSidLiteral(sid);
        }
        else {
            writeSymbolToken(name);
        }
    }

    void writeAnnotations(SymbolToken[] annotations)
        throws IOException
    {
        for (SymbolToken ann : annotations) {
            writeAnnotationToken(ann);
            _output.appendAscii("::");
        }
    }

    void writeAnnotationToken(SymbolToken ann)
        throws IOException
    {
        String name = ann.getText();
        if (name == null) {
            _output.appendAscii('$');
            _output.appendAscii(Integer.toString(ann.getSid()));
        }
        else {
            _output.printSymbol(name);
        }
    }

    boolean writeSeparator(boolean followingLongString)
        throws IOException
    {
        if (_options.isPrettyPrintOn()) {
            if (_pending_separator && !IonTextUtils.isAllWhitespace(_separator_character)) {
                // Only bother if the separator is non-whitespace.
                _output.appendAscii(_separator_character);
                followingLongString = false;
            }
            _output.appendAscii(_options.lineSeparator());
            printLeadingWhiteSpace();
        }
        else if (_pending_separator) {
            _output.appendAscii(_separator_character);
            if (!IonTextUtils.isAllWhitespace(_separator_character)) followingLongString = false;
        }
        return followingLongString;
    }

    @Override
    void startValue() throws IOException
    {
        super.startValue();

        boolean followingLongString = _following_long_string;

        followingLongString = writeSeparator(followingLongString);

        // write field name
        if (_in_struct) {
            SymbolToken sym = assumeFieldNameSymbol();
            writeFieldNameToken(sym);
            _output.appendAscii(':');
            clearFieldName();
            followingLongString = false;
        }

        // write annotations only if they exist and we're not currently
        // writing an IVM
        if (hasAnnotations() && !_is_writing_ivm) {
            if (! _options._skip_annotations) {
                SymbolToken[] annotations = getTypeAnnotationSymbols();
                writeAnnotations(annotations);
                followingLongString = false;
            }
            clearAnnotations();
        }

        _following_long_string = followingLongString;
    }

    void closeValue()
        throws IOException
    {
        super.endValue();
        _pending_separator = true;
        _following_long_string = false;  // Caller overwrites this as needed.

        // Flush if a top-level-value was written
        if (getDepth() == 0)
        {
            try
            {
                flush();
            }
            catch (IOException e)
            {
                throw new IonException(e);
            }
        }
    }

    @Override
    void writeIonVersionMarkerAsIs(SymbolTable systemSymtab)
        throws IOException
    {
        _is_writing_ivm = true;
        writeSymbolAsIs(systemSymtab.getIonVersionId());
        _is_writing_ivm = false;
    }

    @Override
    void writeLocalSymtab(SymbolTable symtab)
        throws IOException
    {
        SymbolTable[] imports = symtab.getImportedTables();

        LstMinimizing min = _options.getLstMinimizing();
        if (min == null)
        {
            symtab.writeTo(this);
        }
        else if (min == LstMinimizing.LOCALS && imports.length > 0)
        {
            // Copy the symtab, but filter out local symbols.

            IonReader reader = new SymbolTableReader(symtab);

            // move onto and write the struct header
            IonType t = reader.next();
            assert(IonType.STRUCT.equals(t));
            SymbolToken[] a = reader.getTypeAnnotationSymbols();
            // you (should) always have the $ion_symbol_table annotation
            assert(a != null && a.length >= 1);

            // now we'll start a local symbol table struct
            // in the underlying system writer
            setTypeAnnotationSymbols(a);
            stepIn(IonType.STRUCT);

            // step into the symbol table struct and
            // write the values - EXCEPT the symbols field
            reader.stepIn();
            for (;;) {
                t = reader.next();
                if (t == null) break;
                // get the field name and skip over 'symbols'
                String name = reader.getFieldName();
                if (SYMBOLS.equals(name)) {
                    continue;
                }
                writeValue(reader);
            }

            // we're done step out and move along
            stepOut();
        }
        else  // Collapse to IVM
        {
            SymbolTable systemSymtab = symtab.getSystemSymbolTable();
            writeIonVersionMarker(systemSymtab);
        }

        super.writeLocalSymtab(symtab);
    }

    public void stepIn(IonType containerType) throws IOException
    {
        startValue();

        int tid;
        char opener;
        switch (containerType)
        {
            case SEXP:
                if (!_options._sexp_as_list) {
                    tid = tidSexp;
                    _in_struct = false;
                    opener = '('; break;
                }
                // else fall through and act just like list
            case LIST:
                tid = tidList;
                _in_struct = false;
                opener = '[';
                break;
            case STRUCT:
                tid = tidStruct;
                _in_struct = true;
                opener = '{';
                break;
            default:
                throw new IllegalArgumentException();
        }

        push(tid);
        _output.appendAscii(opener);
        _pending_separator = false;
        _following_long_string = false;
    }

    public void stepOut() throws IOException
    {
        if (_top < 1) {
            throw new IllegalStateException(IonMessages.CANNOT_STEP_OUT);
        }
        _pending_separator = topPendingComma();
        int tid = pop();

        char closer;
        switch (tid)
        {
            case tidList:   closer = ']'; break;
            case tidSexp:   closer = ')'; break;
            case tidStruct: closer = '}'; break;
            default:
                throw new IllegalStateException();
        }
        closeCollection(closer);
        closeValue();
    }


    //========================================================================


    @Override
    public void writeNull()
        throws IOException
    {
        startValue();
        _output.appendAscii("null");
        closeValue();
    }

    public void writeNull(IonType type) throws IOException
    {
        startValue();

        String nullimage;

        if (_options._untyped_nulls)
        {
            nullimage = "null";
        }
        else
        {
            switch (type) {
                case NULL:      nullimage = "null";           break;
                case BOOL:      nullimage = "null.bool";      break;
                case INT:       nullimage = "null.int";       break;
                case FLOAT:     nullimage = "null.float";     break;
                case DECIMAL:   nullimage = "null.decimal";   break;
                case TIMESTAMP: nullimage = "null.timestamp"; break;
                case SYMBOL:    nullimage = "null.symbol";    break;
                case STRING:    nullimage = "null.string";    break;
                case BLOB:      nullimage = "null.blob";      break;
                case CLOB:      nullimage = "null.clob";      break;
                case SEXP:      nullimage = "null.sexp";      break;
                case LIST:      nullimage = "null.list";      break;
                case STRUCT:    nullimage = "null.struct";    break;

                default: throw new IllegalStateException("unexpected type " + type);
            }
        }

        _output.appendAscii(nullimage);
        closeValue();
    }

    public void writeBool(boolean value)
        throws IOException
    {
        startValue();
        _output.appendAscii(value ? "true" : "false");
        closeValue();
    }


    public void writeInt(long value)
        throws IOException
    {
        startValue();
        _output.printInt(value);
        closeValue();
    }

    public void writeInt(BigInteger value) throws IOException
    {
        if (value == null) {
            writeNull(IonType.INT);
            return;
        }

        startValue();
        _output.printInt(value);
        closeValue();
    }

    public void writeFloat(double value)
        throws IOException
    {
        startValue();
        _output.printFloat(_options, value);
        closeValue();
    }


    @Override
    public void writeDecimal(BigDecimal value)
        throws IOException
    {
        if (value == null) {
            writeNull(IonType.DECIMAL);
            return;
        }

        startValue();
        _output.printDecimal(_options, value);
        closeValue();
    }

    public void writeTimestamp(Timestamp value) throws IOException
    {
        if (value == null) {
            writeNull(IonType.TIMESTAMP);
            return;
        }

        startValue();

        if (_options._timestamp_as_millis)
        {
            long millis = value.getMillis();
            _output.appendAscii(Long.toString(millis));
        }
        else if (_options._timestamp_as_string)
        {
            // Timestamp is ASCII-safe so this is easy
            _output.appendAscii('"');
            _output.appendAscii(value.toString());
            _output.appendAscii('"');
        }
        else
        {
            _output.appendAscii(value.toString());
        }

        closeValue();
    }

    public void writeString(String value)
        throws IOException
    {
        startValue();
        if (value != null
            && ! _following_long_string
            && _long_string_threshold < value.length())
        {
            // TODO amzn/ion-java/issues/57 This can lead to mixed newlines in the output.
            // It assumes NL line separators, but _options could use CR+NL
            _output.printLongString(value);

            // This sets _following_long_string = false so we must overwrite
            closeValue();
            _following_long_string = true;
        }
        else
        {
            if (_options._string_as_json)
            {
                _output.printJsonString(value);
            }
            else
            {
                _output.printString(value);
            }
            closeValue();
        }
    }


    @Override
    void writeSymbolAsIs(int symbolId)
        throws IOException
    {
        SymbolTable symtab = getSymbolTable();
        String text = symtab.findKnownSymbol(symbolId);
        if (text != null)
        {
            writeSymbolAsIs(text);
        }
        else
        {
            startValue();
            writeSidLiteral(symbolId);
            closeValue();
        }
    }

    @Override
    public void writeSymbolAsIs(String value)
        throws IOException
    {
        if (value == null)
        {
            writeNull(IonType.SYMBOL);
            return;
        }

        startValue();
        writeSymbolToken(value);
        closeValue();
    }

    public void writeBlob(byte[] value, int start, int len)
        throws IOException
    {
        if (value == null)
        {
            writeNull(IonType.BLOB);
            return;
        }

        startValue();
        _output.printBlob(_options, value, start, len);
        closeValue();
    }

    public void writeClob(byte[] value, int start, int len)
        throws IOException
    {
        if (value == null)
        {
            writeNull(IonType.CLOB);
            return;
        }

        startValue();
        _output.printClob(_options, value, start, len);
        closeValue();
    }


    /**
     * {@inheritDoc}
     * 

* The {@link OutputStream} spec is mum regarding the behavior of flush on * a closed stream, so we shouldn't assume that our stream can handle that. */ public void flush() throws IOException { if (! _closed) { _output.flush(); } } public void close() throws IOException { if (! _closed) { try { if (getDepth() == 0) { finish(); } } finally { // Do this first so we are closed even if the call below throws. _closed = true; _output.close(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy