// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // http://code.google.com/p/protobuf/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package com.google.protobuf; import java.io.InputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FilterOutputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.List; /** * Immutable array of bytes. * * @author crazybob@google.com Bob Lee * @author kenton@google.com Kenton Varda */ public final class ByteString { private final byte[] bytes; private ByteString(final byte[] bytes) { this.bytes = bytes; } /** * Gets the byte at the given index. * * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size */ public byte byteAt(final int index) { return bytes[index]; } /** * Gets the number of bytes. */ public int size() { return bytes.length; } /** * Returns {@code true} if the size is {@code 0}, {@code false} otherwise. */ public boolean isEmpty() { return bytes.length == 0; } // ================================================================= // byte[] -> ByteString /** * Empty ByteString. */ public static final ByteString EMPTY = new ByteString(new byte[0]); /** * Copies the given bytes into a {@code ByteString}. */ public static ByteString copyFrom(final byte[] bytes, final int offset, final int size) { final byte[] copy = new byte[size]; System.arraycopy(bytes, offset, copy, 0, size); return new ByteString(copy); } /** * Copies the given bytes into a {@code ByteString}. */ public static ByteString copyFrom(final byte[] bytes) { return copyFrom(bytes, 0, bytes.length); } /** * Copies {@code size} bytes from a {@code java.nio.ByteBuffer} into * a {@code ByteString}. */ public static ByteString copyFrom(final ByteBuffer bytes, final int size) { final byte[] copy = new byte[size]; bytes.get(copy); return new ByteString(copy); } /** * Copies the remaining bytes from a {@code java.nio.ByteBuffer} into * a {@code ByteString}. */ public static ByteString copyFrom(final ByteBuffer bytes) { return copyFrom(bytes, bytes.remaining()); } /** * Encodes {@code text} into a sequence of bytes using the named charset * and returns the result as a {@code ByteString}. */ public static ByteString copyFrom(final String text, final String charsetName) throws UnsupportedEncodingException { return new ByteString(text.getBytes(charsetName)); } /** * Encodes {@code text} into a sequence of UTF-8 bytes and returns the * result as a {@code ByteString}. */ public static ByteString copyFromUtf8(final String text) { try { return new ByteString(text.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 not supported?", e); } } /** * Concatenates all byte strings in the list and returns the result. * * <p>The returned {@code ByteString} is not necessarily a unique object. * If the list is empty, the returned object is the singleton empty * {@code ByteString}. If the list has only one element, that * {@code ByteString} will be returned without copying. */ public static ByteString copyFrom(List<ByteString> list) { if (list.size() == 0) { return EMPTY; } else if (list.size() == 1) { return list.get(0); } int size = 0; for (ByteString str : list) { size += str.size(); } byte[] bytes = new byte[size]; int pos = 0; for (ByteString str : list) { System.arraycopy(str.bytes, 0, bytes, pos, str.size()); pos += str.size(); } return new ByteString(bytes); } // ================================================================= // ByteString -> byte[] /** * Copies bytes into a buffer at the given offset. * * @param target buffer to copy into * @param offset in the target buffer */ public void copyTo(final byte[] target, final int offset) { System.arraycopy(bytes, 0, target, offset, bytes.length); } /** * Copies bytes into a buffer. * * @param target buffer to copy into * @param sourceOffset offset within these bytes * @param targetOffset offset within the target buffer * @param size number of bytes to copy */ public void copyTo(final byte[] target, final int sourceOffset, final int targetOffset, final int size) { System.arraycopy(bytes, sourceOffset, target, targetOffset, size); } /** * Copies bytes into a ByteBuffer. * * @param target ByteBuffer to copy into. * @throws ReadOnlyBufferException if the {@code target} is read-only * @throws BufferOverflowException if the {@code target}'s remaining() * space is not large enough to hold the data. */ public void copyTo(ByteBuffer target) { target.put(bytes, 0, bytes.length); } /** * Copies bytes to a {@code byte[]}. */ public byte[] toByteArray() { final int size = bytes.length; final byte[] copy = new byte[size]; System.arraycopy(bytes, 0, copy, 0, size); return copy; } /** * Constructs a new read-only {@code java.nio.ByteBuffer} with the * same backing byte array. */ public ByteBuffer asReadOnlyByteBuffer() { final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); return byteBuffer.asReadOnlyBuffer(); } /** * Constructs a new {@code String} by decoding the bytes using the * specified charset. */ public String toString(final String charsetName) throws UnsupportedEncodingException { return new String(bytes, charsetName); } /** * Constructs a new {@code String} by decoding the bytes as UTF-8. */ public String toStringUtf8() { try { return new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 not supported?", e); } } // ================================================================= // equals() and hashCode() @Override public boolean equals(final Object o) { if (o == this) { return true; } if (!(o instanceof ByteString)) { return false; } final ByteString other = (ByteString) o; final int size = bytes.length; if (size != other.bytes.length) { return false; } final byte[] thisBytes = bytes; final byte[] otherBytes = other.bytes; for (int i = 0; i < size; i++) { if (thisBytes[i] != otherBytes[i]) { return false; } } return true; } private volatile int hash = 0; @Override public int hashCode() { int h = hash; if (h == 0) { final byte[] thisBytes = bytes; final int size = bytes.length; h = size; for (int i = 0; i < size; i++) { h = h * 31 + thisBytes[i]; } if (h == 0) { h = 1; } hash = h; } return h; } // ================================================================= // Input stream /** * Creates an {@code InputStream} which can be used to read the bytes. */ public InputStream newInput() { return new ByteArrayInputStream(bytes); } /** * Creates a {@link CodedInputStream} which can be used to read the bytes. * Using this is more efficient than creating a {@link CodedInputStream} * wrapping the result of {@link #newInput()}. */ public CodedInputStream newCodedInput() { // We trust CodedInputStream not to modify the bytes, or to give anyone // else access to them. return CodedInputStream.newInstance(bytes); } // ================================================================= // Output stream /** * Creates a new {@link Output} with the given initial capacity. */ public static Output newOutput(final int initialCapacity) { return new Output(new ByteArrayOutputStream(initialCapacity)); } /** * Creates a new {@link Output}. */ public static Output newOutput() { return newOutput(32); } /** * Outputs to a {@code ByteString} instance. Call {@link #toByteString()} to * create the {@code ByteString} instance. */ public static final class Output extends FilterOutputStream { private final ByteArrayOutputStream bout; /** * Constructs a new output with the given initial capacity. */ private Output(final ByteArrayOutputStream bout) { super(bout); this.bout = bout; } /** * Creates a {@code ByteString} instance from this {@code Output}. */ public ByteString toByteString() { final byte[] byteArray = bout.toByteArray(); return new ByteString(byteArray); } } /** * Constructs a new ByteString builder, which allows you to efficiently * construct a {@code ByteString} by writing to a {@link CodedOutputStream}. * Using this is much more efficient than calling {@code newOutput()} and * wrapping that in a {@code CodedOutputStream}. * * <p>This is package-private because it's a somewhat confusing interface. * Users can call {@link Message#toByteString()} instead of calling this * directly. * * @param size The target byte size of the {@code ByteString}. You must * write exactly this many bytes before building the result. */ static CodedBuilder newCodedBuilder(final int size) { return new CodedBuilder(size); } /** See {@link ByteString#newCodedBuilder(int)}. */ static final class CodedBuilder { private final CodedOutputStream output; private final byte[] buffer; private CodedBuilder(final int size) { buffer = new byte[size]; output = CodedOutputStream.newInstance(buffer); } public ByteString build() { output.checkNoSpaceLeft(); // We can be confident that the CodedOutputStream will not modify the // underlying bytes anymore because it already wrote all of them. So, // no need to make a copy. return new ByteString(buffer); } public CodedOutputStream getCodedOutput() { return output; } } }