com.amazon.ion.impl.IonReaderNonContinuableSystem 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.Decimal;
import com.amazon.ion.IntegerSize;
import com.amazon.ion.IonException;
import com.amazon.ion.IonReader;
import com.amazon.ion.IonCursor;
import com.amazon.ion.IonType;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.SymbolToken;
import com.amazon.ion.SystemSymbols;
import com.amazon.ion.Timestamp;
import com.amazon.ion.UnknownSymbolException;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayDeque;
import java.util.Date;
import java.util.Iterator;
import java.util.Queue;
import static com.amazon.ion.IonCursor.Event.NEEDS_DATA;
/**
* Provides a system-level view of an Ion stream. This differs from the application-level view in that system values
* (IVMs and symbol tables) are surfaced as if they were user values and symbols are not mapped, except for symbols
* that occur within the system symbol table. Because this is primarily intended to be used for debugging and is not
* exposed via public interfaces, it is not currently considered performance-critical, and no continuable equivalent
* is provided.
*/
final class IonReaderNonContinuableSystem implements IonReader {
private final IonReaderContinuableCore reader;
private IonType type = null;
private IonType typeAfterIvm = null;
private final Queue pendingIvmSids = new ArrayDeque<>(1);
private int pendingIvmSid = -1;
/**
* Constructs a new non-continuable system-level reader over the given continuable reader.
* @param reader the reader to wrap.
*/
IonReaderNonContinuableSystem(IonReaderContinuableCore reader) {
this.reader = reader;
reader.registerIvmNotificationConsumer((x, y) -> {
pendingIvmSids.add(SystemSymbols.ION_1_0_SID); // TODO generalize for Ion 1.1
});
}
@Override
public boolean hasNext() {
throw new UnsupportedOperationException("Not implemented -- use `next() != null`");
}
/**
* Consumes an IVM that was encountered before the next value, presenting the IVM as a symbol value. If an IVM
* was emitted on the last invocation, restores the next value to be presented to the user.
* @return true if a value is ready to be presented to the user; otherwise, false.
*/
private boolean handleIvm() {
Integer ivmSid = pendingIvmSids.poll();
if (ivmSid != null) {
// An IVM has been found between values.
if (typeAfterIvm == null) {
// Only save the type of the next user value the first time an IVM is encountered before that value.
typeAfterIvm = type;
}
// For consistency with the legacy implementation, the system reader surfaces IVMs as symbol values.
type = IonType.SYMBOL;
pendingIvmSid = ivmSid;
return true;
} else if (pendingIvmSid != -1) {
// All preceding IVMs have been surfaced. Restore the value that follows.
pendingIvmSid = -1;
type = typeAfterIvm;
typeAfterIvm = null;
return true;
}
return false;
}
@Override
public IonType next() {
if (handleIvm()) {
// This happens if there were multiple IVMs between top-level values. They will be drained from the queue
// one-by-one on each call to next().
return type;
}
if (reader.nextValue() == NEEDS_DATA) {
if (handleIvm()) {
// This happens if one or more IVMs occurs before the end of the stream.
return type;
}
reader.endStream();
type = null;
} else {
type = reader.getType();
handleIvm(); // Handles the case where one or more IVMs occurred before the value.
}
return type;
}
@Override
public void stepIn() {
reader.stepIntoContainer();
type = null;
}
@Override
public void stepOut() {
reader.stepOutOfContainer();
type = null;
}
@Override
public int getDepth() {
return reader.getDepth();
}
@Override
public IonType getType() {
return type;
}
/**
* Prepares a scalar value to be parsed by ensuring it is present in the buffer.
*/
private void prepareScalar() {
IonCursor.Event event = reader.getCurrentEvent();
if (event == IonCursor.Event.VALUE_READY) {
return;
}
if (event != IonCursor.Event.START_SCALAR) {
// Note: existing tests expect IllegalStateException in this case.
throw new IllegalStateException("Reader is not positioned on a scalar value.");
}
if (reader.fillValue() != IonCursor.Event.VALUE_READY) {
throw new IonException("Unexpected EOF.");
}
}
@Override
public IntegerSize getIntegerSize() {
if (getType() != IonType.INT) {
return null;
}
prepareScalar();
return reader.getIntegerSize();
}
@Override
public boolean isNullValue() {
return pendingIvmSid == -1 && reader.isNullValue();
}
@Override
public boolean isInStruct() {
return reader.isInStruct();
}
@Override
public boolean booleanValue() {
prepareScalar();
return reader.booleanValue();
}
@Override
public int intValue() {
prepareScalar();
return reader.intValue();
}
@Override
public long longValue() {
prepareScalar();
return reader.longValue();
}
@Override
public BigInteger bigIntegerValue() {
prepareScalar();
return reader.bigIntegerValue();
}
@Override
public double doubleValue() {
prepareScalar();
return reader.doubleValue();
}
@Override
public BigDecimal bigDecimalValue() {
prepareScalar();
return reader.bigDecimalValue();
}
@Override
public Decimal decimalValue() {
prepareScalar();
return reader.decimalValue();
}
@Override
public Date dateValue() {
prepareScalar();
return reader.dateValue();
}
@Override
public Timestamp timestampValue() {
prepareScalar();
return reader.timestampValue();
}
@Override
public String stringValue() {
if (pendingIvmSid != -1) {
return getSymbolTable().findKnownSymbol(pendingIvmSid);
}
prepareScalar();
String value;
if (type == IonType.SYMBOL) {
int sid = reader.symbolValueId();
value = getSymbolTable().findKnownSymbol(sid);
if (value == null) {
throw new UnknownSymbolException(sid);
}
} else {
value = reader.stringValue();
}
return value;
}
@Override
public int byteSize() {
prepareScalar();
return reader.byteSize();
}
@Override
public byte[] newBytes() {
prepareScalar();
return reader.newBytes();
}
@Override
public int getBytes(byte[] buffer, int offset, int len) {
prepareScalar();
return reader.getBytes(buffer, offset, len);
}
@Override
public T asFacet(Class facetType) {
return null; // The system-level reader has no facets.
}
@Override
public SymbolTable getSymbolTable() {
// TODO generalize for Ion 1.1
return SharedSymbolTable.getSystemSymbolTable(reader.getIonMajorVersion());
}
@Override
public String[] getTypeAnnotations() {
if (pendingIvmSid != -1 || !reader.hasAnnotations()) {
return _Private_Utils.EMPTY_STRING_ARRAY;
}
int[] annotationIds = reader.getAnnotationIds();
String[] annotations = new String[annotationIds.length];
SymbolTable symbolTable = getSymbolTable();
for (int i = 0; i < annotationIds.length; i++) {
int sid = annotationIds[i];
String annotation = symbolTable.findKnownSymbol(sid);
if (annotation == null) {
throw new UnknownSymbolException(sid);
}
annotations[i] = annotation;
}
return annotations;
}
@Override
public SymbolToken[] getTypeAnnotationSymbols() {
if (pendingIvmSid != -1 || !reader.hasAnnotations()) {
return SymbolToken.EMPTY_ARRAY;
}
int[] annotationIds = reader.getAnnotationIds();
SymbolToken[] annotationSymbolTokens = new SymbolToken[annotationIds.length];
SymbolTable symbolTable = getSymbolTable();
for (int i = 0; i < annotationIds.length; i++) {
int sid = annotationIds[i];
annotationSymbolTokens[i] = new SymbolTokenImpl(symbolTable.findKnownSymbol(sid), sid);
}
return annotationSymbolTokens;
}
@Override
public Iterator iterateTypeAnnotations() {
if (pendingIvmSid != -1 || !reader.hasAnnotations()) {
return _Private_Utils.emptyIterator();
}
return _Private_Utils.stringIterator(getTypeAnnotations());
}
@Override
public int getFieldId() {
return reader.getFieldId();
}
@Override
public String getFieldName() {
int sid = reader.getFieldId();
if (sid < 0) {
return null;
}
String name = getSymbolTable().findKnownSymbol(sid);
if (name == null) {
throw new UnknownSymbolException(sid);
}
return name;
}
@Override
public SymbolToken getFieldNameSymbol() {
int sid = reader.getFieldId();
if (sid < 0) {
return null;
}
return new SymbolTokenImpl(getSymbolTable().findKnownSymbol(sid), sid);
}
@Override
public SymbolToken symbolValue() {
int sid;
if (pendingIvmSid != -1) {
sid = pendingIvmSid;
} else {
prepareScalar();
sid = reader.symbolValueId();
}
return new SymbolTokenImpl(getSymbolTable().findKnownSymbol(sid), sid);
}
@Override
public void close() throws IOException {
reader.close();
}
}