content: /*
   * 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 <T> ListIterator<T> emptyIterator()
      {
          return (ListIterator<T>) 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 <T> void addAll(Collection<T> dest, Iterator<T> src)
      {
          if (src != null)
          {
              while (src.hasNext())
              {
                  T value = src.next();
                  dest.add(value);
              }
          }
      }
  
      public static <T> void addAllNonNull(Collection<T> dest, Iterator<T> 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.
       * <p>
       * This method is preferred to {@link Charset#encode(String)} and
       * {@link String#getBytes(String)} (<em>etc.</em>)
       * 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}.
       * <p>
       * This method is preferred to {@link Charset#decode(ByteBuffer)} and
       * {@link String#String(byte[], Charset)} (<em>etc.</em>)
       * 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.
       * <p>
       * This method is preferred to {@link Charset#encode(String)} and
       * {@link String#getBytes(String)} (<em>etc.</em>)
       * 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.
       * <p>
       * This method is preferred to {@link Charset#decode(ByteBuffer)} and
       * {@link String#String(byte[], Charset)} (<em>etc.</em>)
       * 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 > _Private_IonConstants.ARRAY_MAXIMUM_SIZE) {
              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<IonValue> 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, <b>THIS METHOD IS NOT SUITABLE WHEN READING SERIALIZED
       * SHARED SYMBOL TABLES</b> since that scenario must preserve all sids.
       *
       * @param priorSymtab may be null.
       */
      public static SymbolTable newSharedSymtab(String name,
                                                int version,
                                                SymbolTable priorSymtab,
                                                Iterator<String> 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}.
       * <p>
       * 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 =
              ((_Private_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 ((_Private_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<String> 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<String> supersetIter     = superset.iterateDeclaredSymbolNames();
          final Iterator<String> 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}.
       * <p>
       * 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<String>
      {
          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<String> stringIterator(String... values)
      {
          if (values == null || values.length == 0)
          {
              return _Private_Utils.<String>emptyIterator();
          }
          return new StringIterator(values, values.length);
      }
  
      public static final Iterator<String> stringIterator(String[] values, int len)
      {
          if (values == null || values.length == 0 || len == 0)
          {
              return _Private_Utils.<String>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<Integer>
      {
          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<Integer> intIterator(int... values)
      {
          if (values == null || values.length == 0)
          {
              return _Private_Utils.<Integer>emptyIterator();
          }
          return new IntIterator(values);
      }
  
      public static final Iterator<Integer> intIterator(int[] values, int len)
      {
          if (values == null || values.length == 0 || len == 0)
          {
              return _Private_Utils.<Integer>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);
      }
  
  }
  