com.amazon.ion.impl.IonReaderContinuableApplicationBinary Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ion-java Show documentation
Show all versions of ion-java Show documentation
A Java implementation of the Amazon Ion data notation.
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.ion.impl;
import com.amazon.ion.IonBufferConfiguration;
import com.amazon.ion.IonCatalog;
import com.amazon.ion.IonException;
import com.amazon.ion.IonReader;
import com.amazon.ion.IonStruct;
import com.amazon.ion.IonType;
import com.amazon.ion.IonWriter;
import com.amazon.ion.ReadOnlyValueException;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.SymbolToken;
import com.amazon.ion.SystemSymbols;
import com.amazon.ion.UnknownSymbolException;
import com.amazon.ion.ValueFactory;
import com.amazon.ion.impl.bin.IntList;
import com.amazon.ion.system.IonReaderBuilder;
import com.amazon.ion.system.SimpleCatalog;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import static com.amazon.ion.SystemSymbols.IMPORTS_SID;
import static com.amazon.ion.SystemSymbols.ION;
import static com.amazon.ion.SystemSymbols.ION_SYMBOL_TABLE_SID;
import static com.amazon.ion.SystemSymbols.MAX_ID_SID;
import static com.amazon.ion.SystemSymbols.NAME_SID;
import static com.amazon.ion.SystemSymbols.SYMBOLS_SID;
import static com.amazon.ion.SystemSymbols.VERSION_SID;
/**
* An IonCursor capable of application-level parsing of binary Ion streams.
*/
class IonReaderContinuableApplicationBinary extends IonReaderContinuableCoreBinary implements IonReaderContinuableApplication {
// The UTF-8 encoded bytes representing the text `$ion_symbol_table`.
private static final byte[] ION_SYMBOL_TABLE_UTF8;
static {
ION_SYMBOL_TABLE_UTF8 = SystemSymbols.ION_SYMBOL_TABLE.getBytes(StandardCharsets.UTF_8);
}
// An IonCatalog containing zero shared symbol tables.
private static final IonCatalog EMPTY_CATALOG = new SimpleCatalog();
// Initial capacity of the ArrayList used to hold the text in the current symbol table.
private static final int SYMBOLS_LIST_INITIAL_CAPACITY = 128;
// The imports for Ion 1.0 data with no shared user imports.
private static final LocalSymbolTableImports ION_1_0_IMPORTS
= new LocalSymbolTableImports(SharedSymbolTable.getSystemSymbolTable(1));
// The text representations of the symbol table that is currently in scope, indexed by symbol ID. If the element at
// a particular index is null, that symbol has unknown text.
private String[] symbols;
// The maximum offset into the 'symbols' array that points to a valid local symbol.
private int localSymbolMaxOffset = -1;
// The catalog used by the reader to resolve shared symbol table imports.
private final IonCatalog catalog;
// Uses the underlying raw reader to read the symbol tables from the stream.
private final SymbolTableReader symbolTableReader;
// The shared symbol tables imported by the local symbol table that is currently in scope.
private LocalSymbolTableImports imports = ION_1_0_IMPORTS;
// The first lowest local symbol ID in the symbol table.
private int firstLocalSymbolId = imports.getMaxId() + 1;
// A map of symbol ID to SymbolToken representation. Because most use cases only require symbol text, this
// is used only if necessary to avoid imposing the extra expense on all symbol lookups.
private List symbolTokensById = null;
// The cached SymbolTable representation of the current local symbol table. Invalidated whenever a local
// symbol table is encountered in the stream.
private SymbolTable cachedReadOnlySymbolTable = null;
// The reusable annotation iterator.
private final AnnotationSequenceIterator annotationIterator = new AnnotationSequenceIterator();
// ------
/**
* Constructs a new reader from the given byte array.
* @param builder the builder containing the configuration for the new reader.
* @param bytes the byte array containing the bytes to read.
* @param offset the offset into the byte array at which the first byte of Ion data begins.
* @param length the number of bytes to be read from the byte array.
*/
IonReaderContinuableApplicationBinary(IonReaderBuilder builder, byte[] bytes, int offset, int length) {
super(builder.getBufferConfiguration(), bytes, offset, length);
this.catalog = builder.getCatalog() == null ? EMPTY_CATALOG : builder.getCatalog();
symbols = new String[SYMBOLS_LIST_INITIAL_CAPACITY];
symbolTableReader = new SymbolTableReader();
resetImports();
registerIvmNotificationConsumer((x, y) -> {
// Note: for Ion 1.1 support, use the versions to set the proper system symbol table and local symbol table
// processing logic.
resetSymbolTable();
resetImports();
});
}
/**
* Constructs a new reader from the given input stream.
* @param builder the builder containing the configuration for the new reader.
* @param alreadyRead the byte array containing the bytes already read (often the IVM).
* @param alreadyReadOff the offset into `alreadyRead` at which the first byte that was already read exists.
* @param alreadyReadLen the number of bytes already read from `alreadyRead`.
*/
IonReaderContinuableApplicationBinary(final IonReaderBuilder builder, final InputStream inputStream, byte[] alreadyRead, int alreadyReadOff, int alreadyReadLen) {
super(builder.getBufferConfiguration(), inputStream, alreadyRead, alreadyReadOff, alreadyReadLen);
this.catalog = builder.getCatalog() == null ? EMPTY_CATALOG : builder.getCatalog();
symbols = new String[SYMBOLS_LIST_INITIAL_CAPACITY];
symbolTableReader = new SymbolTableReader();
resetImports();
registerIvmNotificationConsumer((x, y) -> {
// Note: for Ion 1.1 support, use the versions to set the proper system symbol table and local symbol table
// processing logic.
resetSymbolTable();
resetImports();
});
registerOversizedValueHandler(
() -> {
boolean mightBeSymbolTable = true;
if (state == State.READING_VALUE) {
// The reader is not currently processing a symbol table.
if (parent != null || !hasAnnotations) {
// Only top-level annotated values can be symbol tables.
mightBeSymbolTable = false;
} else if (annotationSequenceMarker.startIndex >= 0 && annotationSequenceMarker.endIndex <= limit) {
// The annotations on the value are available.
if (startsWithIonSymbolTable()) {
// The first annotation on the value is $ion_symbol_table. It may be a symbol table if
// its type is not yet known (null); it is definitely a symbol table if its type is STRUCT.
IonType type = super.getType();
mightBeSymbolTable = type == null || type == IonType.STRUCT;
} else {
// The first annotation on the value is not $ion_symbol_table, so it cannot be a symbol table.
mightBeSymbolTable = false;
}
}
}
if (mightBeSymbolTable) {
builder.getBufferConfiguration().getOversizedSymbolTableHandler().onOversizedSymbolTable();
terminate();
} else {
builder.getBufferConfiguration().getOversizedValueHandler().onOversizedValue();
}
}
);
}
/**
* Reusable iterator over the annotations on the current value.
*/
private class AnnotationSequenceIterator implements Iterator {
// All of the annotation SIDs on the current value.
private IntList annotationSids;
// The index into `annotationSids` containing the next annotation to be returned.
private int index = 0;
void reset() {
index = 0;
annotationSids = getAnnotationSidList();
}
@Override
public boolean hasNext() {
return index < annotationSids.size();
}
@Override
public String next() {
int sid = annotationSids.get(index);
String annotation = getSymbol(sid);
if (annotation == null) {
throw new UnknownSymbolException(sid);
}
index++;
return annotation;
}
@Override
public void remove() {
throw new UnsupportedOperationException("This iterator does not support element removal.");
}
}
/**
* Gets the system symbol table for the Ion version currently active.
* @return a system SymbolTable.
*/
private SymbolTable getSystemSymbolTable() {
// Note: Ion 1.1 currently proposes changes to the system symbol table. If this is finalized, then
// 'majorVersion' cannot be used to look up the system symbol table; both 'majorVersion' and 'minorVersion'
// will need to be used.
return SharedSymbolTable.getSystemSymbolTable(getIonMajorVersion());
}
/**
* Read-only snapshot of the local symbol table at the reader's current position.
*/
private class LocalSymbolTableSnapshot implements _Private_LocalSymbolTable, SymbolTableAsStruct {
// The system symbol table.
private final SymbolTable system = IonReaderContinuableApplicationBinary.this.getSystemSymbolTable();
// The max ID of this local symbol table.
private final int maxId;
// The shared symbol tables imported by this local symbol table.
private final LocalSymbolTableImports importedTables;
// Map representation of this symbol table. Keys are symbol text; values are the lowest symbol ID that maps
// to that text.
final Map textToId;
// List representation of this symbol table, indexed by symbol ID.
final String[] idToText;
private SymbolTableStructCache structCache = null;
LocalSymbolTableSnapshot() {
int importsMaxId = imports.getMaxId();
int numberOfLocalSymbols = localSymbolMaxOffset + 1;
// Note: 'imports' is immutable, so a clone is not needed.
importedTables = imports;
maxId = importsMaxId + numberOfLocalSymbols;
idToText = new String[numberOfLocalSymbols];
System.arraycopy(symbols, 0, idToText, 0, numberOfLocalSymbols);
// Map with initial size and load factor set so that it will not grow unconditionally when it is filled.
// Note: using the default load factor of 0.75 results in better lookup performance than using 1.0 and
// filling the map to capacity.
textToId = new HashMap<>((int) Math.ceil(numberOfLocalSymbols / 0.75), 0.75f);
for (int i = 0; i < numberOfLocalSymbols; i++) {
String symbol = idToText[i];
if (symbol != null) {
textToId.put(symbol, i + importsMaxId + 1);
}
}
}
@Override
public String getName() {
return null;
}
@Override
public int getVersion() {
return 0;
}
@Override
public boolean isLocalTable() {
return true;
}
@Override
public boolean isSharedTable() {
return false;
}
@Override
public boolean isSubstitute() {
return false;
}
@Override
public boolean isSystemTable() {
return false;
}
@Override
public SymbolTable getSystemSymbolTable() {
return system;
}
@Override
public String getIonVersionId() {
return system.getIonVersionId();
}
@Override
public SymbolTable[] getImportedTables() {
return importedTables.getImportedTables();
}
@Override
public int getImportedMaxId() {
return importedTables.getMaxId();
}
@Override
public SymbolToken find(String text) {
SymbolToken token = importedTables.find(text);
if (token != null) {
return token;
}
Integer sid = textToId.get(text);
if (sid == null) {
return null;
}
// The following per-call allocation is intentional. When weighed against the alternative of making
// 'mapView' a 'Map` instead of a `Map`, the following points should
// be considered:
// 1. A LocalSymbolTableSnapshot is only created when getSymbolTable() is called on the reader. The reader
// does not use the LocalSymbolTableSnapshot internally. There are two cases when getSymbolTable() would be
// called: a) when the user calls it, which will basically never happen, and b) when the user uses
// IonSystem.iterate over the reader, in which case each top-level value holds a reference to the symbol
// table that was in scope when it occurred. In case a), in addition to rarely being called at all, it
// would be even rarer for a user to use find() to retrieve each symbol (especially more than once) from the
// returned symbol table. Case b) may be called more frequently, but it remains equally rare that a user
// would retrieve each symbol at least once.
// 2. If we make mapView a Map, then we are guaranteeing that we will allocate at least
// one SymbolToken per symbol (because mapView is created in the constructor of LocalSymbolTableSnapshot)
// even though it's unlikely most will ever be needed.
return new SymbolTokenImpl(text, sid);
}
@Override
public int findSymbol(String name) {
Integer sid = importedTables.findSymbol(name);
if (sid > UNKNOWN_SYMBOL_ID) {
return sid;
}
sid = textToId.get(name);
if (sid == null) {
return UNKNOWN_SYMBOL_ID;
}
return sid;
}
@Override
public String findKnownSymbol(int id) {
if (id < 0) {
throw new IllegalArgumentException("Symbol IDs must be at least 0.");
}
if (id > getMaxId()) {
return null;
}
return IonReaderContinuableApplicationBinary.this.getSymbolString(id, importedTables, idToText);
}
@Override
public Iterator iterateDeclaredSymbolNames() {
return new Iterator() {
private int index = 0;
@Override
public boolean hasNext() {
return index < idToText.length;
}
@Override
public String next() {
if (index >= idToText.length) {
throw new NoSuchElementException();
}
String symbol = idToText[index];
index++;
return symbol;
}
@Override
public void remove() {
throw new UnsupportedOperationException("This iterator does not support element removal.");
}
};
}
@Override
public SymbolToken intern(String text) {
SymbolToken token = find(text);
if (token != null) {
return token;
}
throw new ReadOnlyValueException();
}
@Override
public int getMaxId() {
return maxId;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public void makeReadOnly() {
// The symbol table is already read-only.
}
@Override
public void writeTo(IonWriter writer) throws IOException {
IonReader reader = new com.amazon.ion.impl.SymbolTableReader(this);
writer.writeValues(reader);
}
@Override
public String toString() {
return "(LocalSymbolTable max_id:" + getMaxId() + ')';
}
@Override
public IonStruct getIonRepresentation(ValueFactory valueFactory) {
if (structCache == null) {
structCache = new SymbolTableStructCache(this, getImportedTables(), null);
}
return structCache.getIonRepresentation(valueFactory);
}
@Override
public _Private_LocalSymbolTable makeCopy() {
// This is a mutable copy. LocalSymbolTable handles the mutability concerns.
return new LocalSymbolTable(importedTables, Arrays.asList(idToText));
}
@Override
public SymbolTable[] getImportedTablesNoCopy() {
return importedTables.getImportedTablesNoCopy();
}
}
/**
* Reset the local symbol table to the system symbol table.
*/
private void resetSymbolTable() {
// The following line is not required for correctness, but it frees the references to the old symbols,
// potentially allowing them to be garbage collected.
Arrays.fill(symbols, 0, localSymbolMaxOffset + 1, null);
localSymbolMaxOffset = -1;
cachedReadOnlySymbolTable = null;
if (symbolTokensById != null) {
symbolTokensById.clear();
}
}
/**
* Reset the list of imported shared symbol tables.
*/
private void resetImports() {
// Note: when support for the next version of Ion is added, conditionals on 'majorVersion' and 'minorVersion'
// must be added here.
imports = ION_1_0_IMPORTS;
firstLocalSymbolId = imports.getMaxId() + 1;
}
/**
* Restore a symbol table from a previous point in the stream.
* @param symbolTable the symbol table to restore.
*/
protected void restoreSymbolTable(SymbolTable symbolTable) {
if (cachedReadOnlySymbolTable == symbolTable) {
return;
}
if (symbolTable instanceof LocalSymbolTableSnapshot) {
LocalSymbolTableSnapshot snapshot = (LocalSymbolTableSnapshot) symbolTable;
cachedReadOnlySymbolTable = snapshot;
imports = snapshot.importedTables;
firstLocalSymbolId = imports.getMaxId() + 1;
localSymbolMaxOffset = snapshot.maxId - firstLocalSymbolId;
// Note: because `symbols` only grows, `snapshot.listView` will always fit within `symbols`.
System.arraycopy(snapshot.idToText, 0, symbols, 0, snapshot.idToText.length);
if (symbolTokensById != null) {
symbolTokensById.clear();
}
} else {
// Note: this will only happen when `symbolTable` is the system symbol table.
resetSymbolTable();
cachedReadOnlySymbolTable = symbolTable;
resetImports();
localSymbolMaxOffset = -1;
}
}
/**
* Creates a shared symbol table import, resolving it from the catalog if possible.
* @param name the name of the shared symbol table.
* @param version the version of the shared symbol table.
* @param maxId the max_id of the shared symbol table. This value takes precedence over the actual max_id for the
* shared symbol table at the requested version.
*/
private SymbolTable createImport(String name, int version, int maxId) {
SymbolTable shared = catalog.getTable(name, version);
if (maxId < 0) {
if (shared == null || version != shared.getVersion()) {
String message =
"Import of shared table "
+ name
+ " lacks a valid max_id field, but an exact match was not"
+ " found in the catalog";
if (shared != null) {
message += " (found version " + shared.getVersion() + ")";
}
throw new IonException(message);
}
// Exact match is found, but max_id is undefined in import declaration. Set max_id to the largest SID of
// the matching symbol table.
maxId = shared.getMaxId();
}
if (shared == null) {
// No match. All symbol IDs that fall within this shared symbol table's range will have unknown text.
return new SubstituteSymbolTable(name, version, maxId);
} else if (shared.getMaxId() != maxId || shared.getVersion() != version) {
// Partial match. If the requested max_id exceeds the actual max_id of the resolved shared symbol table,
// symbol IDs that exceed the max_id of the resolved shared symbol table will have unknown text.
return new SubstituteSymbolTable(shared, version, maxId);
} else {
// Exact match; the resolved shared symbol table may be used as-is.
return shared;
}
}
/**
* Gets the String representation of the given symbol ID. It is the caller's responsibility to ensure that the
* given symbol ID is within the max ID of the symbol table.
* @param sid the symbol ID.
* @param importedSymbols the symbol table's shared symbol table imports.
* @param localSymbols the symbol table's local symbols.
* @return a String, which will be null if the requested symbol ID has undefined text.
*/
private String getSymbolString(int sid, LocalSymbolTableImports importedSymbols, String[] localSymbols) {
if (sid <= importedSymbols.getMaxId()) {
return importedSymbols.findKnownSymbol(sid);
}
return localSymbols[sid - (importedSymbols.getMaxId() + 1)];
}
/**
* Retrieves the String text for the given symbol ID.
* @param sid a symbol ID.
* @return a String.
*/
String getSymbol(int sid) {
if (sid < firstLocalSymbolId) {
return imports.findKnownSymbol(sid);
}
int localSymbolOffset = sid - firstLocalSymbolId;
if (localSymbolOffset > localSymbolMaxOffset) {
throw new UnknownSymbolException(sid);
}
return symbols[localSymbolOffset];
}
/**
* Creates a SymbolToken representation of the given symbol ID.
* @param sid a symbol ID.
* @return a SymbolToken.
*/
private SymbolToken getSymbolToken(int sid) {
int symbolTableSize = localSymbolMaxOffset + firstLocalSymbolId + 1; // +1 because the max ID is 0-indexed.
if (symbolTokensById == null) {
symbolTokensById = new ArrayList<>(symbolTableSize);
}
if (symbolTokensById.size() < symbolTableSize) {
for (int i = symbolTokensById.size(); i < symbolTableSize; i++) {
symbolTokensById.add(null);
}
}
if (sid >= symbolTableSize) {
throw new UnknownSymbolException(sid);
}
SymbolToken token = symbolTokensById.get(sid);
if (token == null) {
String text = getSymbolString(sid, imports, symbols);
if (text == null && sid >= firstLocalSymbolId) {
// All symbols with unknown text in the local symbol range are equivalent to symbol zero.
sid = 0;
}
token = new SymbolTokenImpl(text, sid);
symbolTokensById.set(sid, token);
}
return token;
}
/**
* Uses the underlying raw reader to read the symbol tables from the stream. Capable of resuming if not enough
* data is currently available to complete the symbol table.
*/
private class SymbolTableReader {
private boolean hasSeenImports;
private boolean hasSeenSymbols;
private String name = null;
private int version = -1;
private int maxId = -1;
private List newImports = null;
private List newSymbols = null;
private void resetState() {
hasSeenImports = false;
hasSeenSymbols = false;
newImports = null;
newSymbols = null;
name = null;
version = -1;
maxId = -1;
}
private boolean valueUnavailable() {
Event event = fillValue();
return event == Event.NEEDS_DATA || event == Event.NEEDS_INSTRUCTION;
}
private void growSymbolsArray(int shortfall) {
int newSize = nextPowerOfTwo(symbols.length + shortfall);
String[] resized = new String[newSize];
System.arraycopy(symbols, 0, resized, 0, localSymbolMaxOffset + 1);
symbols = resized;
}
private void finishReadingSymbolTableStruct() {
stepOutOfContainer();
if (!hasSeenImports) {
resetSymbolTable();
resetImports();
}
if (newSymbols != null) {
int numberOfNewSymbols = newSymbols.size();
int numberOfAvailableSlots = symbols.length - (localSymbolMaxOffset + 1);
int shortfall = numberOfNewSymbols - numberOfAvailableSlots;
if (shortfall > 0) {
growSymbolsArray(shortfall);
}
int i = localSymbolMaxOffset;
for (String newSymbol : newSymbols) {
symbols[++i] = newSymbol;
}
localSymbolMaxOffset += newSymbols.size();
}
state = State.READING_VALUE;
}
private void readSymbolTableStructField() {
if (fieldSid == SYMBOLS_SID) {
state = State.ON_SYMBOL_TABLE_SYMBOLS;
if (hasSeenSymbols) {
throw new IonException("Symbol table contained multiple symbols fields.");
}
hasSeenSymbols = true;
} else if (fieldSid == IMPORTS_SID) {
state = State.ON_SYMBOL_TABLE_IMPORTS;
if (hasSeenImports) {
throw new IonException("Symbol table contained multiple imports fields.");
}
hasSeenImports = true;
}
}
private void startReadingImportsList() {
resetImports();
resetSymbolTable();
newImports = new ArrayList<>(3);
newImports.add(getSystemSymbolTable());
state = State.READING_SYMBOL_TABLE_IMPORTS_LIST;
}
private void preparePossibleAppend() {
if (symbolValueId() != ION_SYMBOL_TABLE_SID) {
resetSymbolTable();
}
state = State.ON_SYMBOL_TABLE_FIELD;
}
private void finishReadingImportsList() {
stepOutOfContainer();
imports = new LocalSymbolTableImports(newImports);
firstLocalSymbolId = imports.getMaxId() + 1;
state = State.ON_SYMBOL_TABLE_FIELD;
}
private void startReadingSymbolsList() {
newSymbols = new ArrayList<>(8);
state = State.READING_SYMBOL_TABLE_SYMBOLS_LIST;
}
private void startReadingSymbol() {
if (IonReaderContinuableApplicationBinary.super.getType() == IonType.STRING) {
state = State.READING_SYMBOL_TABLE_SYMBOL;
} else {
newSymbols.add(null);
}
}
private void finishReadingSymbol() {
newSymbols.add(stringValue());
state = State.READING_SYMBOL_TABLE_SYMBOLS_LIST;
}
private void finishReadingSymbolsList() {
stepOutOfContainer();
state = State.ON_SYMBOL_TABLE_FIELD;
}
private void startReadingImportStruct() {
name = null;
version = 1;
maxId = -1;
if (IonReaderContinuableApplicationBinary.super.getType() == IonType.STRUCT) {
stepIntoContainer();
state = State.READING_SYMBOL_TABLE_IMPORT_STRUCT;
}
}
private void finishReadingImportStruct() {
stepOutOfContainer();
state = State.READING_SYMBOL_TABLE_IMPORTS_LIST;
// Ignore import clauses with malformed name field.
if (name == null || name.length() == 0 || name.equals(ION)) {
return;
}
newImports.add(createImport(name, version, maxId));
}
private void startReadingImportStructField() {
int fieldId = getFieldId();
if (fieldId == NAME_SID) {
state = State.READING_SYMBOL_TABLE_IMPORT_NAME;
} else if (fieldId == VERSION_SID) {
state = State.READING_SYMBOL_TABLE_IMPORT_VERSION;
} else if (fieldId == MAX_ID_SID) {
state = State.READING_SYMBOL_TABLE_IMPORT_MAX_ID;
}
}
private void readImportName() {
if (IonReaderContinuableApplicationBinary.super.getType() == IonType.STRING) {
name = stringValue();
}
state = State.READING_SYMBOL_TABLE_IMPORT_STRUCT;
}
private void readImportVersion() {
if (IonReaderContinuableApplicationBinary.super.getType() == IonType.INT && !IonReaderContinuableApplicationBinary.super.isNullValue()) {
version = Math.max(1, intValue());
}
state = State.READING_SYMBOL_TABLE_IMPORT_STRUCT;
}
private void readImportMaxId() {
if (IonReaderContinuableApplicationBinary.super.getType() == IonType.INT && !IonReaderContinuableApplicationBinary.super.isNullValue()) {
maxId = intValue();
}
state = State.READING_SYMBOL_TABLE_IMPORT_STRUCT;
}
void readSymbolTable() {
Event event;
while (true) {
switch (state) {
case ON_SYMBOL_TABLE_STRUCT:
if (Event.NEEDS_DATA == stepIntoContainer()) {
return;
}
state = State.ON_SYMBOL_TABLE_FIELD;
break;
case ON_SYMBOL_TABLE_FIELD:
event = IonReaderContinuableApplicationBinary.super.nextValue();
if (Event.NEEDS_DATA == event) {
return;
}
if (event == Event.END_CONTAINER) {
finishReadingSymbolTableStruct();
return;
}
readSymbolTableStructField();
break;
case ON_SYMBOL_TABLE_SYMBOLS:
if (IonReaderContinuableApplicationBinary.super.getType() == IonType.LIST) {
if (Event.NEEDS_DATA == stepIntoContainer()) {
return;
}
startReadingSymbolsList();
} else {
state = State.ON_SYMBOL_TABLE_FIELD;
}
break;
case ON_SYMBOL_TABLE_IMPORTS:
if (IonReaderContinuableApplicationBinary.super.getType() == IonType.LIST) {
if (Event.NEEDS_DATA == stepIntoContainer()) {
return;
}
startReadingImportsList();
} else if (IonReaderContinuableApplicationBinary.super.getType() == IonType.SYMBOL) {
if (valueUnavailable()) {
return;
}
preparePossibleAppend();
} else {
state = State.ON_SYMBOL_TABLE_FIELD;
}
break;
case READING_SYMBOL_TABLE_SYMBOLS_LIST:
event = IonReaderContinuableApplicationBinary.super.nextValue();
if (event == Event.NEEDS_DATA) {
return;
}
if (event == Event.END_CONTAINER) {
finishReadingSymbolsList();
break;
}
startReadingSymbol();
break;
case READING_SYMBOL_TABLE_SYMBOL:
if (valueUnavailable()) {
return;
}
finishReadingSymbol();
break;
case READING_SYMBOL_TABLE_IMPORTS_LIST:
event = IonReaderContinuableApplicationBinary.super.nextValue();
if (event == Event.NEEDS_DATA) {
return;
}
if (event == Event.END_CONTAINER) {
finishReadingImportsList();
break;
}
startReadingImportStruct();
break;
case READING_SYMBOL_TABLE_IMPORT_STRUCT:
event = IonReaderContinuableApplicationBinary.super.nextValue();
if (event == Event.NEEDS_DATA) {
return;
}
if (event == Event.END_CONTAINER) {
finishReadingImportStruct();
break;
} else if (event != Event.START_SCALAR) {
break;
}
startReadingImportStructField();
break;
case READING_SYMBOL_TABLE_IMPORT_NAME:
if (valueUnavailable()) {
return;
}
readImportName();
break;
case READING_SYMBOL_TABLE_IMPORT_VERSION:
if (valueUnavailable()) {
return;
}
readImportVersion();
break;
case READING_SYMBOL_TABLE_IMPORT_MAX_ID:
if (valueUnavailable()) {
return;
}
readImportMaxId();
break;
default: throw new IllegalStateException();
}
}
}
}
/**
* The reader's state. `READING_VALUE` indicates that the reader is reading a user-level value; all other states
* indicate that the reader is in the middle of reading a symbol table.
*/
private enum State {
ON_SYMBOL_TABLE_STRUCT,
ON_SYMBOL_TABLE_FIELD,
ON_SYMBOL_TABLE_SYMBOLS,
READING_SYMBOL_TABLE_SYMBOLS_LIST,
READING_SYMBOL_TABLE_SYMBOL,
ON_SYMBOL_TABLE_IMPORTS,
READING_SYMBOL_TABLE_IMPORTS_LIST,
READING_SYMBOL_TABLE_IMPORT_STRUCT,
READING_SYMBOL_TABLE_IMPORT_NAME,
READING_SYMBOL_TABLE_IMPORT_VERSION,
READING_SYMBOL_TABLE_IMPORT_MAX_ID,
READING_VALUE
}
// The current state.
private State state = State.READING_VALUE;
/**
* @return true if current value has a sequence of annotations that begins with `$ion_symbol_table`; otherwise,
* false.
*/
boolean startsWithIonSymbolTable() {
long savedPeekIndex = peekIndex;
peekIndex = annotationSequenceMarker.startIndex;
int sid = minorVersion == 0 ? readVarUInt_1_0() : readVarUInt_1_1();
peekIndex = savedPeekIndex;
return ION_SYMBOL_TABLE_SID == sid;
}
/**
* @return true if the reader is positioned on a symbol table; otherwise, false.
*/
private boolean isPositionedOnSymbolTable() {
return hasAnnotations &&
super.getType() == IonType.STRUCT &&
startsWithIonSymbolTable();
}
@Override
public Event nextValue() {
Event event;
if (parent == null || state != State.READING_VALUE) {
while (true) {
if (state != State.READING_VALUE) {
symbolTableReader.readSymbolTable();
if (state != State.READING_VALUE) {
event = Event.NEEDS_DATA;
break;
}
}
event = super.nextValue();
if (parent == null && isPositionedOnSymbolTable()) {
cachedReadOnlySymbolTable = null;
symbolTableReader.resetState();
state = State.ON_SYMBOL_TABLE_STRUCT;
continue;
}
break;
}
} else {
event = super.nextValue();
}
return event;
}
@Override
public SymbolTable getSymbolTable() {
if (cachedReadOnlySymbolTable == null) {
if (localSymbolMaxOffset < 0 && imports == ION_1_0_IMPORTS) {
cachedReadOnlySymbolTable = imports.getSystemSymbolTable();
} else {
cachedReadOnlySymbolTable = new LocalSymbolTableSnapshot();
}
}
return cachedReadOnlySymbolTable;
}
@Override
public String stringValue() {
String value;
IonType type = super.getType();
if (type == IonType.STRING) {
value = super.stringValue();
} else if (type == IonType.SYMBOL) {
int sid = symbolValueId();
if (sid < 0) {
// The raw reader uses this to denote null.symbol.
return null;
}
value = getSymbol(sid);
if (value == null) {
throw new UnknownSymbolException(sid);
}
} else {
throw new IllegalStateException("Invalid type requested.");
}
return value;
}
@Override
public SymbolToken symbolValue() {
int sid = symbolValueId();
if (sid < 0) {
// The raw reader uses this to denote null.symbol.
return null;
}
return getSymbolToken(sid);
}
@Override
public String[] getTypeAnnotations() {
if (!hasAnnotations) {
return _Private_Utils.EMPTY_STRING_ARRAY;
}
IntList annotationSids = getAnnotationSidList();
String[] annotationArray = new String[annotationSids.size()];
for (int i = 0; i < annotationArray.length; i++) {
String symbol = getSymbol(annotationSids.get(i));
if (symbol == null) {
throw new UnknownSymbolException(annotationSids.get(i));
}
annotationArray[i] = symbol;
}
return annotationArray;
}
@Override
public SymbolToken[] getTypeAnnotationSymbols() {
if (!hasAnnotations) {
return SymbolToken.EMPTY_ARRAY;
}
IntList annotationSids = getAnnotationSidList();
SymbolToken[] annotationArray = new SymbolToken[annotationSids.size()];
for (int i = 0; i < annotationArray.length; i++) {
annotationArray[i] = getSymbolToken(annotationSids.get(i));
}
return annotationArray;
}
private static final Iterator EMPTY_ITERATOR = new Iterator() {
@Override
public boolean hasNext() {
return false;
}
@Override
public String next() {
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove from an empty iterator.");
}
};
@Override
public Iterator iterateTypeAnnotations() {
if (!hasAnnotations) {
return EMPTY_ITERATOR;
}
annotationIterator.reset();
return annotationIterator;
}
@Override
public String getFieldName() {
if (fieldSid < 0) {
return null;
}
String fieldName = getSymbol(fieldSid);
if (fieldName == null) {
throw new UnknownSymbolException(fieldSid);
}
return fieldName;
}
@Override
public SymbolToken getFieldNameSymbol() {
if (fieldSid < 0) {
return null;
}
return getSymbolToken(fieldSid);
}
}