com.amazon.ion.impl._Private_Utils 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.SystemSymbols.IMPORTS;
import static com.amazon.ion.SystemSymbols.IMPORTS_SID;
import static com.amazon.ion.SystemSymbols.ION_SYMBOL_TABLE;
import static com.amazon.ion.SystemSymbols.MAX_ID;
import static com.amazon.ion.SystemSymbols.MAX_ID_SID;
import static com.amazon.ion.SystemSymbols.NAME;
import static com.amazon.ion.SystemSymbols.NAME_SID;
import static com.amazon.ion.SystemSymbols.SYMBOLS;
import static com.amazon.ion.SystemSymbols.SYMBOLS_SID;
import static com.amazon.ion.SystemSymbols.VERSION;
import static com.amazon.ion.SystemSymbols.VERSION_SID;
import static com.amazon.ion.util.IonStreamUtils.isIonBinary;
import com.amazon.ion.IonException;
import com.amazon.ion.IonReader;
import com.amazon.ion.IonStruct;
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonType;
import com.amazon.ion.IonValue;
import com.amazon.ion.SubstituteSymbolTableException;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.SymbolToken;
import com.amazon.ion.UnknownSymbolException;
import com.amazon.ion.ValueFactory;
import com.amazon.ion.impl.IonBinary.BufferManager;
import com.amazon.ion.impl.IonBinary.Reader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.TimeZone;
/**
* NOT FOR APPLICATION USE!
*/
public final class _Private_Utils
{
/**
* Marker for code points relevant to removal of IonReader.hasNext().
*/
public static final boolean READER_HASNEXT_REMOVED = true;
/** Just a zero-length byte array, used to avoid allocation. */
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/** Just a zero-length String array, used to avoid allocation. */
public final static String[] EMPTY_STRING_ARRAY = new String[0];
/** Just a zero-length int array, used to avoid allocation. */
public final static int[] EMPTY_INT_ARRAY = new int[0];
/**
* (null.timestamp) requires 11 ASCII chars to distinguish from
* (null.timestamps) aka (null '.' 'timestamps')
*
* @see IonCharacterReader#DEFAULT_BUFFER_SIZE
* @see IonCharacterReader#BUFFER_PADDING
*/
public static final int MAX_LOOKAHEAD_UTF16 = 11;
public static final String ASCII_CHARSET_NAME = "US-ASCII";
public static final Charset ASCII_CHARSET =
Charset.forName(ASCII_CHARSET_NAME);
/** The string {@code "UTF-8"}. */
public static final String UTF8_CHARSET_NAME = "UTF-8";
public static final Charset UTF8_CHARSET =
Charset.forName(UTF8_CHARSET_NAME);
/**
* The UTC {@link TimeZone}.
*
* TODO determine if this is well-defined.
*/
public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
public static final ListIterator> EMPTY_ITERATOR = new ListIterator() {
public boolean hasNext() { return false; }
public boolean hasPrevious() { return false; }
public Object next() { throw new NoSuchElementException(); }
public Object previous() { throw new NoSuchElementException(); }
public void remove() { throw new IllegalStateException(); }
public int nextIndex() { return 0; }
public int previousIndex() { return -1; }
public void add(Object o) { throw new UnsupportedOperationException(); }
public void set(Object o) { throw new UnsupportedOperationException(); }
};
@SuppressWarnings("unchecked")
public static final ListIterator emptyIterator()
{
return (ListIterator) EMPTY_ITERATOR;
}
public static boolean safeEquals(Object a, Object b)
{
// Written for the common case where they are not the same instance
return (a != null ? a.equals(b) : b == null);
}
/**
* Replacement for Java6 {@link Arrays#copyOf(byte[], int)}.
*/
public static byte[] copyOf(byte[] original, int newLength)
{
byte[] result = new byte[newLength];
System.arraycopy(original, 0, result, 0,
Math.min(newLength, original.length));
return result;
}
public static String[] copyOf(String[] original, int newLength)
{
String[] result = new String[newLength];
System.arraycopy(original, 0, result, 0,
Math.min(newLength, original.length));
return result;
}
public static void addAll(Collection dest, Iterator src)
{
if (src != null)
{
while (src.hasNext())
{
T value = src.next();
dest.add(value);
}
}
}
public static void addAllNonNull(Collection dest, Iterator src)
{
if (src != null)
{
while (src.hasNext())
{
T value = src.next();
if (value != null)
{
dest.add(value);
}
}
}
}
/**
* @return not null
*/
public static SymbolTokenImpl newSymbolToken(String text, int sid)
{
return new SymbolTokenImpl(text, sid);
}
/**
* @return not null
*/
public static SymbolTokenImpl newSymbolToken(int sid)
{
return new SymbolTokenImpl(sid);
}
/**
* Checks symbol content.
* @return not null
*/
public static SymbolToken newSymbolToken(SymbolTable symtab,
String text)
{
// TODO amazon-ion/ion-java/issues/21 symtab should not be null
text.getClass(); // quick null check
SymbolToken tok = (symtab == null ? null : symtab.find(text));
if (tok == null)
{
tok = new SymbolTokenImpl(text, UNKNOWN_SYMBOL_ID);
}
return tok;
}
/**
* @return not null
*/
public static SymbolToken newSymbolToken(SymbolTable symtab,
int sid)
{
if (sid < 1) throw new IllegalArgumentException();
// TODO amazon-ion/ion-java/issues/21 symtab should not be null
String text = (symtab == null ? null : symtab.findKnownSymbol(sid));
return new SymbolTokenImpl(text, sid);
}
/**
* Validates each text element.
* @param text may be null or empty.
* @return not null.
*/
public static SymbolToken[] newSymbolTokens(SymbolTable symtab,
String... text)
{
if (text != null)
{
int count = text.length;
if (count != 0)
{
SymbolToken[] result = new SymbolToken[count];
for (int i = 0; i < count; i++)
{
String s = text[i];
result[i] = newSymbolToken(symtab, s);
}
return result;
}
}
return SymbolToken.EMPTY_ARRAY;
}
/**
* @param syms may be null or empty.
* @return not null.
*/
public static SymbolToken[] newSymbolTokens(SymbolTable symtab,
int... syms)
{
if (syms != null)
{
int count = syms.length;
if (syms.length != 0)
{
SymbolToken[] result = new SymbolToken[count];
for (int i = 0; i < count; i++)
{
int s = syms[i];
result[i] = newSymbolToken(symtab, s);
}
return result;
}
}
return SymbolToken.EMPTY_ARRAY;
}
public static SymbolToken localize(SymbolTable symtab,
SymbolToken sym)
{
String text = sym.getText();
int sid = sym.getSid();
if (symtab != null) // TODO amazon-ion/ion-java/issues/21 require symtab
{
if (text == null)
{
text = symtab.findKnownSymbol(sid);
if (text != null)
{
sym = new SymbolTokenImpl(text, sid);
}
}
else
{
SymbolToken newSym = symtab.find(text);
if (newSym != null)
{
sym = newSym;
}
else if (sid >= 0)
{
// We can't trust the sid, discard it.
sym = new SymbolTokenImpl(text, UNKNOWN_SYMBOL_ID);
}
}
}
else if (text != null && sid >= 0)
{
// We can't trust the sid, discard it.
sym = new SymbolTokenImpl(text, UNKNOWN_SYMBOL_ID);
}
return sym;
}
/**
*
* @param syms may be mutated, replacing entries with localized updates!
*/
public static void localize(SymbolTable symtab,
SymbolToken[] syms,
int count)
{
for (int i = 0; i < count; i++)
{
SymbolToken sym = syms[i];
SymbolToken updated = localize(symtab, sym);
if (updated != sym) syms[i] = updated;
}
}
/**
*
* @param syms may be mutated, replacing entries with localized updates!
*/
public static void localize(SymbolTable symtab,
SymbolToken[] syms)
{
localize(symtab, syms, syms.length);
}
/**
* Extracts the non-null text from a list of symbol tokens.
*
* @return not null.
*
* @throws UnknownSymbolException if any token is missing text.
*/
public static String[] toStrings(SymbolToken[] symbols, int count)
{
if (count == 0) return _Private_Utils.EMPTY_STRING_ARRAY;
String[] annotations = new String[count];
for (int i = 0; i < count; i++)
{
SymbolToken tok = symbols[i];
String text = tok.getText();
if (text == null)
{
throw new UnknownSymbolException(tok.getSid());
}
annotations[i] = text;
}
return annotations;
}
public static int[] toSids(SymbolToken[] symbols, int count)
{
if (count == 0) return _Private_Utils.EMPTY_INT_ARRAY;
int[] sids = new int[count];
for (int i = 0; i < count; i++)
{
sids[i] = symbols[i].getSid();
}
return sids;
}
//========================================================================
/**
* Encodes a String into bytes of a given encoding.
*
* This method is preferred to {@link Charset#encode(String)} and
* {@link String#getBytes(String)} (etc.)
* since those methods will replace or ignore bad input, and here we throw
* an exception.
*
* @param s the string to encode.
*
* @return the encoded string, not null.
*
* @throws IonException if there's a {@link CharacterCodingException}.
*/
public static byte[] encode(String s, Charset charset)
{
CharsetEncoder encoder = charset.newEncoder();
try
{
ByteBuffer buffer = encoder.encode(CharBuffer.wrap(s));
byte[] bytes = buffer.array();
// Make another copy iff there's garbage after the limit.
int limit = buffer.limit();
if (limit < bytes.length)
{
bytes = copyOf(bytes, limit);
}
return bytes;
}
catch (CharacterCodingException e)
{
throw new IonException("Invalid string data", e);
}
}
/**
* Decodes a byte sequence into a string, given a {@link Charset}.
*
* This method is preferred to {@link Charset#decode(ByteBuffer)} and
* {@link String#String(byte[], Charset)} (etc.)
* since those methods will replace or ignore bad input, and here we throw
* an exception.
*
* @param bytes the data to decode.
*
* @return the decoded string, not null.
*
* @throws IonException if there's a {@link CharacterCodingException}.
*/
public static String decode(byte[] bytes, Charset charset)
{
CharsetDecoder decoder = charset.newDecoder();
try
{
CharBuffer buffer = decoder.decode(ByteBuffer.wrap(bytes));
return buffer.toString();
}
catch (CharacterCodingException e)
{
String message =
"Input is not valid " + charset.displayName() + " data";
throw new IonException(message, e);
}
}
/**
* Encodes a String into UTF-8 bytes.
*
* This method is preferred to {@link Charset#encode(String)} and
* {@link String#getBytes(String)} (etc.)
* since those methods will replace or ignore bad input, and here we throw
* an exception.
*
* @param s the string to encode.
*
* @return the encoded UTF-8 bytes, not null.
*
* @throws IonException if there's a {@link CharacterCodingException}.
*/
public static byte[] utf8(String s)
{
return encode(s, UTF8_CHARSET);
}
/**
* Decodes a UTF-8 byte sequence to a String.
*
* This method is preferred to {@link Charset#decode(ByteBuffer)} and
* {@link String#String(byte[], Charset)} (etc.)
* since those methods will replace or ignore bad input, and here we throw
* an exception.
*
* @param bytes the data to decode.
*
* @return the decoded string, not null.
*
* @throws IonException if there's a {@link CharacterCodingException}.
*/
public static String utf8(byte[] bytes)
{
return decode(bytes, UTF8_CHARSET);
}
/**
* This differs from {@link #utf8(String)} by using our custem encoder.
* Not sure which is better.
* TODO benchmark the two approaches
*/
public static byte[] convertUtf16UnitsToUtf8(String text)
{
byte[] data = new byte[4*text.length()];
int limit = 0;
for (int i = 0; i < text.length(); i++)
{
char c = text.charAt(i);
limit += IonUTF8.convertToUTF8Bytes(c, data, limit,
data.length - limit);
}
byte[] result = new byte[limit];
System.arraycopy(data, 0, result, 0, limit);
return result;
}
//========================================================================
/**
* Calls {@link InputStream#read(byte[], int, int)} until the buffer is
* filled or EOF is encountered.
* This method will block until the request is satisfied.
*
* @param in The stream to read from.
* @param buf The buffer to read to.
*
* @return the number of bytes read from the stream. May be less than
* {@code buf.length} if EOF is encountered before reading that far.
*
* @see #readFully(InputStream, byte[], int, int)
*/
public static int readFully(InputStream in, byte[] buf)
throws IOException
{
return readFully(in, buf, 0, buf.length);
}
/**
* Calls {@link InputStream#read(byte[], int, int)} until the requested
* length is read or EOF is encountered.
* This method will block until the request is satisfied.
*
* @param in The stream to read from.
* @param buf The buffer to read to.
* @param offset The offset of the buffer to read from.
* @param length The length of the data to read.
*
* @return the number of bytes read from the stream. May be less than
* {@code length} if EOF is encountered before reading that far.
*
* @see #readFully(InputStream, byte[])
*/
public static int readFully(InputStream in, byte[] buf,
int offset, int length)
throws IOException
{
int readBytes = 0;
while (readBytes < length)
{
int amount = in.read(buf, offset, length - readBytes);
if (amount < 0)
{
// EOF
return readBytes;
}
readBytes += amount;
offset += amount;
}
return readBytes;
}
public static byte[] loadFileBytes(File file)
throws IOException
{
long len = file.length();
if (len < 0 || len > Integer.MAX_VALUE) {
throw new IllegalArgumentException("File too long: " + file);
}
byte[] buf = new byte[(int) len];
FileInputStream in = new FileInputStream(file);
try {
int readBytesCount = in.read(buf);
if (readBytesCount != len || in.read() != -1)
{
throw new IOException("Read the wrong number of bytes from "
+ file);
}
}
finally {
in.close();
}
return buf;
}
public static String utf8FileToString(File file)
throws IonException, IOException
{
byte[] utf8Bytes = _Private_Utils.loadFileBytes(file);
String s = utf8(utf8Bytes);
return s;
}
public static byte[] loadStreamBytes(InputStream in)
throws IOException
{
BufferManager buffer = new BufferManager(in);
Reader bufReader = buffer.reader();
bufReader.sync();
bufReader.setPosition(0);
byte[] bytes = bufReader.getBytes();
return bytes;
}
public static String loadReader(java.io.Reader in)
throws IOException
{
StringBuilder buf = new StringBuilder(2048);
char[] chars = new char[2048];
int len;
while ((len = in.read(chars)) != -1)
{
buf.append(chars, 0, len);
}
return buf.toString();
}
public static boolean streamIsIonBinary(PushbackInputStream pushback)
throws IonException, IOException
{
boolean isBinary = false;
byte[] cookie = new byte[_Private_IonConstants.BINARY_VERSION_MARKER_SIZE];
int len = readFully(pushback, cookie);
if (len == _Private_IonConstants.BINARY_VERSION_MARKER_SIZE) {
isBinary = isIonBinary(cookie);
}
if (len > 0) {
pushback.unread(cookie, 0, len);
}
return isBinary;
}
/**
* Create a value iterator from a reader.
* Primarily a trampoline for access permission.
*/
public static Iterator iterate(ValueFactory valueFactory,
IonReader input)
{
return new IonIteratorImpl(valueFactory, input);
}
//========================================================================
// Symbol Table helpers
/**
* Checks the passed in value and returns whether or not
* the value could be a local symbol table. It does this
* by checking the type and annotations.
*
* @return boolean true if v can be a local symbol table otherwise false
*/
public static boolean valueIsLocalSymbolTable(_Private_IonValue v)
{
return (v instanceof IonStruct
&& v.findTypeAnnotation(ION_SYMBOL_TABLE) == 0);
}
/** Indicates whether a table is shared but not a system table. */
public static final boolean symtabIsSharedNotSystem(SymbolTable symtab)
{
return (symtab != null
&& symtab.isSharedTable()
&& ! symtab.isSystemTable());
}
public static boolean symtabIsLocalAndNonTrivial(SymbolTable symtab)
{
if (symtab == null) return false;
if (!symtab.isLocalTable()) return false;
// If symtab has imports we must retain it.
// Note that I chose to retain imports even in the degenerate case
// where the imports have no symbols.
if (symtab.getImportedTables().length > 0) {
return true;
}
if (symtab.getImportedMaxId() < symtab.getMaxId()) {
return true;
}
return false;
}
/**
* Is the table null, system, or local without imported symbols?
*/
public static boolean isTrivialTable(SymbolTable table)
{
if (table == null) return true;
if (table.isSystemTable()) return true;
if (table.isLocalTable()) {
// this is only true when there are no local
// symbols defined
// and there are no imports with any symbols
if (table.getMaxId() == table.getSystemSymbolTable().getMaxId()) {
return true;
}
}
return false;
}
public static SymbolTable systemSymtab(int version)
{
return SharedSymbolTable.getSystemSymbolTable(version);
}
public static SymbolTable newSharedSymtab(IonStruct ionRep)
{
return SharedSymbolTable.newSharedSymbolTable(ionRep);
}
public static SymbolTable newSharedSymtab(IonReader reader,
boolean alreadyInStruct)
{
return SharedSymbolTable.newSharedSymbolTable(reader, alreadyInStruct);
}
/**
* As per {@link IonSystem#newSharedSymbolTable(String, int, Iterator, SymbolTable...)},
* any duplicate or null symbol texts are skipped.
* Therefore, THIS METHOD IS NOT SUITABLE WHEN READING SERIALIZED
* SHARED SYMBOL TABLES since that scenario must preserve all sids.
*
* @param priorSymtab may be null.
*/
public static SymbolTable newSharedSymtab(String name,
int version,
SymbolTable priorSymtab,
Iterator symbols)
{
return SharedSymbolTable.newSharedSymbolTable(name,
version,
priorSymtab,
symbols);
}
/**
* Creates a mutable copy of this local symbol table. The cloned table
* will be created in the context of the same {@link ValueFactory}.
*
* Note that the resulting symbol table holds a distinct, deep copy of the
* given table, adding symbols on either instances will not modify the
* other.
*
* @param symtab
*
* @return a new mutable {@link SymbolTable} instance; not null
*
* @throws IllegalArgumentException
* if the given table is not a local symbol table
* @throws SubstituteSymbolTableException
* if any imported table by the given local symbol table is a
* substituted table (whereby no exact match was found in its
* catalog)
*/
// TODO We need to think about providing a suitable recovery process
// or configuration for users to properly handle the case when the
// local symtab has substituted symtabs for imports.
public static SymbolTable copyLocalSymbolTable(SymbolTable symtab)
throws SubstituteSymbolTableException
{
if (! symtab.isLocalTable())
{
String message = "symtab should be a local symtab";
throw new IllegalArgumentException(message);
}
SymbolTable[] imports =
((LocalSymbolTable) symtab).getImportedTablesNoCopy();
// Iterate over each import, we assume that the list of imports
// rarely exceeds 5.
for (int i = 0; i < imports.length; i++)
{
if (imports[i].isSubstitute())
{
String message =
"local symtabs with substituted symtabs for imports " +
"(indicating no exact match within the catalog) cannot " +
"be copied";
throw new SubstituteSymbolTableException(message);
}
}
return ((LocalSymbolTable) symtab).makeCopy();
}
/**
* Trampoline to {@link LocalSymbolTableAsStruct.Factory#Factory(ValueFactory)}
* @param imageFactory
* the ValueFactory from which to construct the IonStruct representation of the LST
* @return a new {@link LocalSymbolTableAsStruct.Factory}
* @deprecated due to DOM entanglement. Streaming applications should use
* {@value LocalSymbolTable#DEFAULT_LST_FACTORY}.
*/
@Deprecated
public static _Private_LocalSymbolTableFactory newLocalSymbolTableAsStructFactory(ValueFactory imageFactory)
{
return new LocalSymbolTableAsStruct.Factory(imageFactory);
}
/**
* Returns a minimal symtab that, either system or local depending on the
* given values, that supports representation as an IonStruct. If the
* imports are empty, the default system symtab is returned.
*
* @param lstFactory
* the factory to use to build the local symbol table, never null
* @param defaultSystemSymtab
* the default system symtab, which will be used if the first
* import in {@code imports} isn't a system symtab, never null
* @param imports
* the set of shared symbol tables to import; may be null or empty.
* The first (and only the first) may be a system table, in which case the
* {@code defaultSystemSymtab} is ignored.
*/
public static SymbolTable initialSymtab(_Private_LocalSymbolTableFactory lstFactory,
SymbolTable defaultSystemSymtab,
SymbolTable... imports)
{
if (imports == null || imports.length == 0)
{
return defaultSystemSymtab;
}
if (imports.length == 1 && imports[0].isSystemTable())
{
return imports[0];
}
return lstFactory.newLocalSymtab(defaultSystemSymtab, imports);
}
@SuppressWarnings("deprecation")
/**
* Trampoline to
* {@link LocalSymbolTableAsStruct#getIonRepresentation()};
*/
public static IonStruct symtabTree(SymbolTable symtab, ValueFactory valueFactory)
{
SymbolTableAsStruct localSymbolTableAsStruct;
if (symtab instanceof SymbolTableAsStruct) {
localSymbolTableAsStruct = (SymbolTableAsStruct) symtab;
} else {
LocalSymbolTableAsStruct table =
(LocalSymbolTableAsStruct) new LocalSymbolTableAsStruct.Factory(valueFactory)
.newLocalSymtab(symtab.getSystemSymbolTable(), symtab.getImportedTables());
Iterator localSymbolsIterator = symtab.iterateDeclaredSymbolNames();
while (localSymbolsIterator.hasNext()) {
String localSymbol = localSymbolsIterator.next();
if (localSymbol != null) {
table.intern(localSymbol);
}
}
localSymbolTableAsStruct = table;
}
return localSymbolTableAsStruct.getIonRepresentation(valueFactory);
}
/**
* Determines, for two local symbol tables, whether the passed-in {@code superset} symtab is an extension
* of {@code subset}. This works independent of implementation details--particularly in cases
* where {@link LocalSymbolTable#symtabExtends(SymbolTable)} cannot be used.
*
* @see #symtabExtends(SymbolTable, SymbolTable)
*/
private static boolean localSymtabExtends(SymbolTable superset, SymbolTable subset)
{
if (subset.getMaxId() > superset.getMaxId())
{
// the subset has more symbols
return false;
}
// NB this API almost certainly requires cloning--symbol table's API doesn't give us a way to polymorphically
// get this without materializing an array
final SymbolTable[] supersetImports = superset.getImportedTables();
final SymbolTable[] subsetImports = subset.getImportedTables();
// TODO this is over-strict, but not as strict as LocalSymbolTable.symtabExtends()
if (supersetImports.length != subsetImports.length)
{
return false;
}
// NB we cannot trust Arrays.equals--we don't know how an implementation will implement it...
for (int i = 0; i < supersetImports.length; i++)
{
final SymbolTable supersetImport = supersetImports[i];
final SymbolTable subsetImport = subsetImports[i];
if (!supersetImport.getName().equals(subsetImport.getName())
|| supersetImport.getVersion() != subsetImport.getVersion())
{
// bad match on import
return false;
}
}
// all the imports lined up, lets make sure the locals line up too
final Iterator supersetIter = superset.iterateDeclaredSymbolNames();
final Iterator subsetIter = subset.iterateDeclaredSymbolNames();
while (subsetIter.hasNext())
{
final String nextSubsetSymbol = subsetIter.next();
final String nextSupersetSymbol = supersetIter.next();
if (!nextSubsetSymbol.equals(nextSupersetSymbol))
{
// local symbol mismatch
return false;
}
}
// we made it this far--superset is really a superset of subset
return true;
}
/**
* Determines whether the passed-in {@code superset} symtab is an extension
* of {@code subset}.
*
* If both are LSTs, their imported tables and locally declared symbols are
* exhaustively checked, which can be expensive. Callers of this method
* should cache the results of these comparisons.
*
* @param superset
* either a system or local symbol table
* @param subset
* either a system or local symbol table
*
* @return true if {@code superset} extends {@code subset}, false if not
*/
public static boolean symtabExtends(SymbolTable superset, SymbolTable subset)
{
assert superset.isSystemTable() || superset.isLocalTable();
assert subset.isSystemTable() || subset.isLocalTable();
// NB: system symtab 1.0 is a singleton, hence if both symtabs
// are one this will be true.
if (superset == subset) return true;
// If the subset's symtab is a system symtab, the superset's is always
// an extension of the subset's as system symtab-ness is irrelevant to
// the conditions for copy opt. to be safe.
// TODO amazon-ion/ion-java/issues/24 System symtab-ness ARE relevant if there's multiple
// versions.
if (subset.isSystemTable()) return true;
// From here on, subset is a LST because isSystemTable() is false.
if (superset.isLocalTable())
{
if (superset instanceof LocalSymbolTable && subset instanceof LocalSymbolTable)
{
// use the internal comparison
return ((LocalSymbolTable) superset).symtabExtends(subset);
}
// TODO reason about symbol tables that don't extend LocalSymbolTable but are still local
return localSymtabExtends(superset, subset);
}
// From here on, superset is a system symtab.
// If LST subset has no local symbols or imports, and it's system
// symbols are same as those of system symtab superset's, then
// superset extends subset
return subset.getMaxId() == superset.getMaxId();
}
/**
* Determines whether the passed-in data type is a scalar and not a symbol.
*/
public static boolean isNonSymbolScalar(IonType type)
{
return ! IonType.isContainer(type) && ! type.equals(IonType.SYMBOL);
}
/**
* Returns the symbol ID matching a system symbol text of a
* local or shared symtab field.
*/
public static final int getSidForSymbolTableField(String text)
{
final int shortestFieldNameLength = 4; // 'name'
if (text != null && text.length() >= shortestFieldNameLength)
{
int c = text.charAt(0);
switch (c)
{
case 'v':
if (VERSION.equals(text))
{
return VERSION_SID;
}
break;
case 'n':
if (NAME.equals(text))
{
return NAME_SID;
}
break;
case 's':
if (SYMBOLS.equals(text))
{
return SYMBOLS_SID;
}
break;
case 'i':
if (IMPORTS.equals(text))
{
return IMPORTS_SID;
}
break;
case 'm':
if (MAX_ID.equals(text))
{
return MAX_ID_SID;
}
break;
default:
break;
}
}
return UNKNOWN_SYMBOL_ID;
}
//========================================================================
/**
* Private to route clients through the static methods, which can
* optimize the empty-list case.
*/
private static final class StringIterator implements Iterator
{
private final String[] _values;
private int _pos;
private final int _len;
StringIterator(String[] values, int len) {
_values = values;
_len = len;
}
public boolean hasNext() {
return (_pos < _len);
}
public String next() {
if (!hasNext()) throw new NoSuchElementException();
return _values[_pos++];
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public static final Iterator stringIterator(String... values)
{
if (values == null || values.length == 0)
{
return _Private_Utils.emptyIterator();
}
return new StringIterator(values, values.length);
}
public static final Iterator stringIterator(String[] values, int len)
{
if (values == null || values.length == 0 || len == 0)
{
return _Private_Utils.emptyIterator();
}
return new StringIterator(values, len);
}
/**
* Private to route clients through the static methods, which can
* optimize the empty-list case.
*/
private static final class IntIterator implements Iterator
{
private final int [] _values;
private int _pos;
private final int _len;
IntIterator(int[] values) {
this(values, 0, values.length);
}
IntIterator(int[] values, int off, int len) {
_values = values;
_len = len;
_pos = off;
}
public boolean hasNext() {
return (_pos < _len);
}
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
int value = _values[_pos++];
return value;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public static final Iterator intIterator(int... values)
{
if (values == null || values.length == 0)
{
return _Private_Utils.emptyIterator();
}
return new IntIterator(values);
}
public static final Iterator intIterator(int[] values, int len)
{
if (values == null || values.length == 0 || len == 0)
{
return _Private_Utils.emptyIterator();
}
return new IntIterator(values, 0, len);
}
public static void writeAsBase64(InputStream byteStream, Appendable out)
throws IOException
{
Base64Encoder.TextStream ts = new Base64Encoder.TextStream(byteStream);
for (;;) {
int c = ts.read();
if (c == -1) break;
out.append((char) c);
}
}
public static SymbolTable newSubstituteSymtab(SymbolTable original,
int version,
int maxId)
{
return new SubstituteSymbolTable(original, version, maxId);
}
}