com.amazon.ion.impl._Private_IonWriterBase Maven / Gradle / Ivy
Show all versions of ion-java Show documentation
/*
* 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 com.amazon.ion.IonReader;
import com.amazon.ion.IonType;
import com.amazon.ion.IonValue;
import com.amazon.ion.IonWriter;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.SymbolToken;
import com.amazon.ion.Timestamp;
import com.amazon.ion.UnknownSymbolException;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
/**
* NOT FOR APPLICATION USE!
*
* Base type for Ion writers. This handles the writeIonEvents and provides default
* handlers for the list forms of write. This also resolves symbols if a symbol
* table is available (which it will not be if the underlying writer is a system
* writer).
*/
public abstract class _Private_IonWriterBase
implements IonWriter, _Private_ReaderWriter
{
protected static final String ERROR_MISSING_FIELD_NAME =
"IonWriter.setFieldName() must be called before writing a value into a struct.";
static final String ERROR_FINISH_NOT_AT_TOP_LEVEL =
"IonWriter.finish() can only be called at top-level.";
private final boolean requireSymbolValidation;
/**
* @param requireSymbolValidation true if SID validation should be performed; otherwise, false.
* See {@link _Private_IonTextWriterBuilder#withInvalidSidsAllowed(boolean)}
*/
public _Private_IonWriterBase(boolean requireSymbolValidation) {
this.requireSymbolValidation = requireSymbolValidation;
}
/**
* Returns the current depth of containers the writer is at. This is
* 0 if the writer is at top-level.
* @return int depth of container nesting
*/
protected abstract int getDepth();
//========================================================================
// Context management
/**
* Write an Ion version marker symbol to the output. This
* is the $ion_1_0 value currently (in later versions the
* number may change). In text output this appears as the
* text symbol. In binary this will be the symbol id if
* the writer is in a list, sexp or struct. If the writer
* is currently at the top level this will write the
* "magic cookie" value.
*
* Writing a version marker will reset the symbol table
* to be the system symbol table.
*/
abstract void writeIonVersionMarker() throws IOException;
/**
* Sets the symbol table to use for encoding to be the passed
* in symbol table. The can only be done between top-level values.
* As symbols are written
* this symbol table is used to resolve them. If the symbols
* are undefined this symbol table is updated to include them
* as local symbols. The updated symbol table will be
* written before any of the local values are emitted.
*
* If the symbol table is the system symbol table an Ion
* version marker will be written to the output. If symbols
* not in the system symbol table are written a local
* symbol table will be created and written before the
* current top level value.
*
* @param symbols base symbol table for encoding. Must not be null.
* @throws IllegalArgumentException if symbols is null or a shared symbol
* table, or if this writer isn't at top level.
*/
public abstract void setSymbolTable(SymbolTable symbols)
throws IOException;
/**
* Translates a symbol using the current symtab.
*
* @return not null.
*
* @throws UnknownSymbolException if the text is unknown.
*
* @see SymbolTable#findKnownSymbol(int)
*/
abstract String assumeKnownSymbol(int sid);
//========================================================================
// Field names
/**
* Returns true if the field name has been set either through setFieldName or
* setFieldId. This is generally more efficient than calling getFieldName or
* getFieldId and checking the return type as it does not need to resolve the
* name through a symbol table. This returns false if the field name has not
* been set.
* @return true if a field name has been set false otherwise
*/
public abstract boolean isFieldNameSet();
//========================================================================
// Annotations
/**
* Returns the given annotation's index in the value's annotations list, or -1 if not present.
* @param name the annotation to find.
* @return the index or -1.
*/
abstract int findAnnotation(String name);
/**
* Gets the current list of pending annotations.
* This is the contents of the current {@code annotations} array
* of this writer.
*
* If the annotations were set as IDs they
* will be converted if a symbol table is available. In the event
* a symbol table is not available a null array will be returned.
* If no annotations are set a 0 length array will be returned.
*
* @return pending type annotations as strings, null if the
* annotations cannot be expressed as strings.
*/
abstract String[] getTypeAnnotations();
/**
* Gets the current list of pending annotations.
* This is the contents of the current {@code annotations} array
* of this writer.
*
* If the annotations were set as string they
* will be converted to symbol IDs (ints) if a symbol table is
* available. In the event a symbol table is not available a
* null array will be returned.
* If no annotations are set a 0 length array will be returned.
*
* @return pending type annotations as symbol ID ints, null if the
* annotations cannot be expressed as IDs.
*/
abstract int[] getTypeAnnotationIds();
/**
* Write symbolId out as an IonSymbol value. The value must
* be valid in the symbol table.
*
* @param symbolId symbol table id to write
*/
abstract void writeSymbol(int symbolId) throws IOException;
//========================================================================
//
// default overload implementations. These generally
// convert the users value to a value of the intrinsic
// underlying type and then write that type using
// the concrete writers method.
//
public void writeBlob(byte[] value) throws IOException
{
if (value == null) {
this.writeNull(IonType.BLOB);
}
else {
this.writeBlob(value, 0, value.length);
}
return;
}
public void writeClob(byte[] value) throws IOException
{
if (value == null) {
this.writeNull(IonType.CLOB);
}
else {
this.writeClob(value, 0, value.length);
}
return;
}
abstract public void writeDecimal(BigDecimal value) throws IOException;
public void writeFloat(float value) throws IOException
{
writeFloat((double)value);
}
public void writeNull() throws IOException
{
writeNull(IonType.NULL);
}
final void validateSymbolId(int sid) {
if (requireSymbolValidation && sid > getSymbolTable().getMaxId()) {
// There is no slot for this symbol ID in the symbol table,
// so an error would be raised on read. Fail early on write.
throw new UnknownSymbolException(sid);
}
}
public final void writeSymbolToken(SymbolToken tok)
throws IOException
{
if (tok == null) {
writeNull(IonType.SYMBOL);
return;
}
String text = tok.getText();
if (text != null)
{
writeSymbol(text);
}
else
{
int sid = tok.getSid();
validateSymbolId(sid);
writeSymbol(sid);
}
}
public void writeTimestampUTC(Date value) throws IOException
{
Timestamp time = Timestamp.forDateZ(value);
writeTimestamp(time);
}
//
// default value and reader implementations.
// note that these could be optimized, especially
// the reader versions, when the reader is of the
// same format as the writer.
//
@Deprecated
public void writeValue(IonValue value) throws IOException
{
if (value != null)
{
value.writeTo(this);
}
}
public void writeValues(IonReader reader) throws IOException
{
if (reader.getDepth() == 0) {
clear_system_value_stack();
}
if (reader.getType() == null) reader.next();
if (getDepth() == 0 && reader instanceof _Private_ReaderWriter) {
// Optimize symbol table copying
_Private_ReaderWriter private_reader =
(_Private_ReaderWriter)reader;
while (reader.getType() != null) {
transfer_symbol_tables(private_reader);
writeValue(reader);
reader.next();
}
}
else {
while (reader.getType() != null) {
writeValue(reader);
reader.next();
}
}
}
private final void transfer_symbol_tables(_Private_ReaderWriter reader)
throws IOException
{
SymbolTable reader_symbols = reader.pop_passed_symbol_table();
if (reader_symbols != null) {
clear_system_value_stack();
setSymbolTable(reader_symbols);
while (reader_symbols != null) {
// TODO these symtabs are never popped!
// Why bother pushing them?
push_symbol_table(reader_symbols);
reader_symbols = reader.pop_passed_symbol_table();
}
}
}
/**
* @throws UnknownSymbolException if the text of the field name is
* unknown.
*/
private final void write_value_field_name_helper(IonReader reader)
{
if (this.isInStruct() && !isFieldNameSet())
{
SymbolToken tok = reader.getFieldNameSymbol();
if (tok == null)
{
throw new IllegalStateException("Field name not set");
}
setFieldNameSymbol(tok);
}
}
private final void write_value_annotations_helper(IonReader reader)
{
SymbolToken[] a = reader.getTypeAnnotationSymbols();
// At present, we must always call this, even when the list is empty,
// because local symtab diversion leaves the $ion_symbol_table
// dangling on the system writer! TODO fix that, it's broken.
setTypeAnnotationSymbols(a);
}
public boolean isStreamCopyOptimized()
{
return false;
}
/**
* Overrides can optimize special cases.
*/
public void writeValue(IonReader reader) throws IOException
{
// TODO this should do symtab optimization as per writeValues()
writeValueRecursively(reader);
}
/**
* Writes the provided IonReader's current value including any annotations. This function will not advance the
* IonReader beyond the end of the current value; users wishing to continue using the IonReader at the current
* depth will need to call {@link IonReader#next()} again.
*
* - If the IonReader is not positioned over a value (for example: because it is at the beginning or end of a
* stream), then this function does nothing.
* - If the current value is a container, this function will visit all of its child values and write those too,
* advancing the IonReader to the end of the container in the process.
* - If both this writer and the IonReader are in a struct, the writer will write the current value's field name.
* - If the writer is not in a struct but the reader is, the writer will ignore the current value's field name.
* - If the writer is in a struct but the IonReader is not, this function throws an IllegalStateException.
*
* @param reader The IonReader that will provide a value to write.
* @throws IOException if either the provided IonReader or this writer's underlying OutputStream throw an
* IOException.
* @throws IllegalStateException if this writer is inside a struct but the IonReader is not.
*/
final void writeValueRecursively(IonReader reader) throws IOException
{
// The IonReader does not need to be at the top level (getDepth()==0) when the function is called.
// We take note of its initial depth so we can avoid advancing the IonReader beyond the starting value.
int startingDepth = getDepth();
// The IonReader will be at `startingDepth` when the function is first called and then again when we
// have finished traversing all of its children. This boolean tracks which of those two states we are
// in when `getDepth() == startingDepth`.
boolean alreadyProcessedTheStartingValue = false;
// The IonType of the IonReader's current value.
IonType type;
while (true) {
// Each time we reach the top of the loop we are in one of three states:
// 1. We have not yet begun processing the starting value.
// 2. We are currently traversing the starting value's children.
// 3. We have finished processing the starting value.
if (getDepth() == startingDepth) {
// The IonReader is at the starting depth. We're either beginning our traversal or finishing it.
if (alreadyProcessedTheStartingValue) {
// We're finishing our traversal.
break;
}
// We're beginning our traversal. Don't advance the cursor; instead, use the current
// value's IonType.
type = reader.getType();
// We've begun processing the starting value.
alreadyProcessedTheStartingValue = true;
} else {
// We're traversing the starting value's children (that is: values at greater depths). We need to
// advance the cursor by calling next().
type = reader.next();
}
if (type == null) {
// There are no more values at this level. If we're at the starting level, we're done.
if (getDepth() == startingDepth) {
break;
}
// Otherwise, step out once and then try to move forward again.
reader.stepOut();
stepOut();
continue;
}
// We found a value. Write out its field name and annotations, if any.
write_value_field_name_helper(reader);
write_value_annotations_helper(reader);
if (reader.isNullValue()) {
this.writeNull(type);
continue;
}
switch (type) {
case NULL:
// The isNullValue() check above will handle this.
throw new IllegalStateException("isNullValue() was false but IonType was NULL.");
case BOOL:
writeBool(reader.booleanValue());
break;
case INT:
writeInt(reader.bigIntegerValue());
break;
case FLOAT:
writeFloat(reader.doubleValue());
break;
case DECIMAL:
writeDecimal(reader.decimalValue());
break;
case TIMESTAMP:
writeTimestamp(reader.timestampValue());
break;
case STRING:
writeString(reader.stringValue());
break;
case SYMBOL:
writeSymbolToken(reader.symbolValue());
break;
case BLOB:
writeBlob(reader.newBytes());
break;
case CLOB:
writeClob(reader.newBytes());
break;
case STRUCT: // Intentional fallthrough
case LIST: // Intentional fallthrough
case SEXP:
reader.stepIn();
stepIn(type);
break;
default:
throw new IllegalStateException("Unknown value type: " + type);
}
}
}
//
// This code handles the skipped symbol table
// support - it is cloned in IonReaderTextUserX,
// IonReaderBinaryUserX and _Private_IonWriterBase
//
// SO ANY FIXES HERE WILL BE NEEDED IN THOSE
// THREE LOCATIONS AS WELL.
//
private int _symbol_table_top = 0;
private SymbolTable[] _symbol_table_stack = new SymbolTable[3]; // 3 is rare, IVM followed by a local sym tab with open content
private void clear_system_value_stack()
{
while (_symbol_table_top > 0) {
_symbol_table_top--;
_symbol_table_stack[_symbol_table_top] = null;
}
}
private void push_symbol_table(SymbolTable symbols)
{
assert(symbols != null);
if (_symbol_table_top >= _symbol_table_stack.length) {
int new_len = _symbol_table_stack.length * 2;
SymbolTable[] temp = new SymbolTable[new_len];
System.arraycopy(_symbol_table_stack, 0, temp, 0, _symbol_table_stack.length);
_symbol_table_stack = temp;
}
_symbol_table_stack[_symbol_table_top++] = symbols;
}
public final SymbolTable pop_passed_symbol_table()
{
if (_symbol_table_top <= 0) {
return null;
}
_symbol_table_top--;
SymbolTable symbols = _symbol_table_stack[_symbol_table_top];
_symbol_table_stack[_symbol_table_top] = null;
return symbols;
}
public T asFacet(Class facetType)
{
// This implementation has no facets.
return null;
}
}