aboutsummaryrefslogblamecommitdiff
path: root/java/core/src/main/java/com/google/protobuf/RopeByteString.java
blob: 6fa555df150964f16eafca81e63533a8ed590035 (plain) (tree)
1
2
3

                                                      
                                                  




























                                                                         
                                    

                           
                                      
                                 
                            
                           
                                

                           


                                        
                       



                                                                            

                                                         

                                                                       
                                                                                                                                          



                                                                               
                                                                               











                                                                                
                                               




































































                                                                                


                                                                          









                                                                              
                            
















                                                                            










                                                                                



                                                                      


                                                                               
                                                                            


                                                                            
                                                           

       








                                                                                  









                                                                             
                                                             





                                                  
                                                              




























                                                                               
                                   
 

                                                   
                                
     

                                            



































                                                                                
                                                           






                                                                      




                                                                     
     



                                
     




                                                  

     


                                                                             
     







                                                                    


































                                                                            
                                                                  



                                                          
                                                











                                                                     













                                                                           




                                                      
 
           

                                                      


















































                                                                                



                                                                 















                                                                            

                                                                

                       

                                                                 





































                                                                           








































                                                                                
                                                                            












































































































                                                                                
                                                                          
 

                                                     
                                




                                            
                                                           





                                                  
                                  

     
                                                  





                                                                             
                                                                         






                                  
             




                              

                                                             
      
                                                            
       

                                  


                                           
                                   



                                   
             





                                                                      




                                                  
                                          

   
                                                                                                



                                                                      







                                                            
                                        



































                                                                             
                                                                              
































































































                                                                              
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.google.protobuf;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Stack;

/**
 * Class to represent {@code ByteStrings} formed by concatenation of other
 * ByteStrings, without copying the data in the pieces. The concatenation is
 * represented as a tree whose leaf nodes are each a
 * {@link com.google.protobuf.ByteString.LeafByteString}.
 *
 * <p>Most of the operation here is inspired by the now-famous paper <a
 * href="https://web.archive.org/web/20060202015456/http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol25/issue12/spe986.pdf">
 * BAP95 </a> Ropes: an Alternative to Strings hans-j. boehm, russ atkinson and
 * michael plass
 *
 * <p>The algorithms described in the paper have been implemented for character
 * strings in {@code com.google.common.string.Rope} and in the c++ class {@code
 * cord.cc}.
 *
 * <p>Fundamentally the Rope algorithm represents the collection of pieces as a
 * binary tree. BAP95 uses a Fibonacci bound relating depth to a minimum
 * sequence length, sequences that are too short relative to their depth cause a
 * tree rebalance.  More precisely, a tree of depth d is "balanced" in the
 * terminology of BAP95 if its length is at least F(d+2), where F(n) is the
 * n-the Fibonacci number. Thus for depths 0, 1, 2, 3, 4, 5,... we have minimum
 * lengths 1, 2, 3, 5, 8, 13,...
 *
 * @author carlanton@google.com (Carl Haverl)
 */
final class RopeByteString extends ByteString {

  /**
   * BAP95. Let Fn be the nth Fibonacci number. A {@link RopeByteString} of
   * depth n is "balanced", i.e flat enough, if its length is at least Fn+2,
   * e.g. a "balanced" {@link RopeByteString} of depth 1 must have length at
   * least 2, of depth 4 must have length >= 8, etc.
   *
   * <p>There's nothing special about using the Fibonacci numbers for this, but
   * they are a reasonable sequence for encapsulating the idea that we are OK
   * with longer strings being encoded in deeper binary trees.
   *
   * <p>For 32-bit integers, this array has length 46.
   */
  private static final int[] minLengthByDepth;

  static {
    // Dynamically generate the list of Fibonacci numbers the first time this
    // class is accessed.
    List<Integer> numbers = new ArrayList<Integer>();

    // we skip the first Fibonacci number (1).  So instead of: 1 1 2 3 5 8 ...
    // we have: 1 2 3 5 8 ...
    int f1 = 1;
    int f2 = 1;

    // get all the values until we roll over.
    while (f2 > 0) {
      numbers.add(f2);
      int temp = f1 + f2;
      f1 = f2;
      f2 = temp;
    }

    // we include this here so that we can index this array to [x + 1] in the
    // loops below.
    numbers.add(Integer.MAX_VALUE);
    minLengthByDepth = new int[numbers.size()];
    for (int i = 0; i < minLengthByDepth.length; i++) {
      // unbox all the values
      minLengthByDepth[i] = numbers.get(i);
    }
  }

