com.amazon.ion.impl.IonWriterSystem 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 static com.amazon.ion.SymbolTable.UNKNOWN_SYMBOL_ID;
import static com.amazon.ion.impl._Private_Utils.newSymbolToken;
import static com.amazon.ion.impl._Private_Utils.newSymbolTokens;
import com.amazon.ion.IonException;
import com.amazon.ion.IonType;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.SymbolToken;
import com.amazon.ion.SystemSymbols;
import com.amazon.ion.UnknownSymbolException;
import com.amazon.ion.system.IonWriterBuilder.InitialIvmHandling;
import com.amazon.ion.system.IonWriterBuilder.IvmMinimizing;
import java.io.IOException;
abstract class IonWriterSystem
extends _Private_IonWriterBase
{
/**
* The system symtab used when resetting the stream.
* Must not be null.
*/
final SymbolTable _default_system_symbol_table;
/**
* What to do about IVMs at the start of the stream.
* Becomes null as soon as we write anything.
* After a {@link #finish()} this becomes {@link InitialIVMHandling#ENSURE}
* because we must force another IVM to be emitted.
*/
private InitialIvmHandling _initial_ivm_handling;
/**
* What to do about non-initial IVMs.
*/
private final IvmMinimizing _ivm_minimizing;
/**
* Indicates whether the (immediately previous emitted value was an IVM.
* This is cleared by {@link #endValue()}.
*/
private boolean _previous_value_was_ivm;
/**
* Indicates whether we've written anything at all.
*/
private boolean _anything_written;
/**
* Must be either local or system table, and never null.
* May only be changed between top-level values.
*/
private SymbolTable _symbol_table;
/** really ion type is only used for int, string or null (unknown) */
private IonType _field_name_type;
private String _field_name;
private int _field_name_sid = UNKNOWN_SYMBOL_ID;
private static final int DEFAULT_ANNOTATION_COUNT = 4;
private int _annotation_count;
private SymbolToken[] _annotations =
new SymbolToken[DEFAULT_ANNOTATION_COUNT];
//========================================================================
/**
* @param defaultSystemSymbolTable must not be null.
*/
IonWriterSystem(SymbolTable defaultSystemSymbolTable,
InitialIvmHandling initialIvmHandling,
IvmMinimizing ivmMinimizing,
boolean requireSymbolValidation)
{
super(requireSymbolValidation);
defaultSystemSymbolTable.getClass(); // Efficient null check
_default_system_symbol_table = defaultSystemSymbolTable;
_symbol_table = defaultSystemSymbolTable;
_initial_ivm_handling = initialIvmHandling;
_ivm_minimizing = ivmMinimizing;
}
//========================================================================
// Context management
final SymbolTable getDefaultSystemSymtab()
{
return _default_system_symbol_table;
}
public final SymbolTable getSymbolTable()
{
return _symbol_table;
}
/**
* {@inheritDoc}
*
* This implementation simply validates that the argument is not a
* shared symbol table, and assigns it to {@link #_symbol_table}.
*/
@Override
public final void setSymbolTable(SymbolTable symbols)
throws IOException
{
if (symbols == null || _Private_Utils.symtabIsSharedNotSystem(symbols)) {
throw new IllegalArgumentException("symbol table must be local or system to be set, or reset");
}
if (getDepth() > 0) {
throw new IllegalStateException("the symbol table cannot be set, or reset, while a container is open");
}
_symbol_table = symbols;
}
boolean shouldWriteIvm()
{
if (_initial_ivm_handling == InitialIvmHandling.ENSURE)
{
return true;
}
if (_initial_ivm_handling == InitialIvmHandling.SUPPRESS)
{
// TODO amazon-ion/ion-java/issues/24 Must write IVM if given system != 1.0
return false;
}
// TODO amazon-ion/ion-java/issues/24 Add SUPPRESS_ALL to suppress non 1.0 IVMs
if (_ivm_minimizing == IvmMinimizing.ADJACENT)
{
// TODO amazon-ion/ion-java/issues/24 Write IVM if current system version != given system
// For now we assume that it's the same since we only support 1.0
return ! _previous_value_was_ivm;
}
if (_ivm_minimizing == IvmMinimizing.DISTANT)
{
// TODO amazon-ion/ion-java/issues/24 Write IVM if current system version != given system
// For now we assume that it's the same since we only support 1.0
return ! _anything_written;
}
return true;
}
/**
* Sets {@link #_symbol_table} and clears {@link #_initial_ivm_handling}.
* Subclasses should override to generate output.
*/
final void writeIonVersionMarker(SymbolTable systemSymtab)
throws IOException
{
if (getDepth() != 0)
{
String message =
"Ion Version Markers are only valid at the top level of a " +
"data stream";
throw new IllegalStateException(message);
}
assert systemSymtab.isSystemTable();
if (! SystemSymbols.ION_1_0.equals(systemSymtab.getIonVersionId()))
{
String message = "This library only supports Ion 1.0";
throw new UnsupportedOperationException(message);
}
if (shouldWriteIvm())
{
_initial_ivm_handling = null;
writeIonVersionMarkerAsIs(systemSymtab);
_previous_value_was_ivm = true;
}
_symbol_table = systemSymtab;
}
/**
* Writes an IVM without checking preconditions or
* {@link InitialIVMHandling}.
*/
abstract void writeIonVersionMarkerAsIs(SymbolTable systemSymtab)
throws IOException;
@Override
public final void writeIonVersionMarker()
throws IOException
{
writeIonVersionMarker(_default_system_symbol_table);
}
void writeLocalSymtab(SymbolTable symtab)
throws IOException
{
assert symtab.isLocalTable();
_symbol_table = symtab;
}
/**
* Builds a new local symbol table from the current contextual symtab
* (a system symtab).
* @return not null.
*/
SymbolTable inject_local_symbol_table() throws IOException
{
assert _symbol_table.isSystemTable();
// no catalog since it doesn't matter as this is a
// pure local table, with no imports
return LocalSymbolTable.DEFAULT_LST_FACTORY.newLocalSymtab(_symbol_table);
}
@Override
final String assumeKnownSymbol(int sid)
{
String text = _symbol_table.findKnownSymbol(sid);
if (text == null)
{
throw new UnknownSymbolException(sid);
}
return text;
}
final int add_symbol(String name) throws IOException
{
int sid;
if (_symbol_table.isSystemTable()) {
sid = _symbol_table.findSymbol(name);
if (sid != UNKNOWN_SYMBOL_ID) {
return sid;
}
// @name is not a system symbol, so we inject a local symtab
_symbol_table = inject_local_symbol_table();
}
assert _symbol_table.isLocalTable();
sid = _symbol_table.intern(name).getSid();
return sid;
}
void startValue() throws IOException
{
if (_initial_ivm_handling == InitialIvmHandling.ENSURE)
{
writeIonVersionMarker(_default_system_symbol_table);
}
}
void endValue()
{
_initial_ivm_handling = null;
_previous_value_was_ivm = false;
_anything_written = true;
}
/** Writes a symbol without checking for system ID. */
abstract void writeSymbolAsIs(int symbolId) throws IOException;
/** Writes a symbol without checking for system ID. */
abstract void writeSymbolAsIs(String value) throws IOException;
@Override
final void writeSymbol(int symbolId) throws IOException
{
if (symbolId < 0) {
throw new IllegalArgumentException("symbol IDs are >= 0.");
}
if (symbolId == SystemSymbols.ION_1_0_SID
&& getDepth() == 0
&& _annotation_count == 0) {
// $ion_1_0 is written as an IVM only if it is not annotated
// TODO amazon-ion/ion-java/issues/24 Make sure to get the right symtab, default may differ.
writeIonVersionMarker();
}
else
{
writeSymbolAsIs(symbolId);
}
}
public final void writeSymbol(String value) throws IOException
{
if (SystemSymbols.ION_1_0.equals(value)
&& getDepth() == 0
&& _annotation_count == 0) {
// $ion_1_0 is written as an IVM only if it is not annotated
// TODO amazon-ion/ion-java/issues/24 Make sure to get the right symtab, default may differ.
writeIonVersionMarker();
}
else {
writeSymbolAsIs(value);
}
}
public void finish() throws IOException
{
if (getDepth() != 0) {
throw new IllegalStateException(ERROR_FINISH_NOT_AT_TOP_LEVEL);
}
flush();
_previous_value_was_ivm = false;
_initial_ivm_handling = InitialIvmHandling.ENSURE;
_symbol_table = _default_system_symbol_table;
}
//========================================================================
// Field names
// This handles converting string to int (or the reverse) using the current
// symbol table, if that is needed. These routines are not generally
// overridden except to throw UnsupportedOperationException when they are
// not supported.
@Override
public final boolean isFieldNameSet()
{
if (_field_name_type != null) {
switch (_field_name_type) {
case STRING:
return _field_name != null;
case INT:
return _field_name_sid >= 0;
default:
break;
}
}
return false;
}
final void clearFieldName()
{
_field_name_type = null;
_field_name = null;
_field_name_sid = UNKNOWN_SYMBOL_ID;
}
public final void setFieldName(String name)
{
if (!this.isInStruct()) {
throw new IllegalStateException();
}
name.getClass(); // fast null check
_field_name_type = IonType.STRING;
_field_name = name;
_field_name_sid = UNKNOWN_SYMBOL_ID;
}
public final void setFieldNameSymbol(SymbolToken name)
{
if (!this.isInStruct()) {
throw new IllegalStateException();
}
String text = name.getText();
if (text != null)
{
_field_name_type = IonType.STRING;
_field_name = text;
_field_name_sid = UNKNOWN_SYMBOL_ID;
}
else
{
int sid = name.getSid();
if (sid < 0) {
throw new IllegalArgumentException();
}
validateSymbolId(sid);
_field_name_type = IonType.INT;
_field_name_sid = sid;
_field_name = null;
}
}
/**
* Returns the symbol id of the current field name, if the field name
* has been set. If the name has not been set, either as either a String
* or a symbol id value, this returns -1 (undefined symbol).
* @return symbol id of the name of the field about to be written or -1 if
* it is not set
*/
final int getFieldId()
{
int id;
if (_field_name_type == null) {
throw new IllegalStateException("the field has not be set");
}
switch (_field_name_type) {
case STRING:
try {
id = add_symbol(_field_name);
}
catch (IOException e) {
throw new IonException(e);
}
// TODO cache the sid?
break;
case INT:
id = _field_name_sid;
break;
default:
throw new IllegalStateException("the field has not be set");
}
return id;
}
final SymbolToken assumeFieldNameSymbol()
{
if (_field_name_type == null) {
throw new IllegalStateException(ERROR_MISSING_FIELD_NAME);
}
// Exactly one of our fields is set.
assert _field_name != null ^ _field_name_sid >= 0;
return new SymbolTokenImpl(_field_name, _field_name_sid);
}
//========================================================================
// Annotations
/**
* Ensures that our {@link #_annotations} and {@link #_annotation_sids}
* arrays have enough capacity to hold the given number of annotations.
* Does not increase {@link #_annotation_count}.
*/
final void ensureAnnotationCapacity(int length) {
int oldlen = (_annotations == null) ? 0 : _annotations.length;
if (length < oldlen) return;
int newlen = (_annotations == null) ? 10 : (_annotations.length * 2);
if (length > newlen) {
newlen = length;
}
SymbolToken[] temp1 = new SymbolToken[newlen];
if (oldlen > 0) {
System.arraycopy(_annotations, 0, temp1, 0, oldlen);
}
_annotations = temp1;
}
final int[] internAnnotationsAndGetSids() throws IOException
{
int count = _annotation_count;
if (count == 0) return _Private_Utils.EMPTY_INT_ARRAY;
int[] sids = new int[count];
for (int i = 0; i < count; i++)
{
SymbolToken sym = _annotations[i];
int sid = sym.getSid();
if (sid == UNKNOWN_SYMBOL_ID)
{
String text = sym.getText();
sid = add_symbol(text);
_annotations[i] = new SymbolTokenImpl(text, sid);
}
sids[i] = sid;
}
return sids;
}
final boolean hasAnnotations()
{
return _annotation_count != 0;
}
final int annotationCount()
{
return _annotation_count;
}
final void clearAnnotations()
{
_annotation_count = 0;
}
@Override
final int findAnnotation(String name) {
if (_annotation_count > 0) {
for (int ii=0; ii<_annotation_count; ii++) {
if (name.equals(_annotations[ii].getText())) {
return ii;
}
}
}
return -1;
}
final SymbolToken[] getTypeAnnotationSymbols()
{
int count = _annotation_count;
if (count == 0) return SymbolToken.EMPTY_ARRAY;
SymbolToken[] syms = new SymbolToken[count];
System.arraycopy(_annotations, 0, syms, 0, count);
return syms;
}
public final void setTypeAnnotationSymbols(SymbolToken... annotations)
{
if (annotations == null || annotations.length == 0)
{
_annotation_count = 0;
}
else
{
int annotationsArrayLength = annotations.length;
// TODO the following makes two copy passes
// TODO validate the input
ensureAnnotationCapacity(annotationsArrayLength);
SymbolTable symtab = getSymbolTable();
int count = 0;
while (count < annotationsArrayLength)
{
SymbolToken sym = annotations[count];
if (sym == null) {
break;
}
if (sym.getText() == null) {
validateSymbolId(sym.getSid());
}
sym = _Private_Utils.localize(symtab, sym);
_annotations[count] = sym;
count++;
}
_annotation_count = count;
}
}
@Override
final String[] getTypeAnnotations()
{
return _Private_Utils.toStrings(_annotations, _annotation_count);
}
public final void setTypeAnnotations(String... annotations)
{
if (annotations == null || annotations.length == 0)
{
_annotation_count = 0;
}
else
{
SymbolToken[] syms =
newSymbolTokens(getSymbolTable(), annotations);
int count = syms.length;
// TODO the following makes two copy passes
ensureAnnotationCapacity(count);
System.arraycopy(syms, 0, _annotations, 0, count);
_annotation_count = count;
}
}
public final void addTypeAnnotation(String annotation)
{
SymbolToken is = newSymbolToken(getSymbolTable(), annotation);
ensureAnnotationCapacity(_annotation_count + 1);
_annotations[_annotation_count++] = is;
}
@Override
final int[] getTypeAnnotationIds()
{
return _Private_Utils.toSids(_annotations, _annotation_count);
}
public T asFacet(Class facetType)
{
// This implementation has no facets.
return null;
}
}