IonWriterUser.java

/*
 * 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.ION_SYMBOL_TABLE;

import com.amazon.ion.IonCatalog;
import com.amazon.ion.IonException;
import com.amazon.ion.IonStruct;
import com.amazon.ion.IonType;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.SymbolToken;
import com.amazon.ion.Timestamp;
import com.amazon.ion.ValueFactory;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * <p>
 * This writer handles the symbol table processing and
 * provides default implementations for the list forms
 * of the write methods as often the list form is not
 * susceptible to optimization.
 * </p>
 *
 * <p>
 * This writer has a ({@link #_system_writer}) to which the actual data is
 * written, but data flows through the {@link #_current_writer} most of the
 * time.
 * </p>
 *
 * <p>
 * The critical responsibility here is the recognition of IVMs and local symbol
 * tables. When the user starts writing a local symtab, the stream is diverted
 * away from the {@link #_system_writer} into a temporary tree writer that
 * collects the symtab data into an {@link IonStruct} instance.  When that
 * struct is stepped-out, the diversion is stopped and the new
 * {@link SymbolTable} is installed.
 * </p>
 */
class IonWriterUser
    extends _Private_IonWriterBase
    implements _Private_IonWriter
{
    /** Factory for constructing the DOM of local symtabs. Not null. */
    private final ValueFactory _symtab_value_factory;

    /** Used to make correct local symbol tables. May be null. */
    private final IonCatalog _catalog;

    /**
     * The underlying system writer that writing the raw format (text, binary,
     * or ion values).  Not null.
     */
    final IonWriterSystem _system_writer;

    /**
     * This will be either our {@link #_system_writer} or a symbol table writer
     * depending on whether we're diverting the user values to a
     * local symbol table ... or not.
     * Not null.
     */
    IonWriterSystem _current_writer;

    /**
     * While the stream is diverted to collect local symtab data, it is
     * being written to this instance.
     * This is null IFF {@link #_current_writer} == {@link #_system_writer}.
     */
    private IonStruct _symbol_table_value;



    /**
     * Base constructor.
     * <p>
     * POSTCONDITION: {@link IonWriterUser#_system_writer} ==
     * {@link #_current_writer} == systemWriter
     *
     * @param catalog may be null.
     * @param symtabValueFactory must not be null.
     * @param systemWriter must not be null.
     */
    IonWriterUser(IonCatalog catalog,
                  ValueFactory symtabValueFactory,
                  IonWriterSystem systemWriter,
                  boolean requireSymbolValidation)
    {
        super(requireSymbolValidation);
        _symtab_value_factory = symtabValueFactory;
        _catalog = catalog;

        assert systemWriter != null;
        _system_writer = systemWriter;
        _current_writer = systemWriter;
    }


    /**
     * Constructor for text and binary writers.
     * <p>
     * POSTCONDITION: {@link IonWriterUser#_system_writer} ==
     * {@link #_current_writer} == systemWriter
     *
     * @param catalog
     *          may be null
     * @param symtabValueFactory
     *          must not be null
     * @param systemWriter
     *          must not be null
     * @param symtab
     *          must not be null
     */
    IonWriterUser(IonCatalog catalog,
                  ValueFactory symtabValueFactory,
                  IonWriterSystem systemWriter,
                  SymbolTable symtab,
                  boolean requireSymbolValidation)
    {
        this(catalog, symtabValueFactory, systemWriter, requireSymbolValidation);

        SymbolTable defaultSystemSymtab =
            systemWriter.getDefaultSystemSymtab();

        if (symtab.isLocalTable() || symtab != defaultSystemSymtab)
        {
            try {
                setSymbolTable(symtab);
            }
            catch (IOException e) {
                throw new IonException(e);
            }
        }

        assert _system_writer == _current_writer &&
               _system_writer == systemWriter;
    }

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

    public IonCatalog getCatalog()
    {
        return _catalog;
    }

    @Override
    int findAnnotation(String name) {
        return _current_writer.findAnnotation(name);
    }

    @Override
    public int getDepth()
    {
        return _current_writer.getDepth();
    }

    public boolean isInStruct()
    {
        return _current_writer.isInStruct();
    }


    public void flush() throws IOException
    {
        _current_writer.flush();
    }

    public void close() throws IOException
    {
        try
        {
            try
            {
                if (getDepth() == 0) {
                    assert(_current_writer == _system_writer);
                    finish();
                }
            }
            finally
            {
                _current_writer.close();
            }
        }
        finally
        {
            _system_writer.close();
        }
    }


    public final void finish() throws IOException
    {
        if (symbol_table_being_collected()) {
            throw new IllegalStateException(ERROR_FINISH_NOT_AT_TOP_LEVEL);
        }

        _system_writer.finish();
    }

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

    SymbolTable activeSystemSymbolTable()
    {
        return getSymbolTable().getSystemSymbolTable();
    }


    private boolean symbol_table_being_collected()
    {
        return (_current_writer != _system_writer);
    }

    /**
     * <p>
     * Diverts the data stream to a temporary tree writer which collects
     * local symtab data into an IonStruct from which we'll later construct a
     * {@link SymbolTable} instance.
     * </p>
     * <p>
     * Once the value image of the symbol table is complete (which
     * happens when the caller steps out of the containing struct)
     * the diverted stream is abandonded and the symbol table gets constructed.
     * </p>
     * <p>
     * If there was a makeSymbolTable(Reader) this copy might be,
     * at least partially, avoided.
     * </p>
     */
    private void open_local_symbol_table_copy()
    {
        assert(! symbol_table_being_collected());

        _symbol_table_value = _symtab_value_factory.newEmptyStruct();

        SymbolToken[] anns = _system_writer.getTypeAnnotationSymbols();
        _system_writer.clearAnnotations();

        _symbol_table_value.setTypeAnnotationSymbols(anns);

        _current_writer = new IonWriterSystemTree(activeSystemSymbolTable(),
                                                  _catalog,
                                                  _symbol_table_value,
                                                  null /* initialIvmHandling */);
    }

    /**
     * Closes the diverted writer since the local symbol table
     * is complete (i.e. the struct is closed, on {@link #stepOut()}).
     */
    private void close_local_symbol_table_copy() throws IOException
    {
        assert(symbol_table_being_collected());

        // convert the struct we just wrote with the TreeWriter to a
        // local symbol table
        LocalSymbolTableAsStruct.Factory lstFactory =
            (LocalSymbolTableAsStruct.Factory)((_Private_ValueFactory)_symtab_value_factory).getLstFactory();
        SymbolTable symtab = lstFactory.newLocalSymtab(_catalog, _symbol_table_value);

        _symbol_table_value = null;
        _current_writer     = _system_writer;

        // now make this symbol table the current symbol table
        this.setSymbolTable(symtab);
    }


    @Override
    public final void setSymbolTable(SymbolTable symbols)
        throws IOException
    {
        if (symbols == null ||
            _Private_Utils.symtabIsSharedNotSystem(symbols))
        {
            String message =
                "symbol table must be local or system to be set, or reset";
            throw new IllegalArgumentException(message);
        }

        if (getDepth() > 0)
        {
            String message =
                "the symbol table cannot be set, or reset, while a container " +
                "is open";
            throw new IllegalStateException(message);
        }

        if (symbols.isSystemTable())
        {
            writeIonVersionMarker(symbols);
        }
        else
        {
            _system_writer.writeLocalSymtab(symbols);
        }
    }


    public final SymbolTable getSymbolTable()
    {
        SymbolTable symbols = _system_writer.getSymbolTable();
        return symbols;
    }


    @Override
    final String assumeKnownSymbol(int sid)
    {
        return _system_writer.assumeKnownSymbol(sid);
    }

    //========================================================================
    // Field names


    public final void setFieldName(String name)
    {
        _current_writer.setFieldName(name);
    }

    public final void setFieldNameSymbol(SymbolToken name)
    {
        _current_writer.setFieldNameSymbol(name);
    }

    @Override
    public final boolean isFieldNameSet()
    {
        return _current_writer.isFieldNameSet();
    }


    //========================================================================
    // Annotations


    public void addTypeAnnotation(String annotation)
    {
        _current_writer.addTypeAnnotation(annotation);
    }

    public void setTypeAnnotations(String... annotations)
    {
        _current_writer.setTypeAnnotations(annotations);
    }

    public void setTypeAnnotationSymbols(SymbolToken... annotations)
    {
        _current_writer.setTypeAnnotationSymbols(annotations);
    }

    @Override
    String[] getTypeAnnotations()
    {
        return _current_writer.getTypeAnnotations();
    }

    @Override
    int[] getTypeAnnotationIds()
    {
        return _current_writer.getTypeAnnotationIds();
    }

    final SymbolToken[] getTypeAnnotationSymbols()
    {
        return _current_writer.getTypeAnnotationSymbols();
    }

    public void stepIn(IonType containerType) throws IOException
    {
        // see if it looks like we're starting a local symbol table
        if (containerType == IonType.STRUCT
            && _current_writer.getDepth() == 0
            && findAnnotation(ION_SYMBOL_TABLE) == 0)
        {
            open_local_symbol_table_copy();
        }
        else {
            // if not we'll just pass the work on to whatever
            // writer is currently in scope
            _current_writer.stepIn(containerType);
        }
    }

    public void stepOut() throws IOException
    {
        if (symbol_table_being_collected() && _current_writer.getDepth() == 1)
        {
            close_local_symbol_table_copy();
        }
        else {
            _current_writer.stepOut();
        }
    }

    public void writeBlob(byte[] value, int start, int len) throws IOException
    {
        _current_writer.writeBlob(value, start, len);
    }

    public void writeBool(boolean value) throws IOException
    {
        _current_writer.writeBool(value);
    }

    public void writeClob(byte[] value, int start, int len) throws IOException
    {
        _current_writer.writeClob(value, start, len);
    }

    @Override
    public void writeDecimal(BigDecimal value) throws IOException
    {
        _current_writer.writeDecimal(value);
    }

    public void writeFloat(double value) throws IOException
    {
        _current_writer.writeFloat(value);
    }

    @SuppressWarnings("cast")
    public void writeInt(int value) throws IOException
    {
        _current_writer.writeInt((long)value);
    }

    public void writeInt(long value) throws IOException
    {
        _current_writer.writeInt(value);
    }

    public void writeInt(BigInteger value) throws IOException
    {
        _current_writer.writeInt(value);
    }

    public void writeNull(IonType type) throws IOException
    {
        _current_writer.writeNull(type);
    }

    public void writeString(String value) throws IOException
    {
        _current_writer.writeString(value);
    }

    @Override
    final void writeSymbol(int symbolId) throws IOException
    {
        _current_writer.writeSymbol(symbolId);
    }

    public final void writeSymbol(String value) throws IOException
    {
        _current_writer.writeSymbol(value);
    }


    final void writeIonVersionMarker(SymbolTable systemSymtab)
        throws IOException
    {
        _current_writer.writeIonVersionMarker(systemSymtab);
    }

    @Override
    public final void writeIonVersionMarker()
        throws IOException
    {
        _current_writer.writeIonVersionMarker();
    }


    public void writeTimestamp(Timestamp value) throws IOException
    {
        _current_writer.writeTimestamp(value);
    }
}