content: // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  // SPDX-License-Identifier: Apache-2.0
  package com.amazon.ion.impl;
  
  import static com.amazon.ion.impl._Private_IonConstants.tidDATAGRAM;
  import static com.amazon.ion.impl._Private_IonConstants.tidList;
  import static com.amazon.ion.impl._Private_IonConstants.tidSexp;
  import static com.amazon.ion.impl._Private_IonConstants.tidStruct;
  import static com.amazon.ion.impl.lite._Private_LiteDomTrampoline.reverseEncode;
  
  import com.amazon.ion.IonException;
  import com.amazon.ion.IonType;
  import com.amazon.ion.SymbolTable;
  import com.amazon.ion.Timestamp;
  import com.amazon.ion.impl.BlockedBuffer.BlockedByteInputStream;
  import com.amazon.ion.impl.IonBinary.BufferManager;
  import com.amazon.ion.system.IonWriterBuilder.InitialIvmHandling;
  import com.amazon.ion.system.IonWriterBuilder.IvmMinimizing;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.util.LinkedList;
  import java.util.Queue;
  
  
  final class IonWriterSystemBinary
      extends IonWriterSystem
      implements _Private_ListWriter
  {
  
      /**
       * Implements patching of the internal OutputStream with actual value
       * lengths and types. Every instance of this class represents a container
       * (including the datagram). Information about the children of the container
       * is stored in _types/_positions/_lengths arrays.
       *
       * The patching mechanism works as follows. For every primitive value, we store
       * its type, position in internal OutputStream and the serialized lengths
       * in this class. With every value insertion, its length is then summed to the
       * length of the @_parent. Upon seeing a container, a new PatchedValue is created
       * and inserted into @_children queue, then all subsequent values are written
       * as described above. As the container is finished, its @_parent becomes active
       * and values are continued to be written. In the end, a general tree is created
       * with information about all the values in the datagram. The values themselves
       * are serialized into internal OutputStream
       *
       * To finalize the internal OutputStream into a user stream, the arrays in
       * PatchedValues are traversed from the beginning. For every value in
       * in @_types[i], it's type descriptor is written into user stream. If the type
       * is primitive, content of the internal OutputStream is copied starting
       * from @_positions[i] with length @_lengths[i]. If @_types[i] is a container,
       * a child is popped from the @_children queue and recursively written as
       * described above. Whenever TID_SYMBOL_TABLE_PATCH is seen in @_types, the
       * current symbol table gets reset with the one on the top of @_symtabs queue.
       */
      static class PatchedValues {
  
          private final static int DEFAULT_PATCH_COUNT    = 10;
  
          /** first free position in the _types/_positions/_lengths arrays */
          int _freePos;
          /** types of the container */
          int[] _types;
          /** positions where values start in buffer */
          int[] _positions;
          /**
           * lengths of the values. The high 32bits of this value store the length
           * of the field name, the low 32bits store the actual value length. We need the
           * field name length to properly adjust the total @_parent length in @endPatch
           */
          long[] _lengths;
          // the parent
          PatchedValues _parent;
          Queue<PatchedValues> _children;
          Queue<SymbolTable> _symtabs;
  
          PatchedValues() {
              _freePos = -1;
              _types = new int[DEFAULT_PATCH_COUNT];
              _positions = new int[DEFAULT_PATCH_COUNT];
              _lengths = new long[DEFAULT_PATCH_COUNT];
          }
  
          void reset() {
              _freePos = -1;
              _children = null;
              _symtabs = null;
          }
  
          /**
           * Add a new PatchedValues instance to _children and return it
           */
          PatchedValues addChild() {
              PatchedValues pv = new PatchedValues();
              pv._parent = this;
              if (_children == null) {
                  _children = new LinkedList<PatchedValues>();
              }
              _children.add(pv);
              return pv;
          }
  
          /**
           * Inject a symbol table at the specified position in internal OutputStream. Most of the
           * times, the injection will happen upon seeing the first non-system symbol. At that point
           * the information about the symbol is already stored here by @startPatch call, so we need
           * to be able to inject a symtab before this value (injectBeforeCurrent)
           *
           * @param st
           * @param injectBeforeCurrent Flags if symbol table must be injected before the current value
           *                            which essentially shifts the array by 1
           * @throws IllegalStateException if the PatchedValues is not a top-level one
           */
          void injectSymbolTable(SymbolTable st, boolean injectBeforeCurrent) {
              if (_parent != null) {
                  // we're not on top-level
                  throw new IllegalStateException("Cannot inject a symbol table when not on top-level");
              }
              if (_symtabs == null) {
                  _symtabs = new LinkedList<SymbolTable>();
              }
              ++_freePos;
              if (_freePos == _positions.length) {
                  grow();
              }
              if (injectBeforeCurrent) {
                  // move current value to the right
                  _types[_freePos] = _types[_freePos - 1];
                  _lengths[_freePos] = _lengths[_freePos - 1];
                  // set previous value to symbol table type
                  _types[_freePos - 1] = TID_SYMBOL_TABLE_PATCH;
                  _lengths[_freePos - 1] = 0;
              } else {
                  // add symbol table to the next free position as a usual value
                  _types[_freePos] = TID_SYMBOL_TABLE_PATCH;
                  _lengths[_freePos] = 0;
              }
              // inject the symbol table to the current position on the top
              _symtabs.add(st);
          }
  
          int getType() {
              return _types[_freePos];
          }
  
          PatchedValues getParent() {
              return _parent;
          }
  
          /** Start the patch for given @type at given @pos */
          void startPatch(int type, int pos) {
              ++_freePos;
              if (_freePos == _positions.length) {
                  grow();
              }
              _types[_freePos] = type;
              _lengths[_freePos] = 0;
              _positions[_freePos] = pos;
          }
  
          /** Set the field name length as high 32bits of @_lengths item */
          void patchFieldName(int fieldNameLength) {
              _lengths[_freePos] = ((long)fieldNameLength) << 32;
          }
  
          /**
           * Set the value length as low 32bits of @_lengths item. The low 32bits will be
           * summed with the given value (say, it's a list that is in struct)
           * @param len
           */
          void patchValue(int len) {
              long memLen = (_lengths[_freePos] & 0xFFFFFFFF00000000L);
              long curLen = (_lengths[_freePos] & 0x00000000FFFFFFFFL);
              _lengths[_freePos] = memLen | (curLen + len);
          }
  
          /**
           * End this patch. If there's a @_parent available, it's @_length will be summed
           * with the length of current patched value
           */
          void endPatch() {
              if (_parent != null) {
                  int memberLen = (int)(_lengths[_freePos] >> 32);
                  int valueLen = (int)(_lengths[_freePos] & 0xFFFFFFFF);
                  int totalLen = memberLen + valueLen;
                  switch (_types[_freePos]) {
                  case TID_SYMBOL_TABLE_PATCH:
                  case TID_RAW:
                      break;
                  case TID_ANNOTATION_PATCH:
                      totalLen += IonBinary.lenVarUInt(valueLen);
                      break;
                  default:
                      // add the type if it's specified
                      ++totalLen;
                      // add actual length of @totalLen :)
                      if (valueLen >= _Private_IonConstants.lnIsVarLen) {
                          totalLen += IonBinary.lenVarUInt(valueLen);
                      }
                  }
                  _parent.patchValue(totalLen);
              }
          }
  
          // grow all related arrays
          private void grow() {
              int newSize = _positions.length * 2;
              _types = growOne(_types, newSize);
              _positions = growOne(_positions, newSize);
              _lengths = growOne(_lengths, newSize);
          }
          // grow single array and return the result
          static int[] growOne(int[] source, int newSize) {
              int[] dest = new int[newSize];
              System.arraycopy(source, 0, dest, 0, source.length);
              return dest;
          }
          // grow single array and return the result
          static long[] growOne(long[] source, int newSize) {
              long[] dest = new long[newSize];
              System.arraycopy(source, 0, dest, 0, source.length);
              return dest;
          }
      }
  
      // top-level patch
      PatchedValues _patch = new PatchedValues();
  
      private final static int TID_ANNOTATION_PATCH = _Private_IonConstants.tidDATAGRAM + 1;
      private final static int TID_SYMBOL_TABLE_PATCH = _Private_IonConstants.tidDATAGRAM + 2;
      private final static int TID_RAW = _Private_IonConstants.tidDATAGRAM + 3;
  
      // private static final boolean _verbose_debug = false;
  
      static final int UNKNOWN_LENGTH = -1;
  
      BufferManager     _manager;
      IonBinary.Writer  _writer;
  
      /** Not null */
      private final OutputStream _user_output_stream;
  
      /**
       * Do we {@link #flush()} after each top-level value?
       * @see #closeValue()
       */
      private final boolean _auto_flush;
  
      boolean           _in_struct;
  
      /** Ensure we don't use a closed {@link #output} stream. */
      private boolean _closed;
  
      private final static int DEFAULT_PATCH_COUNT        = 10;
      private final static int DEFAULT_PATCH_DEPTH        = 10;
  
      // Patch:
      //          offset in data stream
      //          accumulated length    -- combine w offset in long? offset:len (allows len+=more)
      //          type of data (should this be in the data stream?
      //          patched value's parent is struct flag (low nibble in data stream?)
      //
      // the patches are the accumulated list of patch points and are
      // in position order (which is conveniently the order they are
      // encountered and created).
      int        _patch_count     = 0;
      int []     _patch_lengths   = new int[DEFAULT_PATCH_COUNT];     // TODO: should these be merged? (since array access is expensive)
      int []     _patch_offsets   = new int[DEFAULT_PATCH_COUNT];     // should patch lengths and patch offsets be longs?
      int []     _patch_table_idx = new int[DEFAULT_PATCH_COUNT];
      int []     _patch_types     = new int[DEFAULT_PATCH_COUNT];
      boolean [] _patch_in_struct = new boolean[DEFAULT_PATCH_COUNT];
  
      // this is only loaded by the User writer, but it is read
      // by the "get byte" operations and must be coordinated
      // with the patch list that the system writer maintains here.
      int           _patch_symbol_table_count = 0;
      SymbolTable[] _patch_symbol_tables      = new SymbolTable[DEFAULT_PATCH_COUNT];
  
      // the patch stack is the list of patch points that currently
      // need updating.  The value is the index into the patch arrays.
      // As a value requiring patches is closed its patch idx is removed
      // from the stack.
      int    _top;
      int [] _patch_stack = new int[DEFAULT_PATCH_DEPTH];
  
      /**
       * This is the depth as seen by the user.  Since there are cases where we
       * don't push onto the patch stack and cases where we push non-user
       * containers onto the patch stack we compute this separately during
       * stepIn and stepOut.
       */
      private int _user_depth;
  
  
      /**
       * @param out OutputStream the users output byte stream, if specified
       * @param autoFlush when true the writer flushes to the output stream
       *  between top level values
       * @param ensureInitialIvm when true, an initial IVM will be emitted even
       *  when the user doesn't explicitly write one. When false, an initial IVM
       *  won't be emitted unless the user does it. That can result in an invalid
       *  Ion stream if not used carefully.
       * @throws NullPointerException if any parameter is null.
       */
      IonWriterSystemBinary(SymbolTable defaultSystemSymtab,
                            OutputStream out,
                            boolean autoFlush,
                            boolean ensureInitialIvm)
      {
          super(defaultSystemSymtab,
                (ensureInitialIvm ? InitialIvmHandling.ENSURE : null),
                IvmMinimizing.ADJACENT,
                true);
  
          out.getClass(); // Efficient null check
          _user_output_stream = out;
  
          // the buffer manager and writer
          // are used to hold the buffered
          // binary values pending flush().
          _manager = new BufferManager();
          _writer = _manager.openWriter();
          _auto_flush = autoFlush;
      }
  
      /**
       * Empty our buffers, assuming it is safe to do so.
       * This is called by {@link #flush()} and {@link #finish()}.
       */
      private void writeAllBufferedData()
          throws IOException
      {
          writeBytes(_user_output_stream);
  
          clearFieldName();
          clearAnnotations();
  
          _in_struct = false;
          _patch_count = 0;
          _patch_symbol_table_count = 0;
          _top = 0;
          try {
              _writer.setPosition(0);
              _writer.truncate();
          }
          catch (IOException e) {
              throw new IonException(e);
          }
      }
  
      @Override
      public void finish() throws IOException
      {
          if (getDepth() != 0) {
              throw new IllegalStateException(ERROR_FINISH_NOT_AT_TOP_LEVEL);
          }
  
          writeAllBufferedData();
          super.finish();
      }
  
      final OutputStream getOutputStream()
      {
          return _user_output_stream;
      }
  
      public final boolean isInStruct()
      {
          return _in_struct;
      }
  
      protected final boolean atDatagramLevel()
      {
          return (topType() == _Private_IonConstants.tidDATAGRAM);
  //        return is_datagram;
      }
  
      @Override
      public final int getDepth()
      {
          return _user_depth;
      }
  
      protected final IonType getContainer()
      {
          IonType type;
          int tid = parentType();
          switch (tid) {
          case tidList:
              type = IonType.LIST;
              break;
          case tidSexp:
              type = IonType.SEXP;
              break;
          case tidStruct:
              type = IonType.STRUCT;
              break;
          case tidDATAGRAM:
              type = IonType.DATAGRAM;
              break;
          default:
              throw new IonException("unexpected parent type "+tid+" does not represent a container");
          }
          return type;
      }
  
      @Override
      final SymbolTable inject_local_symbol_table() throws IOException
      {
          SymbolTable symbols = super.inject_local_symbol_table();
          PatchedValues top;
          // find the parent
          for (top = _patch; top.getParent() != null; top = top.getParent()) {}
          // inject the symbol table; if @_patch is not the top element, then inject
          // the symtab before this top-level value begins
          super.startValue();
          top.injectSymbolTable(symbols, _patch.getParent() != null);
          super.endValue();
          return symbols;
      }
  
      private final int topType() {
          if (_top == 0) return _Private_IonConstants.tidDATAGRAM;
          return _patch_types[_patch_stack[_top - 1]];
      }
  
      private final int parentType() {
          int ii = _top - 2;
          while (ii >= 0) {
              int type = _patch_types[_patch_stack[ii]];
              if (type != _Private_IonConstants.tidTypedecl) return type;
              ii--;
          }
          return _Private_IonConstants.tidDATAGRAM;
      }
  
      private final void startValue(int value_type)
          throws IOException
      {
          super.startValue();
  
          int[] sids = null;
          int sid_count = annotationCount();
          if (sid_count > 0) {
              // prepare the SIDs of annotations before doing the patch as this may
              // fail and leave the Writer in undefined state
              sids = super.internAnnotationsAndGetSids();
              _patch.startPatch(_Private_IonConstants.tidTypedecl, _writer.position());
          } else {
              _patch.startPatch(value_type, _writer.position());
          }
          // write field name
          if (_in_struct) {
              if (!isFieldNameSet()) {
                  throw new IllegalStateException(ERROR_MISSING_FIELD_NAME);
              }
              int sid = super.getFieldId();
              if (sid < 0) {
                  throw new UnsupportedOperationException("symbol resolution must be handled by the user writer");
              }
              int fieldNameLength = _writer.writeVarUIntValue(sid, true);
              _patch.patchFieldName(fieldNameLength);
              clearFieldName();
          }
  
          // write annotations
          if (sid_count > 0) {
              _patch = _patch.addChild();
              // add all annotations as if it's a value with type = -1
              _patch.startPatch(TID_ANNOTATION_PATCH, _writer.position());
              int len = 0;
              for (int ii=0; ii<sid_count; ii++) {
                  len += _writer.writeVarUIntValue(sids[ii], true);
              }
              _patch.patchValue(len);
              _patch.endPatch();
              clearAnnotations();
              // add the actual value now
              _patch.startPatch(value_type, _writer.position());
          }
      }
  
      private final void closeValue()
          throws IOException
      {
          super.endValue();
          _patch.endPatch();
          if (_patch.getParent() != null
              && _patch.getParent().getType() == _Private_IonConstants.tidTypedecl)
          {
              // this is an annotated value, gotta get out
              _patch = _patch.getParent();
              _patch.endPatch();
              assert _patch != null;
          }
      }
  
  
      /**
       * {@inheritDoc}
       * <p>
       * The {@link OutputStream} spec is mum regarding the behavior of flush on
       * a closed stream, so we shouldn't assume that our stream can handle that.
       */
      public final void flush() throws IOException
      {
          if (! _closed)
          {
              if (atDatagramLevel() && ! hasAnnotations())
              {
                  SymbolTable symtab = getSymbolTable();
  
                  if (symtab != null &&
                      symtab.isReadOnly() &&
                      symtab.isLocalTable())
                  {
                      // It's no longer possible to add more symbols to the local
                      // symtab, so we can safely write everything out.
                      writeAllBufferedData();
                  }
              }
  
              _user_output_stream.flush();
          }
      }
  
      public final void close() throws IOException
      {
          if (! _closed) {
              try
              {
                  if (getDepth() == 0) {
                      finish();
                  }
              }
              finally
              {
                  // Do this first so we are closed even if the call below throws.
                  _closed = true;
  
                  _user_output_stream.close();
              }
          }
      }
  
  
  
      @Override
      void writeIonVersionMarkerAsIs(SymbolTable systemSymtab)
          throws IOException
      {
          if (_user_depth != 0) {
              throw new IllegalStateException("IVM not on top-level");
          }
          super.startValue();
          _patch.startPatch(TID_RAW, _writer.position());
          _patch.patchValue(4);
          _writer.write(_Private_IonConstants.BINARY_VERSION_MARKER_1_0);
          _patch.endPatch();
          super.endValue();
      }
  
      @Override
      void writeLocalSymtab(SymbolTable symbols)
          throws IOException
      {
          // this method *should* be called when @_patch is a top-level value, but
          // we cannot be sure, so try to find the top-level anyway
          PatchedValues top;
          // find the parent
          for (top = _patch; top.getParent() != null; top = top.getParent()) {}
          super.startValue();
          top.injectSymbolTable(symbols, _patch.getParent() != null);
          super.endValue();
          super.writeLocalSymtab(symbols);
      }
  
      public final void stepIn(IonType containerType) throws IOException
      {
          int tid;
          switch (containerType)
          {
              case LIST:   tid = tidList;   break;
              case SEXP:   tid = tidSexp;   break;
              case STRUCT: tid = tidStruct; break;
              default:
                  throw new IllegalArgumentException();
          }
          startValue(tid);
          _patch = _patch.addChild();
          _in_struct = (tid == tidStruct);
          ++_user_depth;
      }
  
      public final void stepOut() throws IOException
      {
          if (_patch.getParent() == null) {
              throw new IllegalStateException(IonMessages.CANNOT_STEP_OUT);
          }
          // container is over, getting out
          _patch = _patch.getParent();
          // now close current value
          closeValue();
          if (_patch.getParent() == null) {
              _in_struct = false;
              // we're on top-level
              if (_auto_flush) {
                  flush();
              }
          } else {
              _in_struct = (_patch.getParent().getType() == _Private_IonConstants.tidStruct);
          }
          --_user_depth;
      }
  
  
      public void writeNull(IonType type) throws IOException
      {
          int tid;
          switch (type) {
          case NULL:      tid = _Private_IonConstants.tidNull;      break;
          case BOOL:      tid = _Private_IonConstants.tidBoolean;   break;
          case INT:       tid = _Private_IonConstants.tidPosInt;    break;
          case FLOAT:     tid = _Private_IonConstants.tidFloat;     break;
          case DECIMAL:   tid = _Private_IonConstants.tidDecimal;   break;
          case TIMESTAMP: tid = _Private_IonConstants.tidTimestamp; break;
          case SYMBOL:    tid = _Private_IonConstants.tidSymbol;    break;
          case STRING:    tid = _Private_IonConstants.tidString;    break;
          case BLOB:      tid = _Private_IonConstants.tidBlob;      break;
          case CLOB:      tid = _Private_IonConstants.tidClob;      break;
          case SEXP:      tid = _Private_IonConstants.tidSexp;      break;
          case LIST:      tid = _Private_IonConstants.tidList;      break;
          case STRUCT:    tid = _Private_IonConstants.tidStruct;    break;
          default:
              throw new IllegalArgumentException("Invalid type: " + type);
          }
          startValue(TID_RAW);
          _writer.write((tid << 4) | _Private_IonConstants.lnIsNull);
          _patch.patchValue(1);
          closeValue();
      }
  
      public void writeBool(boolean value) throws IOException
      {
          int ln = value ? _Private_IonConstants.lnBooleanTrue : _Private_IonConstants.lnBooleanFalse;
          startValue(TID_RAW);
          _writer.write((_Private_IonConstants.tidBoolean << 4) | ln);
          _patch.patchValue(1);
          closeValue();
      }
      public void writeInt(long value) throws IOException
      {
          int len;
          if (value < 0) {
              startValue(_Private_IonConstants.tidNegInt);
              len = _writer.writeUIntValue(-value);
          } else {
              startValue(_Private_IonConstants.tidPosInt);
              len = _writer.writeUIntValue(value);
          }
          _patch.patchValue(len);
          closeValue();
      }
  
      public void writeInt(BigInteger value) throws IOException
      {
          if (value == null) {
              writeNull(IonType.INT);
              return;
          }
  
          boolean     is_negative = (value.signum() < 0);
          BigInteger  positive = value;
  
          if (is_negative) {
              positive = value.negate();
          }
  
          int len = IonBinary.lenIonInt(positive);
          startValue(is_negative ? _Private_IonConstants.tidNegInt : _Private_IonConstants.tidPosInt);
          _writer.writeUIntValue(positive, len);
          _patch.patchValue(len);
  
          closeValue();
      }
  
      public void writeFloat(double value) throws IOException
      {
          int len = IonBinary.lenIonFloat(value);
          startValue(_Private_IonConstants.tidFloat); // int's are always less than varlen long
          len = _writer.writeFloatValue(value);
          _patch.patchValue(len);
          closeValue();
      }
  
      @Override
      public void writeDecimal(BigDecimal value) throws IOException
      {
          if (value == null) {
              writeNull(IonType.DECIMAL);
              return;
          }
          startValue(_Private_IonConstants.tidDecimal);
          int len = _writer.writeDecimalContent(value);
          _patch.patchValue(len);
          closeValue();
      }
  
      public void writeTimestamp(Timestamp value) throws IOException
      {
          if (value == null) {
              writeNull(IonType.TIMESTAMP);
              return;
          }
          startValue(_Private_IonConstants.tidTimestamp);
          int len = _writer.writeTimestamp(value);
          _patch.patchValue(len);
          closeValue();
      }
  
      public void writeString(String value) throws IOException
      {
          if (value == null) {
              writeNull(IonType.STRING);
              return;
          }
          startValue(_Private_IonConstants.tidString);
          int len = _writer.writeStringData(value);
          _patch.patchValue(len);
          closeValue();
      }
  
      @Override
      void writeSymbolAsIs(int symbolId) throws IOException
      {
          startValue(_Private_IonConstants.tidSymbol);
          int len = _writer.writeUIntValue(symbolId);
          _patch.patchValue(len);
          closeValue();
      }
  
      @Override
      public void writeSymbolAsIs(String value) throws IOException
      {
          if (value == null) {
              writeNull(IonType.SYMBOL);
              return;
          }
          int sid = add_symbol(value);
          writeSymbolAsIs(sid);
      }
  
      public void writeClob(byte[] value, int start, int len) throws IOException
      {
          if (value == null) {
              writeNull(IonType.CLOB);
              return;
          }
          if (start < 0 || len < 0 || start+len > value.length) {
              throw new IllegalArgumentException("the start and len must be contained in the byte array");
          }
          startValue(_Private_IonConstants.tidClob);
          _writer.write(value, start, len);
          _patch.patchValue(len);
          closeValue();
      }
  
      public void writeBlob(byte[] value, int start, int len) throws IOException
      {
          if (value == null) {
              writeNull(IonType.BLOB);
              return;
          }
          if (start < 0 || len < 0 || start+len > value.length) {
              throw new IllegalArgumentException("the start and len must be contained in the byte array");
          }
          startValue(_Private_IonConstants.tidBlob);
          _writer.write(value, start, len);
          _patch.patchValue(len);
          closeValue();
      }
  
      // just transfer the bytes into the current patch as 'proper' ion binary serialization
      public void writeRaw(byte[] value, int start, int len) throws IOException
      {
          startValue(TID_RAW);
          _writer.write(value, start, len);
          _patch.patchValue(len);
          closeValue();
      }
  
      public void writeBoolList(boolean[] values) throws IOException
      {
          stepIn(IonType.LIST);
          for (boolean b : values) {
              writeBool(b);
          }
          stepOut();
      }
  
      public void writeIntList(byte[] values) throws IOException
      {
          stepIn(IonType.LIST);
          for (byte b : values) {
              writeInt(b);
          }
          stepOut();
      }
  
      public void writeIntList(short[] values) throws IOException
      {
          stepIn(IonType.LIST);
          for (short s : values) {
              writeInt(s);
          }
          stepOut();
      }
  
      public void writeIntList(int[] values) throws IOException
      {
          stepIn(IonType.LIST);
          for (int i : values) {
              writeInt(i);
          }
          stepOut();
      }
  
      public void writeIntList(long[] values) throws IOException
      {
          stepIn(IonType.LIST);
          for (long l : values) {
              writeInt(l);
          }
          stepOut();
      }
  
      public void writeFloatList(float[] values) throws IOException
      {
          stepIn(IonType.LIST);
          for (float f : values) {
              writeFloat(f);
          }
          stepOut();
      }
  
      public void writeFloatList(double[] values) throws IOException
      {
          stepIn(IonType.LIST);
          for (double d : values) {
              writeFloat(d);
          }
          stepOut();
      }
  
      public void writeStringList(String[] values) throws IOException
      {
          stepIn(IonType.LIST);
          for (String s : values) {
              writeString(s);
          }
          stepOut();
      }
  
      // TODO make private after IonBinaryWriter is removed
      /**
       * Writes everything we've got into the output stream, performing all
       * necessary patches along the way.
       *
       * This implements {@link com.amazon.ion.IonBinaryWriter#writeBytes(OutputStream)}
       * via our subclass {@link IonWriterBinaryCompatibility.System}.
       */
      int writeBytes(OutputStream userstream) throws IOException {
          if (_patch.getParent() != null) {
              throw new IllegalStateException("Tried to flush while not on top-level");
          }
  
          try {
              BlockedByteInputStream datastream =
                  new BlockedByteInputStream(_manager.buffer());
              int size = writeRecursive(datastream, userstream, _patch);
              return size;
          } finally {
              _patch.reset();
          }
      }
  
      int writeRecursive(BlockedByteInputStream datastream, OutputStream userstream, PatchedValues p) throws IOException {
          int totalSize = 0;
          for (int i = 0; i <= p._freePos; ++i) {
              int type = p._types[i];
              int pos = p._positions[i];
              int fnlen = (int)(p._lengths[i] >> 32);
              int vallen = (int)(p._lengths[i] & 0xFFFFFFFF);
              if (p.getParent() == null) {
                  if (pos > totalSize) {
                      // write whatever data that we have in the datastream (eg external data)
                      datastream.writeTo(userstream, pos - totalSize);
                      totalSize = pos;
                  }
                  totalSize += fnlen + vallen;
              }
              // write member name
              if (fnlen > 0) {
                  datastream.writeTo(userstream, fnlen);
              }
              switch (type) {
              case TID_ANNOTATION_PATCH:
                  IonBinary.writeVarUInt(userstream, vallen);
                  datastream.writeTo(userstream, vallen);
                  break;
  
              case TID_SYMBOL_TABLE_PATCH:
                  SymbolTable symtab = p._symtabs.remove();
                  if (!symtab.isSystemTable()) {
                      byte[] symtabBytes = reverseEncode(1024, symtab);
                      userstream.write(symtabBytes);
                      totalSize += symtabBytes.length;
                  }
                  break;
  
              case TID_RAW:
                  datastream.writeTo(userstream, vallen);
                  break;
  
              default:
                  // write type
                  if (vallen >= _Private_IonConstants.lnIsVarLen) {
                      int typeByte = (type << 4) | _Private_IonConstants.lnIsVarLen;
                      userstream.write(typeByte);
                      IonBinary.writeVarUInt(userstream, vallen);
                  } else {
                      int typeByte = (type << 4) | vallen;
                      userstream.write(typeByte);
                  }
                  switch (type) {
                  case _Private_IonConstants.tidList:
                  case _Private_IonConstants.tidSexp:
                  case _Private_IonConstants.tidStruct:
                  case _Private_IonConstants.tidTypedecl:
                      // write the container
                      assert p._children != null;
                      writeRecursive(datastream, userstream, p._children.remove());
                      break;
  
                  default:
                      datastream.writeTo(userstream, vallen);
                  }
              }
          }
          return totalSize;
      }
  
      static class CountingStream extends OutputStream
      {
          private final OutputStream _wrapped;
          private       int          _written;
  
          CountingStream(OutputStream userstream) {
              _wrapped = userstream;
          }
  
          public int getBytesWritten() {
              return _written;
          }
  
          @Override
          public void write(int b) throws IOException
          {
              _wrapped.write(b);
              _written++;
          }
          @Override
          public void write(byte[] bytes) throws IOException
          {
              _wrapped.write(bytes);
              _written += bytes.length;
          }
          @Override
          public void write(byte[] bytes, int off, int len) throws IOException
          {
              _wrapped.write(bytes, off, len);
              _written += len;
          }
  
      }
  
      protected int write_symbol_table(OutputStream userstream,
                                       SymbolTable symtab) throws IOException
      {
          CountingStream cs = new CountingStream(userstream);
          // TODO this is assuming the symtab needed here, broken for open content.
          IonWriterSystemBinary writer =
              new IonWriterSystemBinary(_default_system_symbol_table,
                                        cs,
                                        false /* autoflush */ ,
                                        false /* ensureInitialIvm */);
          symtab.writeTo(writer);
          writer.finish();
          int symtab_len = cs.getBytesWritten();
          return symtab_len;
      }
  
      protected int XXX_get_pending_length_with_no_symbol_tables()
      {
          int buffer_length = _manager.buffer().size();
          int patch_amount = 0;
  
          for (int patch_idx = 0; patch_idx < _patch_count; patch_idx ++) {
              // int vlen = _patch_list[patch_idx + IonBinaryWriter.POSITION_OFFSET];
              int vlen = _patch_lengths[patch_idx];
              if (vlen >= _Private_IonConstants.lnIsVarLen) {
                  int ln = IonBinary.lenVarUInt(vlen);
                  patch_amount += ln;
              }
          }
  
          int symbol_table_length = 0;
  
          int total_length = 0;
          total_length += buffer_length
                       +  patch_amount
                       +  symbol_table_length;
  
          return total_length;
      }
  }
  