aboutsummaryrefslogblamecommitdiff
path: root/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
blob: 093a5f616b6bf6ab19567b7bb7c8ae4af05c1dee (plain) (tree)
1
2
3
4
5
6
7
8
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
                                                      
                                                   
                                                  
  


                                                                         
  








                                                                         
  










                                                                        


                            

                                                          
                                                             

                                 
                                                           
                           
                            
                                        
                           
                          

                                











                                                                               
   
                                                            
                                                                                           
                                                                                                   
 


                                                                   
             
                                                               

     
                                                                


                                                     







                                                                          


                                           


                      
     




                                                                                           
     
                                                                          



                                                    





                                                                                                
     
                                                                                                
                                                       








                                                                             
                                                                       









                                                                             

                                                                   
                                                       

   



                                                                                            
     















                                                                  


     



























                                                                                                   
                                               








                                             




                                                                                                
     



                                                                    
   
 













                                                                                                  

     

                                                         
 

                                                 

   

                                                                      
                                

                                                                                  
 
                                                                    

                                                                                 
 
                                                                    

                                                                                  

                                                                    

                                                                                            


                                                                     

                                                                                   

                                                                       

                                                                                              


                                                                    

                                                                                            

   
                                                                    

                                                                                   

                                                                     

                                                                                             


                                                                     

                                                                                    
 
                                                                       

                                                                                               


                                                                   

                                                                                             


                                                                    

                                                                                               


                                                                  

                                                                                    
 



                                                                                         

                                                                                          

   
                                                                    

                                                                                     

                                                                   

                                                                                        
 
                                                                   

                                                                                        

                                                                   


                                                                                            










                                                                               

                                                                                             
 




                                                                       

   
                                                              

                                                                      

   
                                 

                                                                          

   




                                                                                                  

   
                             

                                                                              

   








                                                                             

                                                                                

                                                                       


                                                                                   




                                                                               


                                                                                               




                                                                     


                                                                                                 
 

                                                                      
                                                    

                                                                           
 
                                                    

                                                                      
 
                                                    

                                                                          


                                                     

                                                                       

                                                      

                                                                            


                                                    

                                                                          

   
                                                    

                                                                       

                                                    

                                                                           


                                                     

                                                                        
 
                                                      

                                                                             


                                                   

                                                                           


                                                    

                                                                             


                                                  

                                                                            

   



                                                                                     
                                                                        


                           
                                                    
                                                               

                                                                         
 
                                                   

                                                                                  
 
                                                   

                                                                                

   
                                                       






















                                                                                          
 

                                                                      

     

                                                                  
     

                                                                              


     

                                                                 
     

                                                                               

   
     

                                                                  
     

                                                                               



                                                                 
                                          
     

                                                                                


     

                                                                  
     

                                                                                 





                                                                  
                                                                               
                                                                      


     







                                                                                
                                                                  
                                         
     

                                                                                





                                                                 
                                                                                 
                                                                        


     

                                                                  
     

                                                                                  



                                                                 
                                        
     

                                                                                



                                                                 
                                         
     

                                                                                  



                                                                 
                                       
     

                                                                                 


     
                                                                  

                                                                                     
     









                                                                                  





                                                                 
                                                                                     
                                                                      


     


                                                                 
                                                                                     






                                                                          
                                                                                          



                                                                           


                                                                  
                                                                                            



                                                                          

                                                                  
     

                                                                                        


     


                                                                 
     



                                                                                                    



                                                                  

                                                                    
     




                                                                        



                                                                  

                                                                    
     




                                                                        

   



                                                                          
                                                                      



                                                                  
                                        
     

                                                            
                                           

                          
                             
     



                                                                 
                          
     
                                                             












                                    



                                                                  
                          
     
                                                             
                                                         

   
     

                                                                 
     
                                                                                           
                        
   
 
     

                                                                  
     
                                                                                            
                        


     

                                                                  
     
                                                             
                                         





                                                                 



















                                                                       



                                                                  
                          
     
                                                              
                                                         


     

                                                                 
     
                                                                                            
                        


     

                                                                  
     
                                                                                             
                        



                                                                 
                                        
     
                                                                                           
                        



                                                                 


                                                                                             
                        



                                                                 

                        
                                                                                            



             








                                                                                         


                                                                 
                                                                








                                                                 
                                                   


     


                                                                           
                                                                          
                                                                      


     


                                                                 
                                                                   
                                                         



                                                                 


                                                                   
                                                         






                                                                        
                                                             


     

                                                                           
     
                                                                      


                                                                      
                                                               
                                                             


     







                                                                           
     


                                                 

   
     







                                                                           
     


                                                   

   

                                                                      



                                                                          
                                                  




                                                                    
                                  







                                                                             
                                        
                           
                                                                                 








                                                                           

                                                                       


                                                                                   
                           


                     



                                                    

                                          
     



                                                                     

   




                                                                            
                                             
 
                                                                      
 


                                                                                           
 



                                                                                                    
 




                                                                               






                                           


     







                                                                      
                                                                                                   


                                                           

   





                                               
                                                                                 




















                                                                                      




                                                            

                                                       
     
             

                                                                    


     


                                                       
     
             

                                                                     

   








                                                                          
                                         







                                                                         
                                                        
                                         

   
     
                                          
    
                                                        
     
             

                                                                                


     
                                          
    
                                                        
     
             



































































































































































                                                                                                    

                                                                                 















                                                                          

                                     
                                                                 

                   
                                                                                   















                                                                  
                                                                                   












                                                                       
                                                                                 





                                                                          

                                      
                                                                 

                   
                                                                                         















                                                                        
                                                                                   
















                                                                        
                                                                                 









                                                                                      
                                                                                      















                                                                                          
                                                                                      





























































                                                                                                    
                                                                  


                                        
                                           













                                                                                 

                                                                                                
     
                                                                             



                                            
                                             













































































































































































































































































































                                                                                                    






















                                                                                                 
                                                        














































































































































































                                                                                                   
                               
































                                                                                 
                               















                                                                                   
                                                             

































































































                                                                                                    



































































                                                                                              
                                          

                                     
                                                                 

                  
                                                                                   


                         
                                                   





















                                                                                              
                                          

                                      
                                                                 

                  
                                                                                         


                         
                                                   
























                                                                                              
                                        














                                                                                              
                                        







































                                                                                                
                                                          












                                                                                          
                                                          




























































































































                                                                                                    
                                        










                                                                        
                                        





















































































































































                                                                                                  
                                                          












                                                                                          
                                                          




























































































































                                                                                                    
                                        










                                                                        
                                        


































































































































































                                                                                                    
 
// 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 static com.google.protobuf.WireFormat.FIXED32_SIZE;
import static com.google.protobuf.WireFormat.FIXED64_SIZE;
import static com.google.protobuf.WireFormat.MAX_VARINT_SIZE;
import static java.lang.Math.max;