  private final int totalLength;
  private final ByteString left;
  private final ByteString right;
  private final int leftLength;
  private final int treeDepth;

  /**
   * Create a new RopeByteString, which can be thought of as a new tree node, by
   * recording references to the two given strings.
   *
   * @param left  string on the left of this node, should have {@code size() >
   *              0}
   * @param right string on the right of this node, should have {@code size() >
   *              0}
   */
  private RopeByteString(ByteString left, ByteString right) {
    this.left = left;
    this.right = right;
    leftLength = left.size();
    totalLength = leftLength + right.size();
    treeDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1;
  }

  /**
   * Concatenate the given strings while performing various optimizations to
   * slow the growth rate of tree depth and tree node count. The result is
   * either a {@link com.google.protobuf.ByteString.LeafByteString} or a
   * {@link RopeByteString} depending on which optimizations, if any, were
   * applied.
   *
   * <p>Small pieces of length less than {@link
   * ByteString#CONCATENATE_BY_COPY_SIZE} may be copied by value here, as in
   * BAP95.  Large pieces are referenced without copy.
   *
   * @param left  string on the left
   * @param right string on the right
   * @return concatenation representing the same sequence as the given strings
   */
  static ByteString concatenate(ByteString left, ByteString right) {
    if (right.size() == 0) {
      return left;
    }

    if (left.size() == 0) {
      return right;
    }

    final int newLength = left.size() + right.size();
    if (newLength < ByteString.CONCATENATE_BY_COPY_SIZE) {
      // Optimization from BAP95: For short (leaves in paper, but just short
      // here) total length, do a copy of data to a new leaf.
      return concatenateBytes(left, right);
    }

    if (left instanceof RopeByteString) {
      final RopeByteString leftRope = (RopeByteString) left;
      if (leftRope.right.size() + right.size() < CONCATENATE_BY_COPY_SIZE) {
        // Optimization from BAP95: As an optimization of the case where the
        // ByteString is constructed by repeated concatenate, recognize the case
        // where a short string is concatenated to a left-hand node whose
        // right-hand branch is short.  In the paper this applies to leaves, but
        // we just look at the length here. This has the advantage of shedding
        // references to unneeded data when substrings have been taken.
        //
        // When we recognize this case, we do a copy of the data and create a
        // new parent node so that the depth of the result is the same as the
        // given left tree.
        ByteString newRight = concatenateBytes(leftRope.right, right);
        return new RopeByteString(leftRope.left, newRight);
      }

      if (leftRope.left.getTreeDepth() > leftRope.right.getTreeDepth()
          && leftRope.getTreeDepth() > right.getTreeDepth()) {
        // Typically for concatenate-built strings the left-side is deeper than
        // the right.  This is our final attempt to concatenate without
        // increasing the tree depth.  We'll redo the node on the RHS.  This
        // is yet another optimization for building the string by repeatedly
        // concatenating on the right.
        ByteString newRight = new RopeByteString(leftRope.right, right);
        return new RopeByteString(leftRope.left, newRight);
      }
    }

    // Fine, we'll add a node and increase the tree depth--unless we rebalance ;^)
    int newDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1;
    if (newLength >= minLengthByDepth[newDepth]) {
      // The tree is shallow enough, so don't rebalance
      return new RopeByteString(left, right);
    }

    return new Balancer().balance(left, right);
  }

  /**
   * Concatenates two strings by copying data values. This is called in a few
   * cases in order to reduce the growth of the number of tree nodes.
   *
   * @param left  string on the left
   * @param right string on the right
   * @return string formed by copying data bytes
   */
  private static ByteString concatenateBytes(ByteString left,
      ByteString right) {
    int leftSize = left.size();
    int rightSize = right.size();
    byte[] bytes = new byte[leftSize + rightSize];
    left.copyTo(bytes, 0, 0, leftSize);
    right.copyTo(bytes, 0, leftSize, rightSize);
    return ByteString.wrap(bytes);  // Constructor wraps bytes
  }

