Home | History | Annotate | Download | only in protobuf
      1 // Protocol Buffers - Google's data interchange format
      2 // Copyright 2008 Google Inc.  All rights reserved.
      3 // http://code.google.com/p/protobuf/
      4 //
      5 // Redistribution and use in source and binary forms, with or without
      6 // modification, are permitted provided that the following conditions are
      7 // met:
      8 //
      9 //     * Redistributions of source code must retain the above copyright
     10 // notice, this list of conditions and the following disclaimer.
     11 //     * Redistributions in binary form must reproduce the above
     12 // copyright notice, this list of conditions and the following disclaimer
     13 // in the documentation and/or other materials provided with the
     14 // distribution.
     15 //     * Neither the name of Google Inc. nor the names of its
     16 // contributors may be used to endorse or promote products derived from
     17 // this software without specific prior written permission.
     18 //
     19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30 
     31 package com.google.protobuf;
     32 
     33 import java.io.ByteArrayInputStream;
     34 import java.io.IOException;
     35 import java.io.InputStream;
     36 import java.io.OutputStream;
     37 import java.io.UnsupportedEncodingException;
     38 import java.nio.ByteBuffer;
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 import java.util.NoSuchElementException;
     42 
     43 /**
     44  * This class implements a {@link com.google.protobuf.ByteString} backed by a
     45  * single array of bytes, contiguous in memory. It supports substring by
     46  * pointing to only a sub-range of the underlying byte array, meaning that a
     47  * substring will reference the full byte-array of the string it's made from,
     48  * exactly as with {@link String}.
     49  *
     50  * @author carlanton (at) google.com (Carl Haverl)
     51  */
     52 class LiteralByteString extends ByteString {
     53 
     54   protected final byte[] bytes;
     55 
     56   /**
     57    * Creates a {@code LiteralByteString} backed by the given array, without
     58    * copying.
     59    *
     60    * @param bytes array to wrap
     61    */
     62   LiteralByteString(byte[] bytes) {
     63     this.bytes = bytes;
     64   }
     65 
     66   @Override
     67   public byte byteAt(int index) {
     68     // Unlike most methods in this class, this one is a direct implementation
     69     // ignoring the potential offset because we need to do range-checking in the
     70     // substring case anyway.
     71     return bytes[index];
     72   }
     73 
     74   @Override
     75   public int size() {
     76     return bytes.length;
     77   }
     78 
     79   // =================================================================
     80   // ByteString -> substring
     81 
     82   @Override
     83   public ByteString substring(int beginIndex, int endIndex) {
     84     if (beginIndex < 0) {
     85       throw new IndexOutOfBoundsException(
     86           "Beginning index: " + beginIndex + " < 0");
     87     }
     88     if (endIndex > size()) {
     89       throw new IndexOutOfBoundsException("End index: " + endIndex + " > " +
     90           size());
     91     }
     92     int substringLength = endIndex - beginIndex;
     93     if (substringLength < 0) {
     94       throw new IndexOutOfBoundsException(
     95           "Beginning index larger than ending index: " + beginIndex + ", "
     96               + endIndex);
     97     }
     98 
     99     ByteString result;
    100     if (substringLength == 0) {
    101       result = ByteString.EMPTY;
    102     } else {
    103       result = new BoundedByteString(bytes, getOffsetIntoBytes() + beginIndex,
    104           substringLength);
    105     }
    106     return result;
    107   }
    108 
    109   // =================================================================
    110   // ByteString -> byte[]
    111 
    112   @Override
    113   protected void copyToInternal(byte[] target, int sourceOffset,
    114       int targetOffset, int numberToCopy) {
    115     // Optimized form, not for subclasses, since we don't call
    116     // getOffsetIntoBytes() or check the 'numberToCopy' parameter.
    117     System.arraycopy(bytes, sourceOffset, target, targetOffset, numberToCopy);
    118   }
    119 
    120   @Override
    121   public void copyTo(ByteBuffer target) {
    122     target.put(bytes, getOffsetIntoBytes(), size());  // Copies bytes
    123   }
    124 
    125   @Override
    126   public ByteBuffer asReadOnlyByteBuffer() {
    127     ByteBuffer byteBuffer =
    128         ByteBuffer.wrap(bytes, getOffsetIntoBytes(), size());
    129     return byteBuffer.asReadOnlyBuffer();
    130   }
    131 
    132   @Override
    133   public List<ByteBuffer> asReadOnlyByteBufferList() {
    134     // Return the ByteBuffer generated by asReadOnlyByteBuffer() as a singleton
    135     List<ByteBuffer> result = new ArrayList<ByteBuffer>(1);
    136     result.add(asReadOnlyByteBuffer());
    137     return result;
    138  }
    139 
    140  @Override
    141   public void writeTo(OutputStream outputStream) throws IOException {
    142     outputStream.write(toByteArray());
    143   }
    144 
    145   @Override
    146   public String toString(String charsetName)
    147       throws UnsupportedEncodingException {
    148     return new String(bytes, getOffsetIntoBytes(), size(), charsetName);
    149   }
    150 
    151   // =================================================================
    152   // UTF-8 decoding
    153 
    154   @Override
    155   public boolean isValidUtf8() {
    156     int offset = getOffsetIntoBytes();
    157     return Utf8.isValidUtf8(bytes, offset, offset + size());
    158   }
    159 
    160   @Override
    161   protected int partialIsValidUtf8(int state, int offset, int length) {
    162     int index = getOffsetIntoBytes() + offset;
    163     return Utf8.partialIsValidUtf8(state, bytes, index, index + length);
    164   }
    165 
    166   // =================================================================
    167   // equals() and hashCode()
    168 
    169   @Override
    170   public boolean equals(Object other) {
    171     if (other == this) {
    172       return true;
    173     }
    174     if (!(other instanceof ByteString)) {
    175       return false;
    176     }
    177 
    178     if (size() != ((ByteString) other).size()) {
    179       return false;
    180     }
    181     if (size() == 0) {
    182       return true;
    183     }
    184 
    185     if (other instanceof LiteralByteString) {
    186       return equalsRange((LiteralByteString) other, 0, size());
    187     } else if (other instanceof RopeByteString) {
    188       return other.equals(this);
    189     } else {
    190       throw new IllegalArgumentException(
    191           "Has a new type of ByteString been created? Found "
    192               + other.getClass());
    193     }
    194   }
    195 
    196   /**
    197    * Check equality of the substring of given length of this object starting at
    198    * zero with another {@code LiteralByteString} substring starting at offset.
    199    *
    200    * @param other  what to compare a substring in
    201    * @param offset offset into other
    202    * @param length number of bytes to compare
    203    * @return true for equality of substrings, else false.
    204    */
    205   boolean equalsRange(LiteralByteString other, int offset, int length) {
    206     if (length > other.size()) {
    207       throw new IllegalArgumentException(
    208           "Length too large: " + length + size());
    209     }
    210     if (offset + length > other.size()) {
    211       throw new IllegalArgumentException(
    212           "Ran off end of other: " + offset + ", " + length + ", " +
    213               other.size());
    214     }
    215 
    216     byte[] thisBytes = bytes;
    217     byte[] otherBytes = other.bytes;
    218     int thisLimit = getOffsetIntoBytes() + length;
    219     for (int thisIndex = getOffsetIntoBytes(), otherIndex =
    220         other.getOffsetIntoBytes() + offset;
    221         (thisIndex < thisLimit); ++thisIndex, ++otherIndex) {
    222       if (thisBytes[thisIndex] != otherBytes[otherIndex]) {
    223         return false;
    224       }
    225     }
    226     return true;
    227   }
    228 
    229   /**
    230    * Cached hash value.  Intentionally accessed via a data race, which
    231    * is safe because of the Java Memory Model's "no out-of-thin-air values"
    232    * guarantees for ints.
    233    */
    234   private int hash = 0;
    235 
    236   /**
    237    * Compute the hashCode using the traditional algorithm from {@link
    238    * ByteString}.
    239    *
    240    * @return hashCode value
    241    */
    242   @Override
    243   public int hashCode() {
    244     int h = hash;
    245 
    246     if (h == 0) {
    247       int size = size();
    248       h = partialHash(size, 0, size);
    249       if (h == 0) {
    250         h = 1;
    251       }
    252       hash = h;
    253     }
    254     return h;
    255   }
    256 
    257   @Override
    258   protected int peekCachedHashCode() {
    259     return hash;
    260   }
    261 
    262   @Override
    263   protected int partialHash(int h, int offset, int length) {
    264     byte[] thisBytes = bytes;
    265     for (int i = getOffsetIntoBytes() + offset, limit = i + length; i < limit;
    266         i++) {
    267       h = h * 31 + thisBytes[i];
    268     }
    269     return h;
    270   }
    271 
    272   // =================================================================
    273   // Input stream
    274 
    275   @Override
    276   public InputStream newInput() {
    277     return new ByteArrayInputStream(bytes, getOffsetIntoBytes(),
    278         size());  // No copy
    279   }
    280 
    281   @Override
    282   public CodedInputStream newCodedInput() {
    283     // We trust CodedInputStream not to modify the bytes, or to give anyone
    284     // else access to them.
    285     return CodedInputStream
    286         .newInstance(bytes, getOffsetIntoBytes(), size());  // No copy
    287   }
    288 
    289   // =================================================================
    290   // ByteIterator
    291 
    292   @Override
    293   public ByteIterator iterator() {
    294     return new LiteralByteIterator();
    295   }
    296 
    297   private class LiteralByteIterator implements ByteIterator {
    298     private int position;
    299     private final int limit;
    300 
    301     private LiteralByteIterator() {
    302       position = 0;
    303       limit = size();
    304     }
    305 
    306     public boolean hasNext() {
    307       return (position < limit);
    308     }
    309 
    310     public Byte next() {
    311       // Boxing calls Byte.valueOf(byte), which does not instantiate.
    312       return nextByte();
    313     }
    314 
    315     public byte nextByte() {
    316       try {
    317         return bytes[position++];
    318       } catch (ArrayIndexOutOfBoundsException e) {
    319         throw new NoSuchElementException(e.getMessage());
    320       }
    321     }
    322 
    323     public void remove() {
    324       throw new UnsupportedOperationException();
    325     }
    326   }
    327 
    328   // =================================================================
    329   // Internal methods
    330 
    331   @Override
    332   protected int getTreeDepth() {
    333     return 0;
    334   }
    335 
    336   @Override
    337   protected boolean isBalanced() {
    338     return true;
    339   }
    340 
    341   /**
    342    * Offset into {@code bytes[]} to use, non-zero for substrings.
    343    *
    344    * @return always 0 for this class
    345    */
    346   protected int getOffsetIntoBytes() {
    347     return 0;
    348   }
    349 }
    350