IonTypeID.java
// 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.IonType;
/**
* Holds pre-computed information about a binary Ion type ID byte.
*/
final class IonTypeID {
private static final int NUMBER_OF_BYTES = 0x100;
private static final int BITS_PER_NIBBLE = 4;
private static final int LOW_NIBBLE_BITMASK = 0x0F;
private static final int NULL_VALUE_NIBBLE = 0xF;
private static final int VARIABLE_LENGTH_NIBBLE = 0xE;
private static final int NEGATIVE_INT_TYPE_CODE = 0x3;
private static final int TYPE_CODE_INVALID = 0xF;
private static final int ANNOTATION_WRAPPER_MIN_LENGTH = 0x3;
private static final int ANNOTATION_WRAPPER_MAX_LENGTH = 0xE;
static final int ORDERED_STRUCT_NIBBLE = 0x1;
// NOTE: 'annotation wrapper' is not an IonType, but it is simplest to treat it as one for the purposes of this
// implementation in order to have a direct mapping from binary type IDs to IonType enum values. IonType.DATAGRAM
// does not have a type ID, so we will use it to mean 'annotation wrapper' instead.
static final IonType ION_TYPE_ANNOTATION_WRAPPER = IonType.DATAGRAM;
// Lookup table from type ID to "binary token type", loosely represented by the IonType enum to avoid the need to
// define a completely new enum with translations between them. "Binary token types" are a superset of IonType,
// adding annotation wrapper and `null` (i.e., illegal).
// See https://amzn.github.io/ion-docs/docs/binary.html#typed-value-formats
static final IonType[] BINARY_TOKEN_TYPES_1_0 = new IonType[] {
IonType.NULL,
IonType.BOOL,
IonType.INT,
IonType.INT,
IonType.FLOAT,
IonType.DECIMAL,
IonType.TIMESTAMP,
IonType.SYMBOL,
IonType.STRING,
IonType.CLOB,
IonType.BLOB,
IonType.LIST,
IonType.SEXP,
IonType.STRUCT,
ION_TYPE_ANNOTATION_WRAPPER,
null // The 0xF type code is illegal in Ion 1.0.
};
// Singleton invalid type ID.
private static final IonTypeID ALWAYS_INVALID_TYPE_ID = new IonTypeID((byte) 0xFF, 0);
// Pre-compute all possible type ID bytes.
static final IonTypeID[] TYPE_IDS_NO_IVM;
static final IonTypeID[] TYPE_IDS_1_0;
static {
TYPE_IDS_NO_IVM = new IonTypeID[NUMBER_OF_BYTES];
TYPE_IDS_1_0 = new IonTypeID[NUMBER_OF_BYTES];
for (int b = 0x00; b < NUMBER_OF_BYTES; b++) {
TYPE_IDS_NO_IVM[b] = ALWAYS_INVALID_TYPE_ID;
TYPE_IDS_1_0[b] = new IonTypeID((byte) b, 0);
}
}
final IonType type;
final int length;
final boolean variableLength;
final boolean isNull;
final boolean isNopPad;
final byte lowerNibble;
final boolean isValid;
final boolean isNegativeInt;
final boolean isTemplateInvocation; // Unused in Ion 1.0
final int templateId; // Unused in Ion 1.0
final boolean isDelimited; // Unused in Ion 1.0
// For structs, denotes whether field names are VarSyms. For symbols, denotes whether the text is inline.
// For annotation wrappers, denotes whether tokens are VarSyms.
final boolean isInlineable; // Unused in Ion 1.0
/**
* Determines whether the Ion spec allows this particular upperNibble/lowerNibble pair.
*/
private static boolean isValid_1_0(byte upperNibble, byte lowerNibble, IonType type) {
if (upperNibble == TYPE_CODE_INVALID) {
// Type code F is unused in Ion 1.0.
return false;
}
if (type == IonType.BOOL) {
// Bool values can only be false (0), true (1), or null (F).
return lowerNibble <= 1 || lowerNibble == NULL_VALUE_NIBBLE;
}
if (type == IonType.INT && upperNibble == NEGATIVE_INT_TYPE_CODE) {
// There is no negative zero int.
return lowerNibble != 0;
}
if (type == IonType.FLOAT) {
// Floats are either 0e0 (0), 32-bit (4), 64-bit (8), or null (F).
return lowerNibble == 0 || lowerNibble == 4 || lowerNibble == 8 || lowerNibble == NULL_VALUE_NIBBLE;
}
if (type == IonType.TIMESTAMP) {
// There is no zero-length timestamp representation.
return lowerNibble > 1;
}
if (type == ION_TYPE_ANNOTATION_WRAPPER) {
return lowerNibble >= ANNOTATION_WRAPPER_MIN_LENGTH && lowerNibble <= ANNOTATION_WRAPPER_MAX_LENGTH;
}
return true;
}
private IonTypeID(byte id, int minorVersion) {
if (minorVersion == 0) {
byte upperNibble = (byte) ((id >> BITS_PER_NIBBLE) & LOW_NIBBLE_BITMASK);
this.lowerNibble = (byte) (id & LOW_NIBBLE_BITMASK);
if (upperNibble == 0 && lowerNibble != NULL_VALUE_NIBBLE) {
this.isNopPad = true;
this.type = null;
} else {
this.isNopPad = false;
this.type = BINARY_TOKEN_TYPES_1_0[upperNibble];
}
this.isValid = isValid_1_0(upperNibble, lowerNibble, type);
this.isNull = lowerNibble == NULL_VALUE_NIBBLE;
byte length = lowerNibble;
if (type == IonType.NULL || type == IonType.BOOL || !isValid) {
variableLength = false;
length = 0;
} else if (type == IonType.STRUCT && length == ORDERED_STRUCT_NIBBLE) {
variableLength = true;
} else {
variableLength = length == VARIABLE_LENGTH_NIBBLE;
}
if (isNull) {
length = 0;
}
this.isNegativeInt = type == IonType.INT && upperNibble == NEGATIVE_INT_TYPE_CODE;
this.length = length;
this.isTemplateInvocation = false;
this.templateId = -1;
this.isDelimited = false;
this.isInlineable = false;
} else {
throw new IllegalStateException("Only Ion 1.0 is currently supported.");
}
}
/**
* @return a String representation of this object (for debugging).
*/
@Override
public String toString() {
return String.format("%s(%s)", type, length);
}
}