import com.google.protobuf.Utf8.UnpairedSurrogateException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Encodes and writes protocol message fields.
 *
 * <p>This class contains two kinds of methods:  methods that write specific
 * protocol message constructs and field types (e.g. {@link #writeTag} and
 * {@link #writeInt32}) and methods that write low-level values (e.g.
 * {@link #writeRawVarint32} and {@link #writeRawBytes}).  If you are
 * writing encoded protocol messages, you should use the former methods, but if
 * you are writing some other format of your own design, use the latter.
 *
 * <p>This class is totally unsynchronized.
 */
public abstract class CodedOutputStream extends ByteOutput {
  private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName());
  private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = UnsafeUtil.hasUnsafeArrayOperations();

  /**
   * @deprecated Use {@link #computeFixed32SizeNoTag(int)} instead.
   */
  @Deprecated
  public static final int LITTLE_ENDIAN_32_SIZE = FIXED32_SIZE;

  /**
   * The buffer size used in {@link #newInstance(OutputStream)}.
   */
  public static final int DEFAULT_BUFFER_SIZE = 4096;

  /**
   * Returns the buffer size to efficiently write dataLength bytes to this
   * CodedOutputStream. Used by AbstractMessageLite.
   *
   * @return the buffer size to efficiently write dataLength bytes to this
   *         CodedOutputStream.
   */
  static int computePreferredBufferSize(int dataLength) {
    if (dataLength > DEFAULT_BUFFER_SIZE) {
      return DEFAULT_BUFFER_SIZE;
    }
    return dataLength;
  }

  /**
   * Create a new {@code CodedOutputStream} wrapping the given {@code OutputStream}.
   *
   * <p> NOTE: The provided {@link OutputStream} <strong>MUST NOT</strong> retain access or
   * modify the provided byte arrays. Doing so may result in corrupted data, which would be
   * difficult to debug.
   */
  public static CodedOutputStream newInstance(final OutputStream output) {
    return newInstance(output, DEFAULT_BUFFER_SIZE);
  }

  /**
   * Create a new {@code CodedOutputStream} wrapping the given {@code OutputStream} with a given
   * buffer size.
   *
   * <p> NOTE: The provided {@link OutputStream} <strong>MUST NOT</strong> retain access or
   * modify the provided byte arrays. Doing so may result in corrupted data, which would be
   * difficult to debug.
   */
  public static CodedOutputStream newInstance(final OutputStream output, final int bufferSize) {
    return new OutputStreamEncoder(output, bufferSize);
  }

  /**
   * Create a new {@code CodedOutputStream} that writes directly to the given
   * byte array.  If more bytes are written than fit in the array,
   * {@link OutOfSpaceException} will be thrown.  Writing directly to a flat
   * array is faster than writing to an {@code OutputStream}.  See also
   * {@link ByteString#newCodedBuilder}.
   */
  public static CodedOutputStream newInstance(final byte[] flatArray) {
    return newInstance(flatArray, 0, flatArray.length);
  }

  /**
   * Create a new {@code CodedOutputStream} that writes directly to the given
   * byte array slice.  If more bytes are written than fit in the slice,
   * {@link OutOfSpaceException} will be thrown.  Writing directly to a flat
   * array is faster than writing to an {@code OutputStream}.  See also
   * {@link ByteString#newCodedBuilder}.
   */
  public static CodedOutputStream newInstance(
      final byte[] flatArray, final int offset, final int length) {
    return new ArrayEncoder(flatArray, offset, length);
  }

  /** Create a new {@code CodedOutputStream} that writes to the given {@link ByteBuffer}. */
  public static CodedOutputStream newInstance(ByteBuffer buffer) {
    if (buffer.hasArray()) {
      return new HeapNioEncoder(buffer);
    }
    if (buffer.isDirect() && !buffer.isReadOnly()) {
      return UnsafeDirectNioEncoder.isSupported()
          ? newUnsafeInstance(buffer)
          : newSafeInstance(buffer);
    }
    throw new IllegalArgumentException("ByteBuffer is read-only");
  }

  /** For testing purposes only. */
  static CodedOutputStream newUnsafeInstance(ByteBuffer buffer) {
    return new UnsafeDirectNioEncoder(buffer);
  }

  /** For testing purposes only. */
  static CodedOutputStream newSafeInstance(ByteBuffer buffer) {
    return new SafeDirectNioEncoder(buffer);
  }

  /**
   * Configures serialization to be deterministic.
   *
   * <p>The deterministic serialization guarantees that for a given binary, equal (defined by the
   * {@code equals()} methods in protos) messages will always be serialized to the same bytes. This
   * implies:
   *
   * <ul>
   * <li>repeated serialization of a message will return the same bytes
   * <li>different processes of the same binary (which may be executing on different machines) will
   *     serialize equal messages to the same bytes.
   * </ul>
   *
   * <p>Note the deterministic serialization is NOT canonical across languages; it is also unstable
   * across different builds with schema changes due to unknown fields. Users who need canonical
   * serialization, e.g. persistent storage in a canonical form, fingerprinting, etc, should define
   * their own canonicalization specification and implement the serializer using reflection APIs
   * rather than relying on this API.
   *
   * <p> Once set, the serializer will:  (Note this is an implementation detail and may subject to
   * change in the future)
   *
   * <ul>
   * <li> sort map entries by keys in lexicographical order or numerical order. Note: For string
   *     keys, the order is based on comparing the Unicode value of each character in the strings.
   *     The order may be different from the deterministic serialization in other languages where
   *     maps are sorted on the lexicographical order of the UTF8 encoded keys.
   * </ul>
   */
  public void useDeterministicSerialization() {
    serializationDeterministic = true;
  }

  boolean isSerializationDeterministic() {
    return serializationDeterministic;
  }
  private boolean serializationDeterministic;

  /**
   * Create a new {@code CodedOutputStream} that writes to the given {@link ByteBuffer}.
   *
   * @deprecated the size parameter is no longer used since use of an internal buffer is useless
   * (and wasteful) when writing to a {@link ByteBuffer}. Use {@link #newInstance(ByteBuffer)}
   * instead.
   */
  @Deprecated
  public static CodedOutputStream newInstance(ByteBuffer byteBuffer,
      @SuppressWarnings("unused") int unused) {
    return newInstance(byteBuffer);
  }

  /**
   * Create a new {@code CodedOutputStream} that writes to the provided {@link ByteOutput}.
   *
   * <p> NOTE: The {@link ByteOutput} <strong>MUST NOT</strong> modify the provided buffers. Doing
   * so may result in corrupted data, which would be difficult to debug.
   *
   * @param byteOutput the output target for encoded bytes.
   * @param bufferSize the size of the internal scratch buffer to be used for string encoding.
   * Setting this to {@code 0} will disable buffering, requiring an allocation for each encoded
   * string.
   */
  static CodedOutputStream newInstance(ByteOutput byteOutput, int bufferSize) {
    if (bufferSize < 0) {
      throw new IllegalArgumentException("bufferSize must be positive");
    }

    return new ByteOutputEncoder(byteOutput, bufferSize);
  }

  // Disallow construction outside of this class.
  private CodedOutputStream() {
  }

  // -----------------------------------------------------------------

  /** Encode and write a tag. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeTag(int fieldNumber, int wireType) throws IOException;

  /** Write an {@code int32} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeInt32(int fieldNumber, int value) throws IOException;

  /** Write a {@code uint32} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeUInt32(int fieldNumber, int value) throws IOException;

  /** Write a {@code sint32} field, including tag, to the stream. */
  public final void writeSInt32(final int fieldNumber, final int value) throws IOException {
    writeUInt32(fieldNumber, encodeZigZag32(value));
  }

  /** Write a {@code fixed32} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeFixed32(int fieldNumber, int value) throws IOException;

  /** Write an {@code sfixed32} field, including tag, to the stream. */
  public final void writeSFixed32(final int fieldNumber, final int value) throws IOException {
    writeFixed32(fieldNumber, value);
  }

  /** Write an {@code int64} field, including tag, to the stream. */
  public final void writeInt64(final int fieldNumber, final long value) throws IOException {
    writeUInt64(fieldNumber, value);
  }

  /** Write a {@code uint64} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeUInt64(int fieldNumber, long value) throws IOException;

  /** Write an {@code sint64} field, including tag, to the stream. */
  public final void writeSInt64(final int fieldNumber, final long value) throws IOException {
    writeUInt64(fieldNumber, encodeZigZag64(value));
  }

  /** Write a {@code fixed64} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeFixed64(int fieldNumber, long value) throws IOException;

  /** Write an {@code sfixed64} field, including tag, to the stream. */
  public final void writeSFixed64(final int fieldNumber, final long value) throws IOException {
    writeFixed64(fieldNumber, value);
  }

  /** Write a {@code float} field, including tag, to the stream. */
  public final void writeFloat(final int fieldNumber, final float value) throws IOException {
    writeFixed32(fieldNumber, Float.floatToRawIntBits(value));
  }

  /** Write a {@code double} field, including tag, to the stream. */
  public final void writeDouble(final int fieldNumber, final double value) throws IOException {
    writeFixed64(fieldNumber, Double.doubleToRawLongBits(value));
  }

  /** Write a {@code bool} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeBool(int fieldNumber, boolean value) throws IOException;

  /**
   * Write an enum field, including tag, to the stream. The provided value is the numeric
   * value used to represent the enum value on the wire (not the enum ordinal value).
   */
  public final void writeEnum(final int fieldNumber, final int value) throws IOException {
    writeInt32(fieldNumber, value);
  }

  /** Write a {@code string} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeString(int fieldNumber, String value) throws IOException;

  /** Write a {@code bytes} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeBytes(int fieldNumber, ByteString value) throws IOException;

  /** Write a {@code bytes} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeByteArray(int fieldNumber, byte[] value) throws IOException;

  /** Write a {@code bytes} field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeByteArray(int fieldNumber, byte[] value, int offset, int length)
      throws IOException;

  /**
   * Write a {@code bytes} field, including tag, to the stream.
   * This method will write all content of the ByteBuffer regardless of the
   * current position and limit (i.e., the number of bytes to be written is
   * value.capacity(), not value.remaining()). Furthermore, this method doesn't
   * alter the state of the passed-in ByteBuffer. Its position, limit, mark,
   * etc. will remain unchanged. If you only want to write the remaining bytes
   * of a ByteBuffer, you can call
   * {@code writeByteBuffer(fieldNumber, byteBuffer.slice())}.
   */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeByteBuffer(int fieldNumber, ByteBuffer value) throws IOException;

  /**
   * Write a single byte.
   */
  public final void writeRawByte(final byte value) throws IOException {
    write(value);
  }

  /** Write a single byte, represented by an integer value. */
  public final void writeRawByte(final int value) throws IOException {
    write((byte) value);
  }

  /** Write an array of bytes. */
  public final void writeRawBytes(final byte[] value) throws IOException {
    write(value, 0, value.length);
  }

  /**
   * Write part of an array of bytes.
   */
  public final void writeRawBytes(final byte[] value, int offset, int length) throws IOException {
    write(value, offset, length);
  }

  /** Write a byte string. */
  public final void writeRawBytes(final ByteString value) throws IOException {
    value.writeTo(this);
  }

  /**
   * Write a ByteBuffer. This method will write all content of the ByteBuffer
   * regardless of the current position and limit (i.e., the number of bytes
   * to be written is value.capacity(), not value.remaining()). Furthermore,
   * this method doesn't alter the state of the passed-in ByteBuffer. Its
   * position, limit, mark, etc. will remain unchanged. If you only want to
   * write the remaining bytes of a ByteBuffer, you can call
   * {@code writeRawBytes(byteBuffer.slice())}.
   */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeRawBytes(final ByteBuffer value) throws IOException;

  /** Write an embedded message field, including tag, to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeMessage(final int fieldNumber, final MessageLite value)
      throws IOException;

  /**
   * Write a MessageSet extension field to the stream.  For historical reasons,
   * the wire format differs from normal fields.
   */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
      throws IOException;

  /**
   * Write an unparsed MessageSet extension field to the stream.  For
   * historical reasons, the wire format differs from normal fields.
   */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeRawMessageSetExtension(final int fieldNumber, final ByteString value)
      throws IOException;

  // -----------------------------------------------------------------

  /** Write an {@code int32} field to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeInt32NoTag(final int value) throws IOException;

  /** Write a {@code uint32} field to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeUInt32NoTag(int value) throws IOException;

  /** Write a {@code sint32} field to the stream. */
  public final void writeSInt32NoTag(final int value) throws IOException {
    writeUInt32NoTag(encodeZigZag32(value));
  }

  /** Write a {@code fixed32} field to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeFixed32NoTag(int value) throws IOException;

  /** Write a {@code sfixed32} field to the stream. */
  public final void writeSFixed32NoTag(final int value) throws IOException {
    writeFixed32NoTag(value);
  }

  /** Write an {@code int64} field to the stream. */
  public final void writeInt64NoTag(final long value) throws IOException {
    writeUInt64NoTag(value);
  }

  /** Write a {@code uint64} field to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeUInt64NoTag(long value) throws IOException;

  /** Write a {@code sint64} field to the stream. */
  public final void writeSInt64NoTag(final long value) throws IOException {
    writeUInt64NoTag(encodeZigZag64(value));
  }

  /** Write a {@code fixed64} field to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeFixed64NoTag(long value) throws IOException;

  /** Write a {@code sfixed64} field to the stream. */
  public final void writeSFixed64NoTag(final long value) throws IOException {
    writeFixed64NoTag(value);
  }

  /** Write a {@code float} field to the stream. */
  public final void writeFloatNoTag(final float value) throws IOException {
    writeFixed32NoTag(Float.floatToRawIntBits(value));
  }

  /** Write a {@code double} field to the stream. */
  public final void writeDoubleNoTag(final double value) throws IOException {
    writeFixed64NoTag(Double.doubleToRawLongBits(value));
  }

  /** Write a {@code bool} field to the stream. */
  public final void writeBoolNoTag(final boolean value) throws IOException {
    write((byte) (value ? 1 : 0));
  }

  /**
   * Write an enum field to the stream. The provided value is the numeric
   * value used to represent the enum value on the wire (not the enum ordinal value).
   */
  public final void writeEnumNoTag(final int value) throws IOException {
    writeInt32NoTag(value);
  }

  /** Write a {@code string} field to the stream. */
  // TODO(dweis): Document behavior on ill-formed UTF-16 input.
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeStringNoTag(String value) throws IOException;

  /** Write a {@code bytes} field to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeBytesNoTag(final ByteString value) throws IOException;

  /** Write a {@code bytes} field to the stream. */
  public final void writeByteArrayNoTag(final byte[] value) throws IOException {
    writeByteArrayNoTag(value, 0, value.length);
  }

  /** Write an embedded message field to the stream. */
  // Abstract to avoid overhead of additional virtual method calls.
  public abstract void writeMessageNoTag(final MessageLite value) throws IOException;

  //=================================================================

  @ExperimentalApi
  @Override
  public abstract void write(byte value) throws IOException;

  @ExperimentalApi
  @Override
  public abstract void write(byte[] value, int offset, int length) throws IOException;

  @ExperimentalApi
  @Override
  public abstract void writeLazy(byte[] value, int offset, int length) throws IOException;

  @Override
  public abstract void write(ByteBuffer value) throws IOException;

  @ExperimentalApi
  @Override
  public abstract void writeLazy(ByteBuffer value) throws IOException;

  // =================================================================
  // =================================================================

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code int32} field, including tag.
   */
  public static int computeInt32Size(final int fieldNumber, final int value) {
    return computeTagSize(fieldNumber) + computeInt32SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code uint32} field, including tag.
   */
  public static int computeUInt32Size(final int fieldNumber, final int value) {
    return computeTagSize(fieldNumber) + computeUInt32SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code sint32} field, including tag.
   */
  public static int computeSInt32Size(final int fieldNumber, final int value) {
    return computeTagSize(fieldNumber) + computeSInt32SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code fixed32} field, including tag.
   */
  public static int computeFixed32Size(final int fieldNumber, final int value) {
    return computeTagSize(fieldNumber) + computeFixed32SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code sfixed32} field, including tag.
   */
  public static int computeSFixed32Size(final int fieldNumber, final int value) {
    return computeTagSize(fieldNumber) + computeSFixed32SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code int64} field, including tag.
   */
  public static int computeInt64Size(final int fieldNumber, final long value) {
    return computeTagSize(fieldNumber) + computeInt64SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code uint64} field, including tag.
   */
  public static int computeUInt64Size(final int fieldNumber, final long value) {
    return computeTagSize(fieldNumber) + computeUInt64SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code sint64} field, including tag.
   */
  public static int computeSInt64Size(final int fieldNumber, final long value) {
    return computeTagSize(fieldNumber) + computeSInt64SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code fixed64} field, including tag.
   */
  public static int computeFixed64Size(final int fieldNumber, final long value) {
    return computeTagSize(fieldNumber) + computeFixed64SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code sfixed64} field, including tag.
   */
  public static int computeSFixed64Size(final int fieldNumber, final long value) {
    return computeTagSize(fieldNumber) + computeSFixed64SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code float} field, including tag.
   */
  public static int computeFloatSize(final int fieldNumber, final float value) {
    return computeTagSize(fieldNumber) + computeFloatSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code double} field, including tag.
   */
  public static int computeDoubleSize(final int fieldNumber, final double value) {
    return computeTagSize(fieldNumber) + computeDoubleSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code bool} field, including tag.
   */
  public static int computeBoolSize(final int fieldNumber, final boolean value) {
    return computeTagSize(fieldNumber) + computeBoolSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * enum field, including tag. The provided value is the numeric
   * value used to represent the enum value on the wire (not the enum ordinal value).
   */
  public static int computeEnumSize(final int fieldNumber, final int value) {
    return computeTagSize(fieldNumber) + computeEnumSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code string} field, including tag.
   */
  public static int computeStringSize(final int fieldNumber, final String value) {
    return computeTagSize(fieldNumber) + computeStringSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code bytes} field, including tag.
   */
  public static int computeBytesSize(final int fieldNumber, final ByteString value) {
    return computeTagSize(fieldNumber) + computeBytesSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code bytes} field, including tag.
   */
  public static int computeByteArraySize(final int fieldNumber, final byte[] value) {
    return computeTagSize(fieldNumber) + computeByteArraySizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code bytes} field, including tag.
   */
  public static int computeByteBufferSize(final int fieldNumber, final ByteBuffer value) {
    return computeTagSize(fieldNumber) + computeByteBufferSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * embedded message in lazy field, including tag.
   */
  public static int computeLazyFieldSize(final int fieldNumber, final LazyFieldLite value) {
    return computeTagSize(fieldNumber) + computeLazyFieldSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * embedded message field, including tag.
   */
  public static int computeMessageSize(final int fieldNumber, final MessageLite value) {
    return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * MessageSet extension to the stream.  For historical reasons,
   * the wire format differs from normal fields.
   */
  public static int computeMessageSetExtensionSize(final int fieldNumber, final MessageLite value) {
    return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2
        + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber)
        + computeMessageSize(WireFormat.MESSAGE_SET_MESSAGE, value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * unparsed MessageSet extension field to the stream.  For
   * historical reasons, the wire format differs from normal fields.
   */
  public static int computeRawMessageSetExtensionSize(
      final int fieldNumber, final ByteString value) {
    return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2
        + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber)
        + computeBytesSize(WireFormat.MESSAGE_SET_MESSAGE, value);
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * lazily parsed MessageSet extension field to the stream.  For
   * historical reasons, the wire format differs from normal fields.
   */
  public static int computeLazyFieldMessageSetExtensionSize(
      final int fieldNumber, final LazyFieldLite value) {
    return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2
        + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber)
        + computeLazyFieldSize(WireFormat.MESSAGE_SET_MESSAGE, value);
  }

  // -----------------------------------------------------------------

  /** Compute the number of bytes that would be needed to encode a tag. */
  public static int computeTagSize(final int fieldNumber) {
    return computeUInt32SizeNoTag(WireFormat.makeTag(fieldNumber, 0));
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code int32} field, including tag.
   */
  public static int computeInt32SizeNoTag(final int value) {
    if (value >= 0) {
      return computeUInt32SizeNoTag(value);
    } else {
      // Must sign-extend.
      return MAX_VARINT_SIZE;
    }
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code uint32} field.
   */
  public static int computeUInt32SizeNoTag(final int value) {
    if ((value & (~0 <<  7)) == 0) {
      return 1;
    }
    if ((value & (~0 << 14)) == 0) {
      return 2;
    }
    if ((value & (~0 << 21)) == 0) {
      return 3;
    }
    if ((value & (~0 << 28)) == 0) {
      return 4;
    }
    return 5;
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code sint32} field.
   */
  public static int computeSInt32SizeNoTag(final int value) {
    return computeUInt32SizeNoTag(encodeZigZag32(value));
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code fixed32} field.
   */
  public static int computeFixed32SizeNoTag(@SuppressWarnings("unused") final int unused) {
    return FIXED32_SIZE;
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code sfixed32} field.
   */
  public static int computeSFixed32SizeNoTag(@SuppressWarnings("unused") final int unused) {
    return FIXED32_SIZE;
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code int64} field, including tag.
   */
  public static int computeInt64SizeNoTag(final long value) {
    return computeUInt64SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code uint64} field, including tag.
   */
  public static int computeUInt64SizeNoTag(long value) {
    // handle two popular special cases up front ...
    if ((value & (~0L << 7)) == 0L) {
      return 1;
    }
    if (value < 0L) {
      return 10;
    }
    // ... leaving us with 8 remaining, which we can divide and conquer
    int n = 2;
    if ((value & (~0L << 35)) != 0L) {
      n += 4; value >>>= 28;
    }
    if ((value & (~0L << 21)) != 0L) {
      n += 2; value >>>= 14;
    }
    if ((value & (~0L << 14)) != 0L) {
      n += 1;
    }
    return n;
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code sint64} field.
   */
  public static int computeSInt64SizeNoTag(final long value) {
    return computeUInt64SizeNoTag(encodeZigZag64(value));
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code fixed64} field.
   */
  public static int computeFixed64SizeNoTag(@SuppressWarnings("unused") final long unused) {
    return FIXED64_SIZE;
  }

  /**
   * Compute the number of bytes that would be needed to encode an
   * {@code sfixed64} field.
   */
  public static int computeSFixed64SizeNoTag(@SuppressWarnings("unused") final long unused) {
    return FIXED64_SIZE;
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code float} field, including tag.
   */
  public static int computeFloatSizeNoTag(@SuppressWarnings("unused") final float unused) {
    return FIXED32_SIZE;
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code double} field, including tag.
   */
  public static int computeDoubleSizeNoTag(@SuppressWarnings("unused") final double unused) {
    return FIXED64_SIZE;
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code bool} field.
   */
  public static int computeBoolSizeNoTag(@SuppressWarnings("unused") final boolean unused) {
    return 1;
  }

  /**
   * Compute the number of bytes that would be needed to encode an enum field.
   * The provided value is the numeric value used to represent the enum value on the wire
   * (not the enum ordinal value).
   */
  public static int computeEnumSizeNoTag(final int value) {
    return computeInt32SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code string} field.
   */
  public static int computeStringSizeNoTag(final String value) {
    int length;
    try {
      length = Utf8.encodedLength(value);
    } catch (UnpairedSurrogateException e) {
      // TODO(dweis): Consider using nio Charset methods instead.
      final byte[] bytes = value.getBytes(Internal.UTF_8);
      length = bytes.length;
    }

    return computeLengthDelimitedFieldSize(length);
  }

  /**
   * Compute the number of bytes that would be needed to encode an embedded
   * message stored in lazy field.
   */
  public static int computeLazyFieldSizeNoTag(final LazyFieldLite value) {
    return computeLengthDelimitedFieldSize(value.getSerializedSize());
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code bytes} field.
   */
  public static int computeBytesSizeNoTag(final ByteString value) {
    return computeLengthDelimitedFieldSize(value.size());
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code bytes} field.
   */
  public static int computeByteArraySizeNoTag(final byte[] value) {
    return computeLengthDelimitedFieldSize(value.length);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code bytes} field.
   */
  public static int computeByteBufferSizeNoTag(final ByteBuffer value) {
    return computeLengthDelimitedFieldSize(value.capacity());
  }

  /**
   * Compute the number of bytes that would be needed to encode an embedded
   * message field.
   */
  public static int computeMessageSizeNoTag(final MessageLite value) {
    return computeLengthDelimitedFieldSize(value.getSerializedSize());
  }

  static int computeLengthDelimitedFieldSize(int fieldLength) {
    return computeUInt32SizeNoTag(fieldLength) + fieldLength;
  }

  /**
   * Encode a ZigZag-encoded 32-bit value.  ZigZag encodes signed integers
   * into values that can be efficiently encoded with varint.  (Otherwise,
   * negative values must be sign-extended to 64 bits to be varint encoded,
   * thus always taking 10 bytes on the wire.)
   *
   * @param n A signed 32-bit integer.
   * @return An unsigned 32-bit integer, stored in a signed int because
   *         Java has no explicit unsigned support.
   */
  public static int encodeZigZag32(final int n) {
    // Note:  the right-shift must be arithmetic
    return (n << 1) ^ (n >> 31);
  }

  /**
   * Encode a ZigZag-encoded 64-bit value.  ZigZag encodes signed integers
   * into values that can be efficiently encoded with varint.  (Otherwise,
   * negative values must be sign-extended to 64 bits to be varint encoded,
   * thus always taking 10 bytes on the wire.)
   *
   * @param n A signed 64-bit integer.
   * @return An unsigned 64-bit integer, stored in a signed int because
   *         Java has no explicit unsigned support.
   */
  public static long encodeZigZag64(final long n) {
    // Note:  the right-shift must be arithmetic
    return (n << 1) ^ (n >> 63);
  }

  // =================================================================

  /**
   * Flushes the stream and forces any buffered bytes to be written.  This
   * does not flush the underlying OutputStream.
   */
  public abstract void flush() throws IOException;

  /**
   * If writing to a flat array, return the space left in the array.
   * Otherwise, throws {@code UnsupportedOperationException}.
   */
  public abstract int spaceLeft();

  /**
   * Verifies that {@link #spaceLeft()} returns zero.  It's common to create
   * a byte array that is exactly big enough to hold a message, then write to
   * it with a {@code CodedOutputStream}.  Calling {@code checkNoSpaceLeft()}
   * after writing verifies that the message was actually as big as expected,
   * which can help catch bugs.
   */
  public final void checkNoSpaceLeft() {
    if (spaceLeft() != 0) {
      throw new IllegalStateException("Did not write as much data as expected.");
    }
  }

  /**
   * If you create a CodedOutputStream around a simple flat array, you must
   * not attempt to write more bytes than the array has space.  Otherwise,
   * this exception will be thrown.
   */
  public static class OutOfSpaceException extends IOException {
    private static final long serialVersionUID = -6947486886997889499L;

    private static final String MESSAGE =
        "CodedOutputStream was writing to a flat byte array and ran out of space.";

    OutOfSpaceException() {
      super(MESSAGE);
    }

    OutOfSpaceException(String explanationMessage) {
      super(MESSAGE + ": " + explanationMessage);
    }

    OutOfSpaceException(Throwable cause) {
      super(MESSAGE, cause);
    }

    OutOfSpaceException(String explanationMessage, Throwable cause) {
      super(MESSAGE + ": " + explanationMessage, cause);
    }
  }

  /**
   * Get the total number of bytes successfully written to this stream.  The
   * returned value is not guaranteed to be accurate if exceptions have been
   * found in the middle of writing.
   */
  public abstract int getTotalBytesWritten();

  // =================================================================

  /** Write a {@code bytes} field to the stream. Visible for testing. */
  abstract void writeByteArrayNoTag(final byte[] value, final int offset, final int length)
      throws IOException;

  final void inefficientWriteStringNoTag(String value, UnpairedSurrogateException cause)
      throws IOException {
    logger.log(Level.WARNING,
        "Converting ill-formed UTF-16. Your Protocol Buffer will not round trip correctly!", cause);

    // Unfortunately there does not appear to be any way to tell Java to encode
    // UTF-8 directly into our buffer, so we have to let it create its own byte
    // array and then copy.
    // TODO(dweis): Consider using nio Charset methods instead.
    final byte[] bytes = value.getBytes(Internal.UTF_8);
    try {
      writeUInt32NoTag(bytes.length);
      writeLazy(bytes, 0, bytes.length);
    } catch (IndexOutOfBoundsException e) {
      throw new OutOfSpaceException(e);
    } catch (OutOfSpaceException e) {
      throw e;
    }
  }

  // =================================================================

  /**
   * Write a {@code group} field, including tag, to the stream.
   *
   * @deprecated groups are deprecated.
   */
  @Deprecated
  public final void writeGroup(final int fieldNumber, final MessageLite value) throws IOException {
    writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP);
    writeGroupNoTag(value);
    writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP);
  }

  /**
   * Write a {@code group} field to the stream.
   *
   * @deprecated groups are deprecated.
   */
  @Deprecated
  public final void writeGroupNoTag(final MessageLite value) throws IOException {
    value.writeTo(this);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code group} field, including tag.
   *
   * @deprecated groups are deprecated.
   */
  @Deprecated
  public static int computeGroupSize(final int fieldNumber, final MessageLite value) {
    return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a
   * {@code group} field.
   */
  @Deprecated
  public static int computeGroupSizeNoTag(final MessageLite value) {
    return value.getSerializedSize();
  }

  /**
   * Encode and write a varint.  {@code value} is treated as
   * unsigned, so it won't be sign-extended if negative.
   *
   * @deprecated use {@link #writeUInt32NoTag} instead.
   */
  @Deprecated
  public final void writeRawVarint32(int value) throws IOException {
    writeUInt32NoTag(value);
  }

  /**
   * Encode and write a varint.
   *
   * @deprecated use {@link #writeUInt64NoTag} instead.
   */
  @Deprecated
  public final void writeRawVarint64(long value) throws IOException {
    writeUInt64NoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a varint.
   * {@code value} is treated as unsigned, so it won't be sign-extended if
   * negative.
   *
   * @deprecated use {@link #computeUInt32SizeNoTag(int)} instead.
   */
  @Deprecated
  public static int computeRawVarint32Size(final int value) {
    return computeUInt32SizeNoTag(value);
  }

  /**
   * Compute the number of bytes that would be needed to encode a varint.
   *
   * @deprecated use {@link #computeUInt64SizeNoTag(long)} instead.
   */
  @Deprecated
  public static int computeRawVarint64Size(long value) {
    return computeUInt64SizeNoTag(value);
  }

  /**
   * Write a little-endian 32-bit integer.
   *
   * @deprecated Use {@link #writeFixed32NoTag} instead.
   */
  @Deprecated
  public final void writeRawLittleEndian32(final int value) throws IOException {
    writeFixed32NoTag(value);
  }

  /**
   * Write a little-endian 64-bit integer.
   *
   * @deprecated Use {@link #writeFixed64NoTag} instead.
   */
  @Deprecated
  public final void writeRawLittleEndian64(final long value) throws IOException {
    writeFixed64NoTag(value);
  }

  // =================================================================

  /**
   * A {@link CodedOutputStream} that writes directly to a byte array.
   */
  private static class ArrayEncoder extends CodedOutputStream {
    private final byte[] buffer;
    private final int offset;
    private final int limit;
    private int position;

    ArrayEncoder(byte[] buffer, int offset, int length) {
      if (buffer == null) {
        throw new NullPointerException("buffer");
      }
      if ((offset | length | (buffer.length - (offset + length))) < 0) {
        throw new IllegalArgumentException(String.format(
            "Array range is invalid. Buffer.length=%d, offset=%d, length=%d",
            buffer.length, offset, length));
      }
      this.buffer = buffer;
      this.offset = offset;
      position = offset;
      limit = offset + length;
    }

    @Override
    public final void writeTag(final int fieldNumber, final int wireType) throws IOException {
      writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType));
    }

    @Override
    public final void writeInt32(final int fieldNumber, final int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeInt32NoTag(value);
    }

    @Override
    public final void writeUInt32(final int fieldNumber, final int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeUInt32NoTag(value);
    }

    @Override
    public final void writeFixed32(final int fieldNumber, final int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32);
      writeFixed32NoTag(value);
    }

    @Override
    public final void writeUInt64(final int fieldNumber, final long value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeUInt64NoTag(value);
    }

    @Override
    public final void writeFixed64(final int fieldNumber, final long value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64);
      writeFixed64NoTag(value);
    }

    @Override
    public final void writeBool(final int fieldNumber, final boolean value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      write((byte) (value ? 1 : 0));
    }

    @Override
    public final void writeString(final int fieldNumber, final String value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeStringNoTag(value);
    }

    @Override
    public final void writeBytes(final int fieldNumber, final ByteString value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeBytesNoTag(value);
    }

    @Override
    public final void writeByteArray(final int fieldNumber, final byte[] value) throws IOException {
      writeByteArray(fieldNumber, value, 0, value.length);
    }

    @Override
    public final void writeByteArray(
        final int fieldNumber, final byte[] value, final int offset, final int length)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeByteArrayNoTag(value, offset, length);
    }

    @Override
    public final void writeByteBuffer(final int fieldNumber, final ByteBuffer value)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeUInt32NoTag(value.capacity());
      writeRawBytes(value);
    }

    @Override
    public final void writeBytesNoTag(final ByteString value) throws IOException {
      writeUInt32NoTag(value.size());
      value.writeTo(this);
    }

    @Override
    public final void writeByteArrayNoTag(final byte[] value, int offset, int length)
        throws IOException {
      writeUInt32NoTag(length);
      write(value, offset, length);
    }

    @Override
    public final void writeRawBytes(final ByteBuffer value) throws IOException {
      if (value.hasArray()) {
        write(value.array(), value.arrayOffset(), value.capacity());
      } else {
        ByteBuffer duplicated = value.duplicate();
        duplicated.clear();
        write(duplicated);
      }
    }

    @Override
    public final void writeMessage(final int fieldNumber, final MessageLite value)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeMessageNoTag(value);
    }

    @Override
    public final void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
        throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public final void writeRawMessageSetExtension(final int fieldNumber, final ByteString value)
        throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public final void writeMessageNoTag(final MessageLite value) throws IOException {
      writeUInt32NoTag(value.getSerializedSize());
      value.writeTo(this);
    }

    @Override
    public final void write(byte value) throws IOException {
      try {
        buffer[position++] = value;
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
      }
    }

    @Override
    public final void writeInt32NoTag(int value) throws IOException {
      if (value >= 0) {
        writeUInt32NoTag(value);
      } else {
        // Must sign-extend.
        writeUInt64NoTag(value);
      }
    }

    @Override
    public final void writeUInt32NoTag(int value) throws IOException {
      if (HAS_UNSAFE_ARRAY_OPERATIONS && spaceLeft() >= MAX_VARINT_SIZE) {
        while (true) {
          if ((value & ~0x7F) == 0) {
            UnsafeUtil.putByte(buffer, position++, (byte) value);
            return;
          } else {
            UnsafeUtil.putByte(buffer, position++, (byte) ((value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
      } else {
        try {
          while (true) {
            if ((value & ~0x7F) == 0) {
              buffer[position++] = (byte) value;
              return;
            } else {
              buffer[position++] = (byte) ((value & 0x7F) | 0x80);
              value >>>= 7;
            }
          }
        } catch (IndexOutOfBoundsException e) {
          throw new OutOfSpaceException(
              String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
        }
      }
    }

    @Override
    public final void writeFixed32NoTag(int value) throws IOException {
      try {
        buffer[position++] = (byte) (value & 0xFF);
        buffer[position++] = (byte) ((value >> 8) & 0xFF);
        buffer[position++] = (byte) ((value >> 16) & 0xFF);
        buffer[position++] = (byte) ((value >> 24) & 0xFF);
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
      }
    }

    @Override
    public final void writeUInt64NoTag(long value) throws IOException {
      if (HAS_UNSAFE_ARRAY_OPERATIONS && spaceLeft() >= MAX_VARINT_SIZE) {
        while (true) {
          if ((value & ~0x7FL) == 0) {
            UnsafeUtil.putByte(buffer, position++, (byte) value);
            return;
          } else {
            UnsafeUtil.putByte(buffer, position++, (byte) (((int) value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
      } else {
        try {
          while (true) {
            if ((value & ~0x7FL) == 0) {
              buffer[position++] = (byte) value;
              return;
            } else {
              buffer[position++] = (byte) (((int) value & 0x7F) | 0x80);
              value >>>= 7;
            }
          }
        } catch (IndexOutOfBoundsException e) {
          throw new OutOfSpaceException(
              String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
        }
      }
    }

    @Override
    public final void writeFixed64NoTag(long value) throws IOException {
      try {
        buffer[position++] = (byte) ((int) (value) & 0xFF);
        buffer[position++] = (byte) ((int) (value >> 8) & 0xFF);
        buffer[position++] = (byte) ((int) (value >> 16) & 0xFF);
        buffer[position++] = (byte) ((int) (value >> 24) & 0xFF);
        buffer[position++] = (byte) ((int) (value >> 32) & 0xFF);
        buffer[position++] = (byte) ((int) (value >> 40) & 0xFF);
        buffer[position++] = (byte) ((int) (value >> 48) & 0xFF);
        buffer[position++] = (byte) ((int) (value >> 56) & 0xFF);
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
      }
    }

    @Override
    public final void write(byte[] value, int offset, int length) throws IOException {
      try {
        System.arraycopy(value, offset, buffer, position, length);
        position += length;
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, length), e);
      }
    }

    @Override
    public final void writeLazy(byte[] value, int offset, int length) throws IOException {
      write(value, offset, length);
    }

    @Override
    public final void write(ByteBuffer value) throws IOException {
      final int length = value.remaining();
      try {
        value.get(buffer, position, length);
        position += length;
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, length), e);
      }
    }

    @Override
    public final void writeLazy(ByteBuffer value) throws IOException {
      write(value);
    }

    @Override
    public final void writeStringNoTag(String value) throws IOException {
      final int oldPosition = position;
      try {
        // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
        // and at most 3 times of it. We take advantage of this in both branches below.
        final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR;
        final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength);
        final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length());
        if (minLengthVarIntSize == maxLengthVarIntSize) {
          position = oldPosition + minLengthVarIntSize;
          int newPosition = Utf8.encode(value, buffer, position, spaceLeft());
          // Since this class is stateful and tracks the position, we rewind and store the state,
          // prepend the length, then reset it back to the end of the string.
          position = oldPosition;
          int length = newPosition - oldPosition - minLengthVarIntSize;
          writeUInt32NoTag(length);
          position = newPosition;
        } else {
          int length = Utf8.encodedLength(value);
          writeUInt32NoTag(length);
          position = Utf8.encode(value, buffer, position, spaceLeft());
        }
      } catch (UnpairedSurrogateException e) {
        // Roll back the change - we fall back to inefficient path.
        position = oldPosition;

        // TODO(nathanmittler): We should throw an IOException here instead.
        inefficientWriteStringNoTag(value, e);
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void flush() {
      // Do nothing.
    }

    @Override
    public final int spaceLeft() {
      return limit - position;
    }

    @Override
    public final int getTotalBytesWritten() {
      return position - offset;
    }
  }

  /**
   * A {@link CodedOutputStream} that writes directly to a heap {@link ByteBuffer}. Writes are
   * done directly to the underlying array. The buffer position is only updated after a flush.
   */
  private static final class HeapNioEncoder extends ArrayEncoder {
    private final ByteBuffer byteBuffer;
    private int initialPosition;

    HeapNioEncoder(ByteBuffer byteBuffer) {
      super(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(),
          byteBuffer.remaining());
      this.byteBuffer = byteBuffer;
      this.initialPosition = byteBuffer.position();
    }

    @Override
    public void flush() {
      // Update the position on the buffer.
      byteBuffer.position(initialPosition + getTotalBytesWritten());
    }
  }

  /**
   * A {@link CodedOutputStream} that writes directly to a direct {@link ByteBuffer}, using only
   * safe operations..
   */
  private static final class SafeDirectNioEncoder extends CodedOutputStream {
    private final ByteBuffer originalBuffer;
    private final ByteBuffer buffer;
    private final int initialPosition;

    SafeDirectNioEncoder(ByteBuffer buffer) {
      this.originalBuffer = buffer;
      this.buffer = buffer.duplicate().order(ByteOrder.LITTLE_ENDIAN);
      initialPosition = buffer.position();
    }

    @Override
    public void writeTag(final int fieldNumber, final int wireType) throws IOException {
      writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType));
    }

    @Override
    public void writeInt32(final int fieldNumber, final int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeInt32NoTag(value);
    }

    @Override
    public void writeUInt32(final int fieldNumber, final int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeUInt32NoTag(value);
    }

    @Override
    public void writeFixed32(final int fieldNumber, final int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32);
      writeFixed32NoTag(value);
    }

    @Override
    public void writeUInt64(final int fieldNumber, final long value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeUInt64NoTag(value);
    }

    @Override
    public void writeFixed64(final int fieldNumber, final long value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64);
      writeFixed64NoTag(value);
    }

    @Override
    public void writeBool(final int fieldNumber, final boolean value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      write((byte) (value ? 1 : 0));
    }

    @Override
    public void writeString(final int fieldNumber, final String value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeStringNoTag(value);
    }

    @Override
    public void writeBytes(final int fieldNumber, final ByteString value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeBytesNoTag(value);
    }

    @Override
    public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException {
      writeByteArray(fieldNumber, value, 0, value.length);
    }

    @Override
    public void writeByteArray(
        final int fieldNumber, final byte[] value, final int offset, final int length)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeByteArrayNoTag(value, offset, length);
    }

    @Override
    public void writeByteBuffer(final int fieldNumber, final ByteBuffer value)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeUInt32NoTag(value.capacity());
      writeRawBytes(value);
    }

    @Override
    public void writeMessage(final int fieldNumber, final MessageLite value)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeMessageNoTag(value);
    }

    @Override
    public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
        throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value)
        throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public void writeMessageNoTag(final MessageLite value) throws IOException {
      writeUInt32NoTag(value.getSerializedSize());
      value.writeTo(this);
    }

    @Override
    public void write(byte value) throws IOException {
      try {
        buffer.put(value);
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void writeBytesNoTag(final ByteString value) throws IOException {
      writeUInt32NoTag(value.size());
      value.writeTo(this);
    }

    @Override
    public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException {
      writeUInt32NoTag(length);
      write(value, offset, length);
    }

    @Override
    public void writeRawBytes(final ByteBuffer value) throws IOException {
      if (value.hasArray()) {
        write(value.array(), value.arrayOffset(), value.capacity());
      } else {
        ByteBuffer duplicated = value.duplicate();
        duplicated.clear();
        write(duplicated);
      }
    }

    @Override
    public void writeInt32NoTag(int value) throws IOException {
      if (value >= 0) {
        writeUInt32NoTag(value);
      } else {
        // Must sign-extend.
        writeUInt64NoTag(value);
      }
    }

    @Override
    public void writeUInt32NoTag(int value) throws IOException {
      try {
        while (true) {
          if ((value & ~0x7F) == 0) {
            buffer.put((byte) value);
            return;
          } else {
            buffer.put((byte) ((value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void writeFixed32NoTag(int value) throws IOException {
      try {
        buffer.putInt(value);
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void writeUInt64NoTag(long value) throws IOException {
      try {
        while (true) {
          if ((value & ~0x7FL) == 0) {
            buffer.put((byte) value);
            return;
          } else {
            buffer.put((byte) (((int) value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void writeFixed64NoTag(long value) throws IOException {
      try {
        buffer.putLong(value);
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void write(byte[] value, int offset, int length) throws IOException {
      try {
        buffer.put(value, offset, length);
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(e);
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void writeLazy(byte[] value, int offset, int length) throws IOException {
      write(value, offset, length);
    }

    @Override
    public void write(ByteBuffer value) throws IOException {
      try {
        buffer.put(value);
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void writeLazy(ByteBuffer value) throws IOException {
      write(value);
    }

    @Override
    public void writeStringNoTag(String value) throws IOException {
      final int startPos = buffer.position();
      try {
        // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
        // and at most 3 times of it. We take advantage of this in both branches below.
        final int maxEncodedSize = value.length() * Utf8.MAX_BYTES_PER_CHAR;
        final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxEncodedSize);
        final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length());
        if (minLengthVarIntSize == maxLengthVarIntSize) {
          // Save the current position and increment past the length field. We'll come back
          // and write the length field after the encoding is complete.
          final int startOfBytes = buffer.position() + minLengthVarIntSize;
          buffer.position(startOfBytes);

          // Encode the string.
          encode(value);

          // Now go back to the beginning and write the length.
          int endOfBytes = buffer.position();
          buffer.position(startPos);
          writeUInt32NoTag(endOfBytes - startOfBytes);

          // Reposition the buffer past the written data.
          buffer.position(endOfBytes);
        } else {
          final int length = Utf8.encodedLength(value);
          writeUInt32NoTag(length);
          encode(value);
        }
      } catch (UnpairedSurrogateException e) {
        // Roll back the change and convert to an IOException.
        buffer.position(startPos);

        // TODO(nathanmittler): We should throw an IOException here instead.
        inefficientWriteStringNoTag(value, e);
      } catch (IllegalArgumentException e) {
        // Thrown by buffer.position() if out of range.
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void flush() {
      // Update the position of the original buffer.
      originalBuffer.position(buffer.position());
    }

    @Override
    public int spaceLeft() {
      return buffer.remaining();
    }

    @Override
    public int getTotalBytesWritten() {
      return buffer.position() - initialPosition;
    }

    private void encode(String value) throws IOException {
      try {
        Utf8.encodeUtf8(value, buffer);
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(e);
      }
    }
  }

  /**
   * A {@link CodedOutputStream} that writes directly to a direct {@link ByteBuffer} using {@code
   * sun.misc.Unsafe}.
   */
  private static final class UnsafeDirectNioEncoder extends CodedOutputStream {
    private final ByteBuffer originalBuffer;
    private final ByteBuffer buffer;
    private final long address;
    private final long initialPosition;
    private final long limit;
    private final long oneVarintLimit;
    private long position;

    UnsafeDirectNioEncoder(ByteBuffer buffer) {
      this.originalBuffer = buffer;
      this.buffer = buffer.duplicate().order(ByteOrder.LITTLE_ENDIAN);
      address = UnsafeUtil.addressOffset(buffer);
      initialPosition = address + buffer.position();
      limit = address + buffer.limit();
      oneVarintLimit = limit - MAX_VARINT_SIZE;
      position = initialPosition;
    }

    static boolean isSupported() {
      return UnsafeUtil.hasUnsafeByteBufferOperations();
    }

    @Override
    public void writeTag(int fieldNumber, int wireType) throws IOException {
      writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType));
    }

    @Override
    public void writeInt32(int fieldNumber, int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeInt32NoTag(value);
    }

    @Override
    public void writeUInt32(int fieldNumber, int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeUInt32NoTag(value);
    }

    @Override
    public void writeFixed32(int fieldNumber, int value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32);
      writeFixed32NoTag(value);
    }

    @Override
    public void writeUInt64(int fieldNumber, long value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      writeUInt64NoTag(value);
    }

    @Override
    public void writeFixed64(int fieldNumber, long value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64);
      writeFixed64NoTag(value);
    }

    @Override
    public void writeBool(int fieldNumber, boolean value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      write((byte) (value ? 1 : 0));
    }

    @Override
    public void writeString(int fieldNumber, String value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeStringNoTag(value);
    }

    @Override
    public void writeBytes(int fieldNumber, ByteString value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeBytesNoTag(value);
    }

    @Override
    public void writeByteArray(int fieldNumber, byte[] value) throws IOException {
      writeByteArray(fieldNumber, value, 0, value.length);
    }

    @Override
    public void writeByteArray(int fieldNumber, byte[] value, int offset, int length)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeByteArrayNoTag(value, offset, length);
    }

    @Override
    public void writeByteBuffer(int fieldNumber, ByteBuffer value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeUInt32NoTag(value.capacity());
      writeRawBytes(value);
    }

    @Override
    public void writeMessage(int fieldNumber, MessageLite value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeMessageNoTag(value);
    }

    @Override
    public void writeMessageSetExtension(int fieldNumber, MessageLite value) throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public void writeRawMessageSetExtension(int fieldNumber, ByteString value) throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public void writeMessageNoTag(MessageLite value) throws IOException {
      writeUInt32NoTag(value.getSerializedSize());
      value.writeTo(this);
    }

    @Override
    public void write(byte value) throws IOException {
      if (position >= limit) {
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1));
      }
      UnsafeUtil.putByte(position++, value);
    }

    @Override
    public void writeBytesNoTag(ByteString value) throws IOException {
      writeUInt32NoTag(value.size());
      value.writeTo(this);
    }

    @Override
    public void writeByteArrayNoTag(byte[] value, int offset, int length) throws IOException {
      writeUInt32NoTag(length);
      write(value, offset, length);
    }

    @Override
    public void writeRawBytes(ByteBuffer value) throws IOException {
      if (value.hasArray()) {
        write(value.array(), value.arrayOffset(), value.capacity());
      } else {
        ByteBuffer duplicated = value.duplicate();
        duplicated.clear();
        write(duplicated);
      }
    }

    @Override
    public void writeInt32NoTag(int value) throws IOException {
      if (value >= 0) {
        writeUInt32NoTag(value);
      } else {
        // Must sign-extend.
        writeUInt64NoTag(value);
      }
    }

    @Override
    public void writeUInt32NoTag(int value) throws IOException {
      if (position <= oneVarintLimit) {
        // Optimization to avoid bounds checks on each iteration.
        while (true) {
          if ((value & ~0x7F) == 0) {
            UnsafeUtil.putByte(position++, (byte) value);
            return;
          } else {
            UnsafeUtil.putByte(position++, (byte) ((value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
      } else {
        while (position < limit) {
          if ((value & ~0x7F) == 0) {
            UnsafeUtil.putByte(position++, (byte) value);
            return;
          } else {
            UnsafeUtil.putByte(position++, (byte) ((value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1));
      }
    }

    @Override
    public void writeFixed32NoTag(int value) throws IOException {
      buffer.putInt(bufferPos(position), value);
      position += FIXED32_SIZE;
    }

    @Override
    public void writeUInt64NoTag(long value) throws IOException {
      if (position <= oneVarintLimit) {
        // Optimization to avoid bounds checks on each iteration.
        while (true) {
          if ((value & ~0x7FL) == 0) {
            UnsafeUtil.putByte(position++, (byte) value);
            return;
          } else {
            UnsafeUtil.putByte(position++, (byte) (((int) value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
      } else {
        while (position < limit) {
          if ((value & ~0x7FL) == 0) {
            UnsafeUtil.putByte(position++, (byte) value);
            return;
          } else {
            UnsafeUtil.putByte(position++, (byte) (((int) value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1));
      }
    }

    @Override
    public void writeFixed64NoTag(long value) throws IOException {
      buffer.putLong(bufferPos(position), value);
      position += FIXED64_SIZE;
    }

    @Override
    public void write(byte[] value, int offset, int length) throws IOException {
      if (value == null
          || offset < 0
          || length < 0
          || (value.length - length) < offset
          || (limit - length) < position) {
        if (value == null) {
          throw new NullPointerException("value");
        }
        throw new OutOfSpaceException(
            String.format("Pos: %d, limit: %d, len: %d", position, limit, length));
      }

      UnsafeUtil.copyMemory(value, offset, position, length);
      position += length;
    }

    @Override
    public void writeLazy(byte[] value, int offset, int length) throws IOException {
      write(value, offset, length);
    }

    @Override
    public void write(ByteBuffer value) throws IOException {
      try {
        int length = value.remaining();
        repositionBuffer(position);
        buffer.put(value);
        position += length;
      } catch (BufferOverflowException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void writeLazy(ByteBuffer value) throws IOException {
      write(value);
    }

    @Override
    public void writeStringNoTag(String value) throws IOException {
      long prevPos = position;
      try {
        // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
        // and at most 3 times of it. We take advantage of this in both branches below.
        int maxEncodedSize = value.length() * Utf8.MAX_BYTES_PER_CHAR;
        int maxLengthVarIntSize = computeUInt32SizeNoTag(maxEncodedSize);
        int minLengthVarIntSize = computeUInt32SizeNoTag(value.length());
        if (minLengthVarIntSize == maxLengthVarIntSize) {
          // Save the current position and increment past the length field. We'll come back
          // and write the length field after the encoding is complete.
          int stringStart = bufferPos(position) + minLengthVarIntSize;
          buffer.position(stringStart);

          // Encode the string.
          Utf8.encodeUtf8(value, buffer);

          // Write the length and advance the position.
          int length = buffer.position() - stringStart;
          writeUInt32NoTag(length);
          position += length;
        } else {
          // Calculate and write the encoded length.
          int length = Utf8.encodedLength(value);
          writeUInt32NoTag(length);

          // Write the string and advance the position.
          repositionBuffer(position);
          Utf8.encodeUtf8(value, buffer);
          position += length;
        }
      } catch (UnpairedSurrogateException e) {
        // Roll back the change and convert to an IOException.
        position = prevPos;
        repositionBuffer(position);

        // TODO(nathanmittler): We should throw an IOException here instead.
        inefficientWriteStringNoTag(value, e);
      } catch (IllegalArgumentException e) {
        // Thrown by buffer.position() if out of range.
        throw new OutOfSpaceException(e);
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void flush() {
      // Update the position of the original buffer.
      originalBuffer.position(bufferPos(position));
    }

    @Override
    public int spaceLeft() {
      return (int) (limit - position);
    }

    @Override
    public int getTotalBytesWritten() {
      return (int) (position - initialPosition);
    }

    private void repositionBuffer(long pos) {
      buffer.position(bufferPos(pos));
    }

    private int bufferPos(long pos) {
      return (int) (pos - address);
    }
  }

  /**
   * Abstract base class for buffered encoders.
   */
  private abstract static class AbstractBufferedEncoder extends CodedOutputStream {
    final byte[] buffer;
    final int limit;
    int position;
    int totalBytesWritten;

    AbstractBufferedEncoder(int bufferSize) {
      if (bufferSize < 0) {
        throw new IllegalArgumentException("bufferSize must be >= 0");
      }
      // As an optimization, we require that the buffer be able to store at least 2
      // varints so that we can buffer any integer write (tag + value). This reduces the
      // number of range checks for a single write to 1 (i.e. if there is not enough space
      // to buffer the tag+value, flush and then buffer it).
      this.buffer = new byte[max(bufferSize, MAX_VARINT_SIZE * 2)];
      this.limit = buffer.length;
    }

    @Override
    public final int spaceLeft() {
      throw new UnsupportedOperationException(
          "spaceLeft() can only be called on CodedOutputStreams that are "
              + "writing to a flat array or ByteBuffer.");
    }

    @Override
    public final int getTotalBytesWritten() {
      return totalBytesWritten;
    }

    /**
     * This method does not perform bounds checking on the array. Checking array bounds is the
     * responsibility of the caller.
     */
    final void buffer(byte value) {
      buffer[position++] = value;
      totalBytesWritten++;
    }

    /**
     * This method does not perform bounds checking on the array. Checking array bounds is the
     * responsibility of the caller.
     */
    final void bufferTag(final int fieldNumber, final int wireType) {
      bufferUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType));
    }

    /**
     * This method does not perform bounds checking on the array. Checking array bounds is the
     * responsibility of the caller.
     */
    final void bufferInt32NoTag(final int value) {
      if (value >= 0) {
        bufferUInt32NoTag(value);
      } else {
        // Must sign-extend.
        bufferUInt64NoTag(value);
      }
    }

    /**
     * This method does not perform bounds checking on the array. Checking array bounds is the
     * responsibility of the caller.
     */
    final void bufferUInt32NoTag(int value) {
      if (HAS_UNSAFE_ARRAY_OPERATIONS) {
        final long originalPos = position;
        while (true) {
          if ((value & ~0x7F) == 0) {
            UnsafeUtil.putByte(buffer, position++, (byte) value);
            break;
          } else {
            UnsafeUtil.putByte(buffer, position++, (byte) ((value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
        int delta = (int) (position - originalPos);
        totalBytesWritten += delta;
      } else {
        while (true) {
          if ((value & ~0x7F) == 0) {
            buffer[position++] = (byte) value;
            totalBytesWritten++;
            return;
          } else {
            buffer[position++] = (byte) ((value & 0x7F) | 0x80);
            totalBytesWritten++;
            value >>>= 7;
          }
        }
      }
    }

    /**
     * This method does not perform bounds checking on the array. Checking array bounds is the
     * responsibility of the caller.
     */
    final void bufferUInt64NoTag(long value) {
      if (HAS_UNSAFE_ARRAY_OPERATIONS) {
        final long originalPos = position;
        while (true) {
          if ((value & ~0x7FL) == 0) {
            UnsafeUtil.putByte(buffer, position++, (byte) value);
            break;
          } else {
            UnsafeUtil.putByte(buffer, position++, (byte) (((int) value & 0x7F) | 0x80));
            value >>>= 7;
          }
        }
        int delta = (int) (position - originalPos);
        totalBytesWritten += delta;
      } else {
        while (true) {
          if ((value & ~0x7FL) == 0) {
            buffer[position++] = (byte) value;
            totalBytesWritten++;
            return;
          } else {
            buffer[position++] = (byte) (((int) value & 0x7F) | 0x80);
            totalBytesWritten++;
            value >>>= 7;
          }
        }
      }
    }

    /**
     * This method does not perform bounds checking on the array. Checking array bounds is the
     * responsibility of the caller.
     */
    final void bufferFixed32NoTag(int value) {
      buffer[position++] = (byte) (value & 0xFF);
      buffer[position++] = (byte) ((value >> 8) & 0xFF);
      buffer[position++] = (byte) ((value >> 16) & 0xFF);
      buffer[position++] = (byte) ((value >> 24) & 0xFF);
      totalBytesWritten += FIXED32_SIZE;
    }

    /**
     * This method does not perform bounds checking on the array. Checking array bounds is the
     * responsibility of the caller.
     */
    final void bufferFixed64NoTag(long value) {
      buffer[position++] = (byte) (value & 0xFF);
      buffer[position++] = (byte) ((value >> 8) & 0xFF);
      buffer[position++] = (byte) ((value >> 16) & 0xFF);
      buffer[position++] = (byte) ((value >> 24) & 0xFF);
      buffer[position++] = (byte) ((int) (value >> 32) & 0xFF);
      buffer[position++] = (byte) ((int) (value >> 40) & 0xFF);
      buffer[position++] = (byte) ((int) (value >> 48) & 0xFF);
      buffer[position++] = (byte) ((int) (value >> 56) & 0xFF);
      totalBytesWritten += FIXED64_SIZE;
    }
  }

  /**
   * A {@link CodedOutputStream} that decorates a {@link ByteOutput}. It internal buffer only to
   * support string encoding operations. All other writes are just passed through to the
   * {@link ByteOutput}.
   */
  private static final class ByteOutputEncoder extends AbstractBufferedEncoder {
    private final ByteOutput out;

    ByteOutputEncoder(ByteOutput out, int bufferSize) {
      super(bufferSize);
      if (out == null) {
        throw new NullPointerException("out");
      }
      this.out = out;
    }

    @Override
    public void writeTag(final int fieldNumber, final int wireType) throws IOException {
      writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType));
    }

    @Override
    public void writeInt32(final int fieldNumber, final int value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE * 2);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      bufferInt32NoTag(value);
    }

    @Override
    public void writeUInt32(final int fieldNumber, final int value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE * 2);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      bufferUInt32NoTag(value);
    }

    @Override
    public void writeFixed32(final int fieldNumber, final int value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE + FIXED32_SIZE);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED32);
      bufferFixed32NoTag(value);
    }

    @Override
    public void writeUInt64(final int fieldNumber, final long value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE * 2);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      bufferUInt64NoTag(value);
    }

    @Override
    public void writeFixed64(final int fieldNumber, final long value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE + FIXED64_SIZE);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED64);
      bufferFixed64NoTag(value);
    }

    @Override
    public void writeBool(final int fieldNumber, final boolean value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE + 1);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      buffer((byte) (value ? 1 : 0));
    }

    @Override
    public void writeString(final int fieldNumber, final String value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeStringNoTag(value);
    }

    @Override
    public void writeBytes(final int fieldNumber, final ByteString value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeBytesNoTag(value);
    }

    @Override
    public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException {
      writeByteArray(fieldNumber, value, 0, value.length);
    }

    @Override
    public void writeByteArray(
        final int fieldNumber, final byte[] value, final int offset, final int length)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeByteArrayNoTag(value, offset, length);
    }

    @Override
    public void writeByteBuffer(final int fieldNumber, final ByteBuffer value)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeUInt32NoTag(value.capacity());
      writeRawBytes(value);
    }

    @Override
    public void writeBytesNoTag(final ByteString value) throws IOException {
      writeUInt32NoTag(value.size());
      value.writeTo(this);
    }

    @Override
    public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException {
      writeUInt32NoTag(length);
      write(value, offset, length);
    }

    @Override
    public void writeRawBytes(final ByteBuffer value) throws IOException {
      if (value.hasArray()) {
        write(value.array(), value.arrayOffset(), value.capacity());
      } else {
        ByteBuffer duplicated = value.duplicate();
        duplicated.clear();
        write(duplicated);
      }
    }

    @Override
    public void writeMessage(final int fieldNumber, final MessageLite value)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeMessageNoTag(value);
    }

    @Override
    public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
        throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value)
        throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public void writeMessageNoTag(final MessageLite value) throws IOException {
      writeUInt32NoTag(value.getSerializedSize());
      value.writeTo(this);
    }

    @Override
    public void write(byte value) throws IOException {
      if (position == limit) {
        doFlush();
      }

      buffer(value);
    }

    @Override
    public void writeInt32NoTag(int value) throws IOException {
      if (value >= 0) {
        writeUInt32NoTag(value);
      } else {
        // Must sign-extend.
        writeUInt64NoTag(value);
      }
    }

    @Override
    public void writeUInt32NoTag(int value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE);
      bufferUInt32NoTag(value);
    }

    @Override
    public void writeFixed32NoTag(final int value) throws IOException {
      flushIfNotAvailable(FIXED32_SIZE);
      bufferFixed32NoTag(value);
    }

    @Override
    public void writeUInt64NoTag(long value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE);
      bufferUInt64NoTag(value);
    }

    @Override
    public void writeFixed64NoTag(final long value) throws IOException {
      flushIfNotAvailable(FIXED64_SIZE);
      bufferFixed64NoTag(value);
    }

    @Override
    public void writeStringNoTag(String value) throws IOException {
      // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
      // and at most 3 times of it. We take advantage of this in both branches below.
      final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR;
      final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength);

      // If we are streaming and the potential length is too big to fit in our buffer, we take the
      // slower path.
      if (maxLengthVarIntSize + maxLength > limit) {
        // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes()
        // does the same internally and then does *another copy* to return a byte[] of exactly the
        // right size. We can skip that copy and just writeRawBytes up to the actualLength of the
        // UTF-8 encoded bytes.
        final byte[] encodedBytes = new byte[maxLength];
        int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength);
        writeUInt32NoTag(actualLength);
        writeLazy(encodedBytes, 0, actualLength);
        return;
      }

      // Fast path: we have enough space available in our buffer for the string...
      if (maxLengthVarIntSize + maxLength > limit - position) {
        // Flush to free up space.
        doFlush();
      }

      final int oldPosition = position;
      try {
        // Optimize for the case where we know this length results in a constant varint length as
        // this saves a pass for measuring the length of the string.
        final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length());

        if (minLengthVarIntSize == maxLengthVarIntSize) {
          position = oldPosition + minLengthVarIntSize;
          int newPosition = Utf8.encode(value, buffer, position, limit - position);
          // Since this class is stateful and tracks the position, we rewind and store the state,
          // prepend the length, then reset it back to the end of the string.
          position = oldPosition;
          int length = newPosition - oldPosition - minLengthVarIntSize;
          bufferUInt32NoTag(length);
          position = newPosition;
          totalBytesWritten += length;
        } else {
          int length = Utf8.encodedLength(value);
          bufferUInt32NoTag(length);
          position = Utf8.encode(value, buffer, position, length);
          totalBytesWritten += length;
        }
      } catch (UnpairedSurrogateException e) {
        // Roll back the change and convert to an IOException.
        totalBytesWritten -= position - oldPosition;
        position = oldPosition;

        // TODO(nathanmittler): We should throw an IOException here instead.
        inefficientWriteStringNoTag(value, e);
      } catch (IndexOutOfBoundsException e) {
        throw new OutOfSpaceException(e);
      }
    }

    @Override
    public void flush() throws IOException {
      if (position > 0) {
        // Flush the buffer.
        doFlush();
      }
    }

    @Override
    public void write(byte[] value, int offset, int length) throws IOException {
      flush();
      out.write(value, offset, length);
      totalBytesWritten += length;
    }

    @Override
    public void writeLazy(byte[] value, int offset, int length) throws IOException {
      flush();
      out.writeLazy(value, offset, length);
      totalBytesWritten += length;
    }

    @Override
    public void write(ByteBuffer value) throws IOException {
      flush();
      int length = value.remaining();
      out.write(value);
      totalBytesWritten += length;
    }

    @Override
    public void writeLazy(ByteBuffer value) throws IOException {
      flush();
      int length = value.remaining();
      out.writeLazy(value);
      totalBytesWritten += length;
    }

    private void flushIfNotAvailable(int requiredSize) throws IOException {
      if (limit - position < requiredSize) {
        doFlush();
      }
    }

    private void doFlush() throws IOException {
      out.write(buffer, 0, position);
      position = 0;
    }
  }

  /**
   * An {@link CodedOutputStream} that decorates an {@link OutputStream}. It performs internal
   * buffering to optimize writes to the {@link OutputStream}.
   */
  private static final class OutputStreamEncoder extends AbstractBufferedEncoder {
    private final OutputStream out;

    OutputStreamEncoder(OutputStream out, int bufferSize) {
      super(bufferSize);
      if (out == null) {
        throw new NullPointerException("out");
      }
      this.out = out;
    }

    @Override
    public void writeTag(final int fieldNumber, final int wireType) throws IOException {
      writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType));
    }

    @Override
    public void writeInt32(final int fieldNumber, final int value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE * 2);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      bufferInt32NoTag(value);
    }

    @Override
    public void writeUInt32(final int fieldNumber, final int value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE * 2);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      bufferUInt32NoTag(value);
    }

    @Override
    public void writeFixed32(final int fieldNumber, final int value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE + FIXED32_SIZE);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED32);
      bufferFixed32NoTag(value);
    }

    @Override
    public void writeUInt64(final int fieldNumber, final long value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE * 2);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      bufferUInt64NoTag(value);
    }

    @Override
    public void writeFixed64(final int fieldNumber, final long value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE + FIXED64_SIZE);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED64);
      bufferFixed64NoTag(value);
    }

    @Override
    public void writeBool(final int fieldNumber, final boolean value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE + 1);
      bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
      buffer((byte) (value ? 1 : 0));
    }

    @Override
    public void writeString(final int fieldNumber, final String value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeStringNoTag(value);
    }

    @Override
    public void writeBytes(final int fieldNumber, final ByteString value) throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeBytesNoTag(value);
    }

    @Override
    public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException {
      writeByteArray(fieldNumber, value, 0, value.length);
    }

    @Override
    public void writeByteArray(
        final int fieldNumber, final byte[] value, final int offset, final int length)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeByteArrayNoTag(value, offset, length);
    }

    @Override
    public void writeByteBuffer(final int fieldNumber, final ByteBuffer value)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeUInt32NoTag(value.capacity());
      writeRawBytes(value);
    }

    @Override
    public void writeBytesNoTag(final ByteString value) throws IOException {
      writeUInt32NoTag(value.size());
      value.writeTo(this);
    }

    @Override
    public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException {
      writeUInt32NoTag(length);
      write(value, offset, length);
    }

    @Override
    public void writeRawBytes(final ByteBuffer value) throws IOException {
      if (value.hasArray()) {
        write(value.array(), value.arrayOffset(), value.capacity());
      } else {
        ByteBuffer duplicated = value.duplicate();
        duplicated.clear();
        write(duplicated);
      }
    }

    @Override
    public void writeMessage(final int fieldNumber, final MessageLite value)
        throws IOException {
      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      writeMessageNoTag(value);
    }

    @Override
    public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
        throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value)
        throws IOException {
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP);
      writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber);
      writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value);
      writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP);
    }

    @Override
    public void writeMessageNoTag(final MessageLite value) throws IOException {
      writeUInt32NoTag(value.getSerializedSize());
      value.writeTo(this);
    }

    @Override
    public void write(byte value) throws IOException {
      if (position == limit) {
        doFlush();
      }

      buffer(value);
    }

    @Override
    public void writeInt32NoTag(int value) throws IOException {
      if (value >= 0) {
        writeUInt32NoTag(value);
      } else {
        // Must sign-extend.
        writeUInt64NoTag(value);
      }
    }

    @Override
    public void writeUInt32NoTag(int value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE);
      bufferUInt32NoTag(value);
    }

    @Override
    public void writeFixed32NoTag(final int value) throws IOException {
      flushIfNotAvailable(FIXED32_SIZE);
      bufferFixed32NoTag(value);
    }

    @Override
    public void writeUInt64NoTag(long value) throws IOException {
      flushIfNotAvailable(MAX_VARINT_SIZE);
      bufferUInt64NoTag(value);
    }

    @Override
    public void writeFixed64NoTag(final long value) throws IOException {
      flushIfNotAvailable(FIXED64_SIZE);
      bufferFixed64NoTag(value);
    }

    @Override
    public void writeStringNoTag(String value) throws IOException {
      try {
        // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
        // and at most 3 times of it. We take advantage of this in both branches below.
        final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR;
        final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength);

        // If we are streaming and the potential length is too big to fit in our buffer, we take the
        // slower path.
        if (maxLengthVarIntSize + maxLength > limit) {
          // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes()
          // does the same internally and then does *another copy* to return a byte[] of exactly the
          // right size. We can skip that copy and just writeRawBytes up to the actualLength of the
          // UTF-8 encoded bytes.
          final byte[] encodedBytes = new byte[maxLength];
          int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength);
          writeUInt32NoTag(actualLength);
          writeLazy(encodedBytes, 0, actualLength);
          return;
        }

        // Fast path: we have enough space available in our buffer for the string...
        if (maxLengthVarIntSize + maxLength > limit - position) {
          // Flush to free up space.
          doFlush();
        }

        // Optimize for the case where we know this length results in a constant varint length as
        // this saves a pass for measuring the length of the string.
        final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length());
        int oldPosition = position;
        final int length;
        try {
          if (minLengthVarIntSize == maxLengthVarIntSize) {
            position = oldPosition + minLengthVarIntSize;
            int newPosition = Utf8.encode(value, buffer, position, limit - position);
            // Since this class is stateful and tracks the position, we rewind and store the
            // state, prepend the length, then reset it back to the end of the string.
            position = oldPosition;
            length = newPosition - oldPosition - minLengthVarIntSize;
            bufferUInt32NoTag(length);
            position = newPosition;
          } else {
            length = Utf8.encodedLength(value);
            bufferUInt32NoTag(length);
            position = Utf8.encode(value, buffer, position, length);
          }
          totalBytesWritten += length;
        } catch (UnpairedSurrogateException e) {
          // Be extra careful and restore the original position for retrying the write with the
          // less efficient path.
          totalBytesWritten -= position - oldPosition;
          position = oldPosition;
          throw e;
        } catch (ArrayIndexOutOfBoundsException e) {
          throw new OutOfSpaceException(e);
        }
      } catch (UnpairedSurrogateException e) {
        inefficientWriteStringNoTag(value, e);
      }
    }

    @Override
    public void flush() throws IOException {
      if (position > 0) {
        // Flush the buffer.
        doFlush();
      }
    }

    @Override
    public void write(byte[] value, int offset, int length)
        throws IOException {
      if (limit - position >= length) {
        // We have room in the current buffer.
        System.arraycopy(value, offset, buffer, position, length);
        position += length;
        totalBytesWritten += length;
      } else {
        // Write extends past current buffer.  Fill the rest of this buffer and
        // flush.
        final int bytesWritten = limit - position;
        System.arraycopy(value, offset, buffer, position, bytesWritten);
        offset += bytesWritten;
        length -= bytesWritten;
        position = limit;
        totalBytesWritten += bytesWritten;
        doFlush();

        // Now deal with the rest.
        // Since we have an output stream, this is our buffer
        // and buffer offset == 0
        if (length <= limit) {
          // Fits in new buffer.
          System.arraycopy(value, offset, buffer, 0, length);
          position = length;
        } else {
          // Write is very big.  Let's do it all at once.
          out.write(value, offset, length);
        }
        totalBytesWritten += length;
      }
    }

    @Override
    public void writeLazy(byte[] value, int offset, int length) throws IOException {
      write(value, offset, length);
    }

    @Override
    public void write(ByteBuffer value) throws IOException {
      int length = value.remaining();
      if (limit - position >= length) {
        // We have room in the current buffer.
        value.get(buffer, position, length);
        position += length;
        totalBytesWritten += length;
      } else {
        // Write extends past current buffer.  Fill the rest of this buffer and
        // flush.
        final int bytesWritten = limit - position;
        value.get(buffer, position, bytesWritten);
        length -= bytesWritten;
        position = limit;
        totalBytesWritten += bytesWritten;
        doFlush();

        // Now deal with the rest.
        // Since we have an output stream, this is our buffer
        // and buffer offset == 0
        while (length > limit) {
          // Copy data into the buffer before writing it to OutputStream.
          value.get(buffer, 0, limit);
          out.write(buffer, 0, limit);
          length -= limit;
          totalBytesWritten += limit;
        }
        value.get(buffer, 0, length);
        position = length;
        totalBytesWritten += length;
      }
    }

    @Override
    public void writeLazy(ByteBuffer value) throws IOException {
      write(value);
    }

    private void flushIfNotAvailable(int requiredSize) throws IOException {
      if (limit - position < requiredSize) {
        doFlush();
      }
    }

    private void doFlush() throws IOException {
      out.write(buffer, 0, position);
      position = 0;
    }
  }
}