  /**
   * Create a new RopeByteString for testing only while bypassing all the
   * defenses of {@link #concatenate(ByteString, ByteString)}. This allows
   * testing trees of specific structure. We are also able to insert empty
   * leaves, though these are dis-allowed, so that we can make sure the
   * implementation can withstand their presence.
   *
   * @param left  string on the left of this node
   * @param right string on the right of this node
   * @return an unsafe instance for testing only
   */
  static RopeByteString newInstanceForTest(ByteString left, ByteString right) {
    return new RopeByteString(left, right);
  }

  /**
   * Gets the byte at the given index.
   * Throws {@link ArrayIndexOutOfBoundsException} for backwards-compatibility
   * reasons although it would more properly be {@link
   * IndexOutOfBoundsException}.
   *
   * @param index index of byte
   * @return the value
   * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size
   */
  @Override
  public byte byteAt(int index) {
    checkIndex(index, totalLength);

    // Find the relevant piece by recursive descent
    if (index < leftLength) {
      return left.byteAt(index);
    }

    return right.byteAt(index - leftLength);
  }

  @Override
  public int size() {
    return totalLength;
  }

  // =================================================================
  // Pieces

  @Override
  protected int getTreeDepth() {
    return treeDepth;
  }

  /**
   * Determines if the tree is balanced according to BAP95, which means the tree
   * is flat-enough with respect to the bounds. Note that this definition of
   * balanced is one where sub-trees of balanced trees are not necessarily
   * balanced.
   *
   * @return true if the tree is balanced
   */
  @Override
  protected boolean isBalanced() {
    return totalLength >= minLengthByDepth[treeDepth];
  }

  /**
   * Takes a substring of this one. This involves recursive descent along the
   * left and right edges of the substring, and referencing any wholly contained
   * segments in between. Any leaf nodes entirely uninvolved in the substring
   * will not be referenced by the substring.
   *
   * <p>Substrings of {@code length < 2} should result in at most a single
   * recursive call chain, terminating at a leaf node. Thus the result will be a
   * {@link com.google.protobuf.ByteString.LeafByteString}.
   *
   * @param beginIndex start at this index
   * @param endIndex   the last character is the one before this index
   * @return substring leaf node or tree
   */
  @Override
  public ByteString substring(int beginIndex, int endIndex) {
    final int length = checkRange(beginIndex, endIndex, totalLength);

    if (length == 0) {
      // Empty substring
      return ByteString.EMPTY;
    }

    if (length == totalLength) {
      // The whole string
      return this;
    }

    // Proper substring
    if (endIndex <= leftLength) {
      // Substring on the left
      return left.substring(beginIndex, endIndex);
    }

    if (beginIndex >= leftLength) {
      // Substring on the right
      return right.substring(beginIndex - leftLength, endIndex - leftLength);
    }

    // Split substring
    ByteString leftSub = left.substring(beginIndex);
    ByteString rightSub = right.substring(0, endIndex - leftLength);
    // Intentionally not rebalancing, since in many cases these two
    // substrings will already be less deep than the top-level
    // RopeByteString we're taking a substring of.
    return new RopeByteString(leftSub, rightSub);
  }

  // =================================================================
  // ByteString -> byte[]

  @Override
  protected void copyToInternal(byte[] target, int sourceOffset,
      int targetOffset, int numberToCopy) {
   if (sourceOffset + numberToCopy <= leftLength) {
      left.copyToInternal(target, sourceOffset, targetOffset, numberToCopy);
    } else if (sourceOffset >= leftLength) {
      right.copyToInternal(target, sourceOffset - leftLength, targetOffset,
          numberToCopy);
    } else {
      int leftLength = this.leftLength - sourceOffset;
      left.copyToInternal(target, sourceOffset, targetOffset, leftLength);
      right.copyToInternal(target, 0, targetOffset + leftLength,
          numberToCopy - leftLength);
    }
  }

  @Override
  public void copyTo(ByteBuffer target) {
    left.copyTo(target);
    right.copyTo(target);
  }

  @Override
  public ByteBuffer asReadOnlyByteBuffer() {
    ByteBuffer byteBuffer = ByteBuffer.wrap(toByteArray());
    return byteBuffer.asReadOnlyBuffer();
  }

  @Override
  public List<ByteBuffer> asReadOnlyByteBufferList() {
    // Walk through the list of LeafByteString's that make up this
    // rope, and add each one as a read-only ByteBuffer.
    List<ByteBuffer> result = new ArrayList<ByteBuffer>();
    PieceIterator pieces = new PieceIterator(this);
    while (pieces.hasNext()) {
      LeafByteString byteString = pieces.next();
      result.add(byteString.asReadOnlyByteBuffer());
    }
    return result;
  }

  @Override
  public void writeTo(OutputStream outputStream) throws IOException {
    left.writeTo(outputStream);
    right.writeTo(outputStream);
  }

  @Override
  void writeToInternal(OutputStream out, int sourceOffset,
      int numberToWrite) throws IOException {
    if (sourceOffset + numberToWrite <= leftLength) {
      left.writeToInternal(out, sourceOffset, numberToWrite);
    } else if (sourceOffset >= leftLength) {
      right.writeToInternal(out, sourceOffset - leftLength, numberToWrite);
    } else {
      int numberToWriteInLeft = leftLength - sourceOffset;
      left.writeToInternal(out, sourceOffset, numberToWriteInLeft);
      right.writeToInternal(out, 0, numberToWrite - numberToWriteInLeft);
    }
  }

  @Override
  void writeTo(ByteOutput output) throws IOException {
    left.writeTo(output);
    right.writeTo(output);
  }


  @Override
  protected String toStringInternal(Charset charset) {
    return new String(toByteArray(), charset);
  }

  // =================================================================
  // UTF-8 decoding

  @Override
  public boolean isValidUtf8() {
    int leftPartial = left.partialIsValidUtf8(Utf8.COMPLETE, 0, leftLength);
    int state = right.partialIsValidUtf8(leftPartial, 0, right.size());
    return state == Utf8.COMPLETE;
  }

  @Override
  protected int partialIsValidUtf8(int state, int offset, int length) {
    int toIndex = offset + length;
    if (toIndex <= leftLength) {
      return left.partialIsValidUtf8(state, offset, length);
    } else if (offset >= leftLength) {
      return right.partialIsValidUtf8(state, offset - leftLength, length);
    } else {
      int leftLength = this.leftLength - offset;
      int leftPartial = left.partialIsValidUtf8(state, offset, leftLength);
      return right.partialIsValidUtf8(leftPartial, 0, length - leftLength);
    }
  }

  // =================================================================
  // equals() and hashCode()

  @Override
  public boolean equals(Object other) {
    if (other == this) {
      return true;
    }
    if (!(other instanceof ByteString)) {
      return false;
    }

    ByteString otherByteString = (ByteString) other;
    if (totalLength != otherByteString.size()) {
      return false;
    }
    if (totalLength == 0) {
      return true;
    }

    // You don't really want to be calling equals on long strings, but since
    // we cache the hashCode, we effectively cache inequality. We use the cached
    // hashCode if it's already computed.  It's arguable we should compute the
    // hashCode here, and if we're going to be testing a bunch of byteStrings,
    // it might even make sense.
    int thisHash = peekCachedHashCode();
    int thatHash = otherByteString.peekCachedHashCode();
    if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) {
      return false;
    }

    return equalsFragments(otherByteString);
  }

  /**
   * Determines if this string is equal to another of the same length by
   * iterating over the leaf nodes. On each step of the iteration, the
   * overlapping segments of the leaves are compared.
   *
   * @param other string of the same length as this one
   * @return true if the values of this string equals the value of the given
   *         one
   */
  private boolean equalsFragments(ByteString other) {
    int thisOffset = 0;
    Iterator<LeafByteString> thisIter = new PieceIterator(this);
    LeafByteString thisString = thisIter.next();

    int thatOffset = 0;
    Iterator<LeafByteString> thatIter = new PieceIterator(other);
    LeafByteString thatString = thatIter.next();

    int pos = 0;
    while (true) {
      int thisRemaining = thisString.size() - thisOffset;
      int thatRemaining = thatString.size() - thatOffset;
      int bytesToCompare = Math.min(thisRemaining, thatRemaining);

      // At least one of the offsets will be zero
      boolean stillEqual = (thisOffset == 0)
          ? thisString.equalsRange(thatString, thatOffset, bytesToCompare)
          : thatString.equalsRange(thisString, thisOffset, bytesToCompare);
      if (!stillEqual) {
        return false;
      }

      pos += bytesToCompare;
      if (pos >= totalLength) {
        if (pos == totalLength) {
          return true;
        }
        throw new IllegalStateException();
      }
      // We always get to the end of at least one of the pieces
      if (bytesToCompare == thisRemaining) { // If reached end of this
        thisOffset = 0;
        thisString = thisIter.next();
      } else {
        thisOffset += bytesToCompare;
      }
      if (bytesToCompare == thatRemaining) { // If reached end of that
        thatOffset = 0;
        thatString = thatIter.next();
      } else {
        thatOffset += bytesToCompare;
      }
    }
  }

  @Override
  protected int partialHash(int h, int offset, int length) {
    int toIndex = offset + length;
    if (toIndex <= leftLength) {
      return left.partialHash(h, offset, length);
    } else if (offset >= leftLength) {
      return right.partialHash(h, offset - leftLength, length);
    } else {
      int leftLength = this.leftLength - offset;
      int leftPartial = left.partialHash(h, offset, leftLength);
      return right.partialHash(leftPartial, 0, length - leftLength);
    }
  }

  // =================================================================
  // Input stream

  @Override
  public CodedInputStream newCodedInput() {
    return CodedInputStream.newInstance(new RopeInputStream());
  }

  @Override
  public InputStream newInput() {
    return new RopeInputStream();
  }

  /**
   * This class implements the balancing algorithm of BAP95. In the paper the
   * authors use an array to keep track of pieces, while here we use a stack.
   * The tree is balanced by traversing subtrees in left to right order, and the
   * stack always contains the part of the string we've traversed so far.
   *
   * <p>One surprising aspect of the algorithm is the result of balancing is not
   * necessarily balanced, though it is nearly balanced.  For details, see
   * BAP95.
   */
  private static class Balancer {
    // Stack containing the part of the string, starting from the left, that
    // we've already traversed.  The final string should be the equivalent of
    // concatenating the strings on the stack from bottom to top.
    private final Stack<ByteString> prefixesStack = new Stack<ByteString>();

    private ByteString balance(ByteString left, ByteString right) {
      doBalance(left);
      doBalance(right);

      // Sweep stack to gather the result
      ByteString partialString = prefixesStack.pop();
      while (!prefixesStack.isEmpty()) {
        ByteString newLeft = prefixesStack.pop();
        partialString = new RopeByteString(newLeft, partialString);
      }
      // We should end up with a RopeByteString since at a minimum we will
      // create one from concatenating left and right
      return partialString;
    }

    private void doBalance(ByteString root) {
      // BAP95: Insert balanced subtrees whole. This means the result might not
      // be balanced, leading to repeated rebalancings on concatenate. However,
      // these rebalancings are shallow due to ignoring balanced subtrees, and
      // relatively few calls to insert() result.
      if (root.isBalanced()) {
        insert(root);
      } else if (root instanceof RopeByteString) {
        RopeByteString rbs = (RopeByteString) root;
        doBalance(rbs.left);
        doBalance(rbs.right);
      } else {
        throw new IllegalArgumentException(
            "Has a new type of ByteString been created? Found " +
                root.getClass());
      }
    }

    /**
     * Push a string on the balance stack (BAP95).  BAP95 uses an array and
     * calls the elements in the array 'bins'.  We instead use a stack, so the
     * 'bins' of lengths are represented by differences between the elements of
     * minLengthByDepth.
     *
     * <p>If the length bin for our string, and all shorter length bins, are
     * empty, we just push it on the stack.  Otherwise, we need to start
     * concatenating, putting the given string in the "middle" and continuing
     * until we land in an empty length bin that matches the length of our
     * concatenation.
     *
     * @param byteString string to place on the balance stack
     */
    private void insert(ByteString byteString) {
      int depthBin = getDepthBinForLength(byteString.size());
      int binEnd = minLengthByDepth[depthBin + 1];

      // BAP95: Concatenate all trees occupying bins representing the length of
      // our new piece or of shorter pieces, to the extent that is possible.
      // The goal is to clear the bin which our piece belongs in, but that may
      // not be entirely possible if there aren't enough longer bins occupied.
      if (prefixesStack.isEmpty() || prefixesStack.peek().size() >= binEnd) {
        prefixesStack.push(byteString);
      } else {
        int binStart = minLengthByDepth[depthBin];

        // Concatenate the subtrees of shorter length
        ByteString newTree = prefixesStack.pop();
        while (!prefixesStack.isEmpty()
            && prefixesStack.peek().size() < binStart) {
          ByteString left = prefixesStack.pop();
          newTree = new RopeByteString(left, newTree);
        }

        // Concatenate the given string
        newTree = new RopeByteString(newTree, byteString);

        // Continue concatenating until we land in an empty bin
        while (!prefixesStack.isEmpty()) {
          depthBin = getDepthBinForLength(newTree.size());
          binEnd = minLengthByDepth[depthBin + 1];
          if (prefixesStack.peek().size() < binEnd) {
            ByteString left = prefixesStack.pop();
            newTree = new RopeByteString(left, newTree);
          } else {
            break;
          }
        }
        prefixesStack.push(newTree);
      }
    }

    private int getDepthBinForLength(int length) {
      int depth = Arrays.binarySearch(minLengthByDepth, length);
      if (depth < 0) {
        // It wasn't an exact match, so convert to the index of the containing
        // fragment, which is one less even than the insertion point.
        int insertionPoint = -(depth + 1);
        depth = insertionPoint - 1;
      }

      return depth;
    }
  }

  /**
   * This class is a continuable tree traversal, which keeps the state
   * information which would exist on the stack in a recursive traversal instead
   * on a stack of "Bread Crumbs". The maximum depth of the stack in this
   * iterator is the same as the depth of the tree being traversed.
   *
   * <p>This iterator is used to implement
   * {@link RopeByteString#equalsFragments(ByteString)}.
   */
  private static class PieceIterator implements Iterator<LeafByteString> {

    private final Stack<RopeByteString> breadCrumbs =
        new Stack<RopeByteString>();
    private LeafByteString next;

    private PieceIterator(ByteString root) {
      next = getLeafByLeft(root);
    }

    private LeafByteString getLeafByLeft(ByteString root) {
      ByteString pos = root;
      while (pos instanceof RopeByteString) {
        RopeByteString rbs = (RopeByteString) pos;
        breadCrumbs.push(rbs);
        pos = rbs.left;
      }
      return (LeafByteString) pos;
    }

    private LeafByteString getNextNonEmptyLeaf() {
      while (true) {
        // Almost always, we go through this loop exactly once.  However, if
        // we discover an empty string in the rope, we toss it and try again.
        if (breadCrumbs.isEmpty()) {
          return null;
        } else {
          LeafByteString result = getLeafByLeft(breadCrumbs.pop().right);
          if (!result.isEmpty()) {
            return result;
          }
        }
      }
    }

    @Override
    public boolean hasNext() {
      return next != null;
    }

    /**
     * Returns the next item and advances one
     * {@link com.google.protobuf.ByteString.LeafByteString}.
     *
     * @return next non-empty LeafByteString or {@code null}
     */
    @Override
    public LeafByteString next() {
      if (next == null) {
        throw new NoSuchElementException();
      }
      LeafByteString result = next;
      next = getNextNonEmptyLeaf();
      return result;
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }
  }

  // =================================================================
  // Serializable

  private static final long serialVersionUID = 1L;

  Object writeReplace() {
    return ByteString.wrap(toByteArray());
  }

  private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException {
    throw new InvalidObjectException(
        "RopeByteStream instances are not to be serialized directly");
  }

  /**
   * This class is the {@link RopeByteString} equivalent for
   * {@link ByteArrayInputStream}.
   */
  private class RopeInputStream extends InputStream {
    // Iterates through the pieces of the rope
    private PieceIterator pieceIterator;
    // The current piece
    private LeafByteString currentPiece;
    // The size of the current piece
    private int currentPieceSize;
    // The index of the next byte to read in the current piece
    private int currentPieceIndex;
    // The offset of the start of the current piece in the rope byte string
    private int currentPieceOffsetInRope;
    // Offset in the buffer at which user called mark();
    private int mark;

    public RopeInputStream() {
      initialize();
    }

    @Override
    public int read(byte b[], int offset, int length)  {
      if (b == null) {
        throw new NullPointerException();
      } else if (offset < 0 || length < 0 || length > b.length - offset) {
        throw new IndexOutOfBoundsException();
      }
      return readSkipInternal(b, offset, length);
    }

    @Override
    public long skip(long length) {
      if (length < 0) {
        throw new IndexOutOfBoundsException();
      } else if (length > Integer.MAX_VALUE) {
        length = Integer.MAX_VALUE;
      }
      return readSkipInternal(null, 0, (int) length);
    }

    /**
     * Internal implementation of read and skip.  If b != null, then read the
     * next {@code length} bytes into the buffer {@code b} at
     * offset {@code offset}.  If b == null, then skip the next {@code length}
     * bytes.
     * <p>
     * This method assumes that all error checking has already happened.
     * <p>
     * Returns the actual number of bytes read or skipped.
     */
    private int readSkipInternal(byte b[], int offset, int length)  {
      int bytesRemaining = length;
      while (bytesRemaining > 0) {
        advanceIfCurrentPieceFullyRead();
        if (currentPiece == null) {
          if (bytesRemaining == length) {
             // We didn't manage to read anything
             return -1;
           }
          break;
        } else {
          // Copy the bytes from this piece.
          int currentPieceRemaining = currentPieceSize - currentPieceIndex;
          int count = Math.min(currentPieceRemaining, bytesRemaining);
          if (b != null) {
            currentPiece.copyTo(b, currentPieceIndex, offset, count);
            offset += count;
          }
          currentPieceIndex += count;
          bytesRemaining -= count;
        }
      }
       // Return the number of bytes read.
      return length - bytesRemaining;
    }

    @Override
    public int read() throws IOException {
      advanceIfCurrentPieceFullyRead();
      if (currentPiece == null) {
        return -1;
      } else {
        return currentPiece.byteAt(currentPieceIndex++) & 0xFF;
      }
    }

    @Override
    public int available() throws IOException {
      int bytesRead = currentPieceOffsetInRope + currentPieceIndex;
      return RopeByteString.this.size() - bytesRead;
    }

    @Override
    public boolean markSupported() {
      return true;
    }

    @Override
    public void mark(int readAheadLimit) {
      // Set the mark to our position in the byte string
      mark = currentPieceOffsetInRope + currentPieceIndex;
    }

    @Override
    public synchronized void reset() {
      // Just reinitialize and skip the specified number of bytes.
      initialize();
      readSkipInternal(null, 0, mark);
    }

    /** Common initialization code used by both the constructor and reset() */
    private void initialize() {
      pieceIterator = new PieceIterator(RopeByteString.this);
      currentPiece = pieceIterator.next();
      currentPieceSize = currentPiece.size();
      currentPieceIndex = 0;
      currentPieceOffsetInRope = 0;
    }

    /**
     * Skips to the next piece if we have read all the data in the current
     * piece.  Sets currentPiece to null if we have reached the end of the
     * input.
     */
    private void advanceIfCurrentPieceFullyRead() {
      if (currentPiece != null && currentPieceIndex == currentPieceSize) {
        // Generally, we can only go through this loop at most once, since
        // empty strings can't end up in a rope.  But better to test.
        currentPieceOffsetInRope += currentPieceSize;
        currentPieceIndex = 0;
        if (pieceIterator.hasNext()) {
          currentPiece = pieceIterator.next();
          currentPieceSize = currentPiece.size();
        } else {
          currentPiece = null;
          currentPieceSize = 0;
        }
      }
    }
  }
}