From cd980d1c13c736b0f9fc453843f696a93c2c2a71 Mon Sep 17 00:00:00 2001 From: Feng Xiao Date: Wed, 19 Nov 2014 15:58:54 -0800 Subject: Prepare nano proto code to be merged into protobuf repository. --- .../protobuf/nano/CodedInputByteBufferNano.java | 641 +++++++++++++++ .../protobuf/nano/CodedOutputByteBufferNano.java | 879 +++++++++++++++++++++ .../protobuf/nano/ExtendableMessageNano.java | 187 +++++ .../java/com/google/protobuf/nano/Extension.java | 722 +++++++++++++++++ .../java/com/google/protobuf/nano/FieldArray.java | 273 +++++++ .../java/com/google/protobuf/nano/FieldData.java | 190 +++++ .../com/google/protobuf/nano/InternalNano.java | 333 ++++++++ .../nano/InvalidProtocolBufferNanoException.java | 93 +++ .../java/com/google/protobuf/nano/MessageNano.java | 190 +++++ .../google/protobuf/nano/MessageNanoPrinter.java | 257 ++++++ .../com/google/protobuf/nano/UnknownFieldData.java | 84 ++ .../com/google/protobuf/nano/WireFormatNano.java | 124 +++ 12 files changed, 3973 insertions(+) create mode 100644 javanano/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/Extension.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/FieldArray.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/FieldData.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/InternalNano.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/InvalidProtocolBufferNanoException.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/MessageNano.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java create mode 100644 javanano/src/main/java/com/google/protobuf/nano/WireFormatNano.java (limited to 'javanano/src/main') diff --git a/javanano/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java b/javanano/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java new file mode 100644 index 00000000..c5fea5ae --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java @@ -0,0 +1,641 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.io.IOException; + +/** + * Reads and decodes protocol message fields. + * + * This class contains two kinds of methods: methods that read specific + * protocol message constructs and field types (e.g. {@link #readTag()} and + * {@link #readInt32()}) and methods that read low-level values (e.g. + * {@link #readRawVarint32()} and {@link #readRawBytes}). If you are reading + * encoded protocol messages, you should use the former methods, but if you are + * reading some other format of your own design, use the latter. + * + * @author kenton@google.com Kenton Varda + */ +public final class CodedInputByteBufferNano { + /** + * Create a new CodedInputStream wrapping the given byte array. + */ + public static CodedInputByteBufferNano newInstance(final byte[] buf) { + return newInstance(buf, 0, buf.length); + } + + /** + * Create a new CodedInputStream wrapping the given byte array slice. + */ + public static CodedInputByteBufferNano newInstance(final byte[] buf, final int off, + final int len) { + return new CodedInputByteBufferNano(buf, off, len); + } + + // ----------------------------------------------------------------- + + /** + * Attempt to read a field tag, returning zero if we have reached EOF. + * Protocol message parsers use this to read tags, since a protocol message + * may legally end wherever a tag occurs, and zero is not a valid tag number. + */ + public int readTag() throws IOException { + if (isAtEnd()) { + lastTag = 0; + return 0; + } + + lastTag = readRawVarint32(); + if (lastTag == 0) { + // If we actually read zero, that's not a valid tag. + throw InvalidProtocolBufferNanoException.invalidTag(); + } + return lastTag; + } + + /** + * Verifies that the last call to readTag() returned the given tag value. + * This is used to verify that a nested group ended with the correct + * end tag. + * + * @throws InvalidProtocolBufferNanoException {@code value} does not match the + * last tag. + */ + public void checkLastTagWas(final int value) + throws InvalidProtocolBufferNanoException { + if (lastTag != value) { + throw InvalidProtocolBufferNanoException.invalidEndTag(); + } + } + + /** + * Reads and discards a single field, given its tag value. + * + * @return {@code false} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code true}. + */ + public boolean skipField(final int tag) throws IOException { + switch (WireFormatNano.getTagWireType(tag)) { + case WireFormatNano.WIRETYPE_VARINT: + readInt32(); + return true; + case WireFormatNano.WIRETYPE_FIXED64: + readRawLittleEndian64(); + return true; + case WireFormatNano.WIRETYPE_LENGTH_DELIMITED: + skipRawBytes(readRawVarint32()); + return true; + case WireFormatNano.WIRETYPE_START_GROUP: + skipMessage(); + checkLastTagWas( + WireFormatNano.makeTag(WireFormatNano.getTagFieldNumber(tag), + WireFormatNano.WIRETYPE_END_GROUP)); + return true; + case WireFormatNano.WIRETYPE_END_GROUP: + return false; + case WireFormatNano.WIRETYPE_FIXED32: + readRawLittleEndian32(); + return true; + default: + throw InvalidProtocolBufferNanoException.invalidWireType(); + } + } + + /** + * Reads and discards an entire message. This will read either until EOF + * or until an endgroup tag, whichever comes first. + */ + public void skipMessage() throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag)) { + return; + } + } + } + + // ----------------------------------------------------------------- + + /** Read a {@code double} field value from the stream. */ + public double readDouble() throws IOException { + return Double.longBitsToDouble(readRawLittleEndian64()); + } + + /** Read a {@code float} field value from the stream. */ + public float readFloat() throws IOException { + return Float.intBitsToFloat(readRawLittleEndian32()); + } + + /** Read a {@code uint64} field value from the stream. */ + public long readUInt64() throws IOException { + return readRawVarint64(); + } + + /** Read an {@code int64} field value from the stream. */ + public long readInt64() throws IOException { + return readRawVarint64(); + } + + /** Read an {@code int32} field value from the stream. */ + public int readInt32() throws IOException { + return readRawVarint32(); + } + + /** Read a {@code fixed64} field value from the stream. */ + public long readFixed64() throws IOException { + return readRawLittleEndian64(); + } + + /** Read a {@code fixed32} field value from the stream. */ + public int readFixed32() throws IOException { + return readRawLittleEndian32(); + } + + /** Read a {@code bool} field value from the stream. */ + public boolean readBool() throws IOException { + return readRawVarint32() != 0; + } + + /** Read a {@code string} field value from the stream. */ + public String readString() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final String result = new String(buffer, bufferPos, size, "UTF-8"); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return new String(readRawBytes(size), "UTF-8"); + } + } + + /** Read a {@code group} field value from the stream. */ + public void readGroup(final MessageNano msg, final int fieldNumber) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferNanoException.recursionLimitExceeded(); + } + ++recursionDepth; + msg.mergeFrom(this); + checkLastTagWas( + WireFormatNano.makeTag(fieldNumber, WireFormatNano.WIRETYPE_END_GROUP)); + --recursionDepth; + } + + public void readMessage(final MessageNano msg) + throws IOException { + final int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferNanoException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + msg.mergeFrom(this); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + } + + /** Read a {@code bytes} field value from the stream. */ + public byte[] readBytes() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final byte[] result = new byte[size]; + System.arraycopy(buffer, bufferPos, result, 0, size); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return readRawBytes(size); + } + } + + /** Read a {@code uint32} field value from the stream. */ + public int readUInt32() throws IOException { + return readRawVarint32(); + } + + /** + * Read an enum field value from the stream. Caller is responsible + * for converting the numeric value to an actual enum. + */ + public int readEnum() throws IOException { + return readRawVarint32(); + } + + /** Read an {@code sfixed32} field value from the stream. */ + public int readSFixed32() throws IOException { + return readRawLittleEndian32(); + } + + /** Read an {@code sfixed64} field value from the stream. */ + public long readSFixed64() throws IOException { + return readRawLittleEndian64(); + } + + /** Read an {@code sint32} field value from the stream. */ + public int readSInt32() throws IOException { + return decodeZigZag32(readRawVarint32()); + } + + /** Read an {@code sint64} field value from the stream. */ + public long readSInt64() throws IOException { + return decodeZigZag64(readRawVarint64()); + } + + // ================================================================= + + /** + * Read a raw Varint from the stream. If larger than 32 bits, discard the + * upper bits. + */ + public int readRawVarint32() throws IOException { + byte tmp = readRawByte(); + if (tmp >= 0) { + return tmp; + } + int result = tmp & 0x7f; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 7; + } else { + result |= (tmp & 0x7f) << 7; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 14; + } else { + result |= (tmp & 0x7f) << 14; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 21; + } else { + result |= (tmp & 0x7f) << 21; + result |= (tmp = readRawByte()) << 28; + if (tmp < 0) { + // Discard upper 32 bits. + for (int i = 0; i < 5; i++) { + if (readRawByte() >= 0) { + return result; + } + } + throw InvalidProtocolBufferNanoException.malformedVarint(); + } + } + } + } + return result; + } + + /** Read a raw Varint from the stream. */ + public long readRawVarint64() throws IOException { + int shift = 0; + long result = 0; + while (shift < 64) { + final byte b = readRawByte(); + result |= (long)(b & 0x7F) << shift; + if ((b & 0x80) == 0) { + return result; + } + shift += 7; + } + throw InvalidProtocolBufferNanoException.malformedVarint(); + } + + /** Read a 32-bit little-endian integer from the stream. */ + public int readRawLittleEndian32() throws IOException { + final byte b1 = readRawByte(); + final byte b2 = readRawByte(); + final byte b3 = readRawByte(); + final byte b4 = readRawByte(); + return ((b1 & 0xff) ) | + ((b2 & 0xff) << 8) | + ((b3 & 0xff) << 16) | + ((b4 & 0xff) << 24); + } + + /** Read a 64-bit little-endian integer from the stream. */ + public long readRawLittleEndian64() throws IOException { + final byte b1 = readRawByte(); + final byte b2 = readRawByte(); + final byte b3 = readRawByte(); + final byte b4 = readRawByte(); + final byte b5 = readRawByte(); + final byte b6 = readRawByte(); + final byte b7 = readRawByte(); + final byte b8 = readRawByte(); + return (((long)b1 & 0xff) ) | + (((long)b2 & 0xff) << 8) | + (((long)b3 & 0xff) << 16) | + (((long)b4 & 0xff) << 24) | + (((long)b5 & 0xff) << 32) | + (((long)b6 & 0xff) << 40) | + (((long)b7 & 0xff) << 48) | + (((long)b8 & 0xff) << 56); + } + + /** + * Decode 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 An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + * @return A signed 32-bit integer. + */ + public static int decodeZigZag32(final int n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Decode 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 An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + * @return A signed 64-bit integer. + */ + public static long decodeZigZag64(final long n) { + return (n >>> 1) ^ -(n & 1); + } + + // ----------------------------------------------------------------- + + private final byte[] buffer; + private int bufferStart; + private int bufferSize; + private int bufferSizeAfterLimit; + private int bufferPos; + private int lastTag; + + /** The absolute position of the end of the current message. */ + private int currentLimit = Integer.MAX_VALUE; + + /** See setRecursionLimit() */ + private int recursionDepth; + private int recursionLimit = DEFAULT_RECURSION_LIMIT; + + /** See setSizeLimit() */ + private int sizeLimit = DEFAULT_SIZE_LIMIT; + + private static final int DEFAULT_RECURSION_LIMIT = 64; + private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB + + private CodedInputByteBufferNano(final byte[] buffer, final int off, final int len) { + this.buffer = buffer; + bufferStart = off; + bufferSize = off + len; + bufferPos = off; + } + + /** + * Set the maximum message recursion depth. In order to prevent malicious + * messages from causing stack overflows, {@code CodedInputStream} limits + * how deeply messages may be nested. The default limit is 64. + * + * @return the old limit. + */ + public int setRecursionLimit(final int limit) { + if (limit < 0) { + throw new IllegalArgumentException( + "Recursion limit cannot be negative: " + limit); + } + final int oldLimit = recursionLimit; + recursionLimit = limit; + return oldLimit; + } + + /** + * Set the maximum message size. In order to prevent malicious + * messages from exhausting memory or causing integer overflows, + * {@code CodedInputStream} limits how large a message may be. + * The default limit is 64MB. You should set this limit as small + * as you can without harming your app's functionality. Note that + * size limits only apply when reading from an {@code InputStream}, not + * when constructed around a raw byte array. + *

+ * If you want to read several messages from a single CodedInputStream, you + * could call {@link #resetSizeCounter()} after each one to avoid hitting the + * size limit. + * + * @return the old limit. + */ + public int setSizeLimit(final int limit) { + if (limit < 0) { + throw new IllegalArgumentException( + "Size limit cannot be negative: " + limit); + } + final int oldLimit = sizeLimit; + sizeLimit = limit; + return oldLimit; + } + + /** + * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). + */ + public void resetSizeCounter() { + } + + /** + * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This + * is called when descending into a length-delimited embedded message. + * + * @return the old limit. + */ + public int pushLimit(int byteLimit) throws InvalidProtocolBufferNanoException { + if (byteLimit < 0) { + throw InvalidProtocolBufferNanoException.negativeSize(); + } + byteLimit += bufferPos; + final int oldLimit = currentLimit; + if (byteLimit > oldLimit) { + throw InvalidProtocolBufferNanoException.truncatedMessage(); + } + currentLimit = byteLimit; + + recomputeBufferSizeAfterLimit(); + + return oldLimit; + } + + private void recomputeBufferSizeAfterLimit() { + bufferSize += bufferSizeAfterLimit; + final int bufferEnd = bufferSize; + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterLimit = bufferEnd - currentLimit; + bufferSize -= bufferSizeAfterLimit; + } else { + bufferSizeAfterLimit = 0; + } + } + + /** + * Discards the current limit, returning to the previous limit. + * + * @param oldLimit The old limit, as returned by {@code pushLimit}. + */ + public void popLimit(final int oldLimit) { + currentLimit = oldLimit; + recomputeBufferSizeAfterLimit(); + } + + /** + * Returns the number of bytes to be read before the current limit. + * If no limit is set, returns -1. + */ + public int getBytesUntilLimit() { + if (currentLimit == Integer.MAX_VALUE) { + return -1; + } + + final int currentAbsolutePosition = bufferPos; + return currentLimit - currentAbsolutePosition; + } + + /** + * Returns true if the stream has reached the end of the input. This is the + * case if either the end of the underlying input source has been reached or + * if the stream has reached a limit created using {@link #pushLimit(int)}. + */ + public boolean isAtEnd() { + return bufferPos == bufferSize; + } + + /** + * Get current position in buffer relative to beginning offset. + */ + public int getPosition() { + return bufferPos - bufferStart; + } + + /** + * Retrieves a subset of data in the buffer. The returned array is not backed by the original + * buffer array. + * + * @param offset the position (relative to the buffer start position) to start at. + * @param length the number of bytes to retrieve. + */ + public byte[] getData(int offset, int length) { + if (length == 0) { + return WireFormatNano.EMPTY_BYTES; + } + byte[] copy = new byte[length]; + int start = bufferStart + offset; + System.arraycopy(buffer, start, copy, 0, length); + return copy; + } + + /** + * Rewind to previous position. Cannot go forward. + */ + public void rewindToPosition(int position) { + if (position > bufferPos - bufferStart) { + throw new IllegalArgumentException( + "Position " + position + " is beyond current " + (bufferPos - bufferStart)); + } + if (position < 0) { + throw new IllegalArgumentException("Bad position " + position); + } + bufferPos = bufferStart + position; + } + + /** + * Read one byte from the input. + * + * @throws InvalidProtocolBufferNanoException The end of the stream or the current + * limit was reached. + */ + public byte readRawByte() throws IOException { + if (bufferPos == bufferSize) { + throw InvalidProtocolBufferNanoException.truncatedMessage(); + } + return buffer[bufferPos++]; + } + + /** + * Read a fixed size of bytes from the input. + * + * @throws InvalidProtocolBufferNanoException The end of the stream or the current + * limit was reached. + */ + public byte[] readRawBytes(final int size) throws IOException { + if (size < 0) { + throw InvalidProtocolBufferNanoException.negativeSize(); + } + + if (bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - bufferPos); + // Then fail. + throw InvalidProtocolBufferNanoException.truncatedMessage(); + } + + if (size <= bufferSize - bufferPos) { + // We have all the bytes we need already. + final byte[] bytes = new byte[size]; + System.arraycopy(buffer, bufferPos, bytes, 0, size); + bufferPos += size; + return bytes; + } else { + throw InvalidProtocolBufferNanoException.truncatedMessage(); + } + } + + /** + * Reads and discards {@code size} bytes. + * + * @throws InvalidProtocolBufferNanoException The end of the stream or the current + * limit was reached. + */ + public void skipRawBytes(final int size) throws IOException { + if (size < 0) { + throw InvalidProtocolBufferNanoException.negativeSize(); + } + + if (bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - bufferPos); + // Then fail. + throw InvalidProtocolBufferNanoException.truncatedMessage(); + } + + if (size <= bufferSize - bufferPos) { + // We have all the bytes we need already. + bufferPos += size; + } else { + throw InvalidProtocolBufferNanoException.truncatedMessage(); + } + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java b/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java new file mode 100644 index 00000000..88df38d7 --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java @@ -0,0 +1,879 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +/** + * Encodes and writes protocol message fields. + * + *

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. + * + *

This class is totally unsynchronized. + * + * @author kneton@google.com Kenton Varda + */ +public final class CodedOutputByteBufferNano { + private final byte[] buffer; + private final int limit; + private int position; + + private CodedOutputByteBufferNano(final byte[] buffer, final int offset, + final int length) { + this.buffer = buffer; + position = offset; + limit = offset + length; + } + + /** + * 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}. + */ + public static CodedOutputByteBufferNano 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}. + */ + public static CodedOutputByteBufferNano newInstance(final byte[] flatArray, + final int offset, + final int length) { + return new CodedOutputByteBufferNano(flatArray, offset, length); + } + + // ----------------------------------------------------------------- + + /** Write a {@code double} field, including tag, to the stream. */ + public void writeDouble(final int fieldNumber, final double value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_FIXED64); + writeDoubleNoTag(value); + } + + /** Write a {@code float} field, including tag, to the stream. */ + public void writeFloat(final int fieldNumber, final float value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_FIXED32); + writeFloatNoTag(value); + } + + /** Write a {@code uint64} field, including tag, to the stream. */ + public void writeUInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_VARINT); + writeUInt64NoTag(value); + } + + /** Write an {@code int64} field, including tag, to the stream. */ + public void writeInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_VARINT); + writeInt64NoTag(value); + } + + /** Write an {@code int32} field, including tag, to the stream. */ + public void writeInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_VARINT); + writeInt32NoTag(value); + } + + /** Write a {@code fixed64} field, including tag, to the stream. */ + public void writeFixed64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_FIXED64); + writeFixed64NoTag(value); + } + + /** Write a {@code fixed32} field, including tag, to the stream. */ + public void writeFixed32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_FIXED32); + writeFixed32NoTag(value); + } + + /** Write a {@code bool} field, including tag, to the stream. */ + public void writeBool(final int fieldNumber, final boolean value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_VARINT); + writeBoolNoTag(value); + } + + /** Write a {@code string} field, including tag, to the stream. */ + public void writeString(final int fieldNumber, final String value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + /** Write a {@code group} field, including tag, to the stream. */ + public void writeGroup(final int fieldNumber, final MessageNano value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_START_GROUP); + writeGroupNoTag(value); + writeTag(fieldNumber, WireFormatNano.WIRETYPE_END_GROUP); + } + + /** Write an embedded message field, including tag, to the stream. */ + public void writeMessage(final int fieldNumber, final MessageNano value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + /** Write a {@code bytes} field, including tag, to the stream. */ + public void writeBytes(final int fieldNumber, final byte[] value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + /** Write a {@code uint32} field, including tag, to the stream. */ + public void writeUInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_VARINT); + writeUInt32NoTag(value); + } + + /** + * Write an enum field, including tag, to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ + public void writeEnum(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_VARINT); + writeEnumNoTag(value); + } + + /** Write an {@code sfixed32} field, including tag, to the stream. */ + public void writeSFixed32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_FIXED32); + writeSFixed32NoTag(value); + } + + /** Write an {@code sfixed64} field, including tag, to the stream. */ + public void writeSFixed64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_FIXED64); + writeSFixed64NoTag(value); + } + + /** Write an {@code sint32} field, including tag, to the stream. */ + public void writeSInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_VARINT); + writeSInt32NoTag(value); + } + + /** Write an {@code sint64} field, including tag, to the stream. */ + public void writeSInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatNano.WIRETYPE_VARINT); + writeSInt64NoTag(value); + } + + /** + * Write a MessageSet extension field to the stream. For historical reasons, + * the wire format differs from normal fields. + */ +// public void writeMessageSetExtension(final int fieldNumber, +// final MessageMicro value) +// throws IOException { +// writeTag(WireFormatMicro.MESSAGE_SET_ITEM, WireFormatMicro.WIRETYPE_START_GROUP); +// writeUInt32(WireFormatMicro.MESSAGE_SET_TYPE_ID, fieldNumber); +// writeMessage(WireFormatMicro.MESSAGE_SET_MESSAGE, value); +// writeTag(WireFormatMicro.MESSAGE_SET_ITEM, WireFormatMicro.WIRETYPE_END_GROUP); +// } + + /** + * Write an unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ +// public void writeRawMessageSetExtension(final int fieldNumber, +// final ByteStringMicro value) +// throws IOException { +// writeTag(WireFormatMicro.MESSAGE_SET_ITEM, WireFormatMicro.WIRETYPE_START_GROUP); +// writeUInt32(WireFormatMicro.MESSAGE_SET_TYPE_ID, fieldNumber); +// writeBytes(WireFormatMicro.MESSAGE_SET_MESSAGE, value); +// writeTag(WireFormatMicro.MESSAGE_SET_ITEM, WireFormatMicro.WIRETYPE_END_GROUP); +// } + + // ----------------------------------------------------------------- + + /** Write a {@code double} field to the stream. */ + public void writeDoubleNoTag(final double value) throws IOException { + writeRawLittleEndian64(Double.doubleToLongBits(value)); + } + + /** Write a {@code float} field to the stream. */ + public void writeFloatNoTag(final float value) throws IOException { + writeRawLittleEndian32(Float.floatToIntBits(value)); + } + + /** Write a {@code uint64} field to the stream. */ + public void writeUInt64NoTag(final long value) throws IOException { + writeRawVarint64(value); + } + + /** Write an {@code int64} field to the stream. */ + public void writeInt64NoTag(final long value) throws IOException { + writeRawVarint64(value); + } + + /** Write an {@code int32} field to the stream. */ + public void writeInt32NoTag(final int value) throws IOException { + if (value >= 0) { + writeRawVarint32(value); + } else { + // Must sign-extend. + writeRawVarint64(value); + } + } + + /** Write a {@code fixed64} field to the stream. */ + public void writeFixed64NoTag(final long value) throws IOException { + writeRawLittleEndian64(value); + } + + /** Write a {@code fixed32} field to the stream. */ + public void writeFixed32NoTag(final int value) throws IOException { + writeRawLittleEndian32(value); + } + + /** Write a {@code bool} field to the stream. */ + public void writeBoolNoTag(final boolean value) throws IOException { + writeRawByte(value ? 1 : 0); + } + + /** Write a {@code string} field to the stream. */ + public void writeStringNoTag(final String value) throws IOException { + // 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. + final byte[] bytes = value.getBytes("UTF-8"); + writeRawVarint32(bytes.length); + writeRawBytes(bytes); + } + + /** Write a {@code group} field to the stream. */ + public void writeGroupNoTag(final MessageNano value) throws IOException { + value.writeTo(this); + } + + /** Write an embedded message field to the stream. */ + public void writeMessageNoTag(final MessageNano value) throws IOException { + writeRawVarint32(value.getCachedSize()); + value.writeTo(this); + } + + /** Write a {@code bytes} field to the stream. */ + public void writeBytesNoTag(final byte[] value) throws IOException { + writeRawVarint32(value.length); + writeRawBytes(value); + } + + /** Write a {@code uint32} field to the stream. */ + public void writeUInt32NoTag(final int value) throws IOException { + writeRawVarint32(value); + } + + /** + * Write an enum field to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ + public void writeEnumNoTag(final int value) throws IOException { + writeRawVarint32(value); + } + + /** Write an {@code sfixed32} field to the stream. */ + public void writeSFixed32NoTag(final int value) throws IOException { + writeRawLittleEndian32(value); + } + + /** Write an {@code sfixed64} field to the stream. */ + public void writeSFixed64NoTag(final long value) throws IOException { + writeRawLittleEndian64(value); + } + + /** Write an {@code sint32} field to the stream. */ + public void writeSInt32NoTag(final int value) throws IOException { + writeRawVarint32(encodeZigZag32(value)); + } + + /** Write an {@code sint64} field to the stream. */ + public void writeSInt64NoTag(final long value) throws IOException { + writeRawVarint64(encodeZigZag64(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 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 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 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 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 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 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 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 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 group} field, including tag. + */ + public static int computeGroupSize(final int fieldNumber, + final MessageNano value) { + return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(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 MessageNano value) { + return computeTagSize(fieldNumber) + computeMessageSizeNoTag(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 byte[] value) { + return computeTagSize(fieldNumber) + computeBytesSizeNoTag(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 + * enum field, including tag. Caller is responsible for converting the + * enum value to its numeric 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 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 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 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 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 + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. + */ +// public static int computeMessageSetExtensionSize( +// final int fieldNumber, final MessageMicro value) { +// return computeTagSize(WireFormatMicro.MESSAGE_SET_ITEM) * 2 + +// computeUInt32Size(WireFormatMicro.MESSAGE_SET_TYPE_ID, fieldNumber) + +// computeMessageSize(WireFormatMicro.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 ByteStringMicro value) { +// return computeTagSize(WireFormatMicro.MESSAGE_SET_ITEM) * 2 + +// computeUInt32Size(WireFormatMicro.MESSAGE_SET_TYPE_ID, fieldNumber) + +// computeBytesSize(WireFormatMicro.MESSAGE_SET_MESSAGE, value); +// } + + // ----------------------------------------------------------------- + + /** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ + public static int computeDoubleSizeNoTag(final double value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ + public static int computeFloatSizeNoTag(final float value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64SizeNoTag(final long value) { + return computeRawVarint64Size(value); + } + + /** + * 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 computeRawVarint64Size(value); + } + + /** + * 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 computeRawVarint32Size(value); + } else { + // Must sign-extend. + return 10; + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field. + */ + public static int computeFixed64SizeNoTag(final long value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field. + */ + public static int computeFixed32SizeNoTag(final int value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field. + */ + public static int computeBoolSizeNoTag(final boolean value) { + return 1; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field. + */ + public static int computeStringSizeNoTag(final String value) { + try { + final byte[] bytes = value.getBytes("UTF-8"); + return computeRawVarint32Size(bytes.length) + + bytes.length; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported."); + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field. + */ + public static int computeGroupSizeNoTag(final MessageNano value) { + return value.getSerializedSize(); + } + + /** + * Compute the number of bytes that would be needed to encode an embedded + * message field. + */ + public static int computeMessageSizeNoTag(final MessageNano value) { + final int size = value.getSerializedSize(); + return computeRawVarint32Size(size) + size; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field. + */ + public static int computeBytesSizeNoTag(final byte[] value) { + return computeRawVarint32Size(value.length) + value.length; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field. + */ + public static int computeUInt32SizeNoTag(final int value) { + return computeRawVarint32Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an enum field. + * Caller is responsible for converting the enum value to its numeric value. + */ + public static int computeEnumSizeNoTag(final int value) { + return computeRawVarint32Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field. + */ + public static int computeSFixed32SizeNoTag(final int value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field. + */ + public static int computeSFixed64SizeNoTag(final long value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field. + */ + public static int computeSInt32SizeNoTag(final int value) { + return computeRawVarint32Size(encodeZigZag32(value)); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field. + */ + public static int computeSInt64SizeNoTag(final long value) { + return computeRawVarint64Size(encodeZigZag64(value)); + } + + // ================================================================= + + /** + * If writing to a flat array, return the space left in the array. + * Otherwise, throws {@code UnsupportedOperationException}. + */ + public int spaceLeft() { + return limit - position; + } + + /** + * 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 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; + + OutOfSpaceException(int position, int limit) { + super("CodedOutputStream was writing to a flat byte array and ran " + + "out of space (pos " + position + " limit " + limit + ")."); + } + } + + /** Write a single byte. */ + public void writeRawByte(final byte value) throws IOException { + if (position == limit) { + // We're writing to a single buffer. + throw new OutOfSpaceException(position, limit); + } + + buffer[position++] = value; + } + + /** Write a single byte, represented by an integer value. */ + public void writeRawByte(final int value) throws IOException { + writeRawByte((byte) value); + } + + /** Write an array of bytes. */ + public void writeRawBytes(final byte[] value) throws IOException { + writeRawBytes(value, 0, value.length); + } + + /** Write part of an array of bytes. */ + public void writeRawBytes(final 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; + } else { + // We're writing to a single buffer. + throw new OutOfSpaceException(position, limit); + } + } + + /** Encode and write a tag. */ + public void writeTag(final int fieldNumber, final int wireType) + throws IOException { + writeRawVarint32(WireFormatNano.makeTag(fieldNumber, wireType)); + } + + /** Compute the number of bytes that would be needed to encode a tag. */ + public static int computeTagSize(final int fieldNumber) { + return computeRawVarint32Size(WireFormatNano.makeTag(fieldNumber, 0)); + } + + /** + * Encode and write a varint. {@code value} is treated as + * unsigned, so it won't be sign-extended if negative. + */ + public void writeRawVarint32(int value) throws IOException { + while (true) { + if ((value & ~0x7F) == 0) { + writeRawByte(value); + return; + } else { + writeRawByte((value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + /** + * 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. + */ + public static int computeRawVarint32Size(final int value) { + if ((value & (0xffffffff << 7)) == 0) return 1; + if ((value & (0xffffffff << 14)) == 0) return 2; + if ((value & (0xffffffff << 21)) == 0) return 3; + if ((value & (0xffffffff << 28)) == 0) return 4; + return 5; + } + + /** Encode and write a varint. */ + public void writeRawVarint64(long value) throws IOException { + while (true) { + if ((value & ~0x7FL) == 0) { + writeRawByte((int)value); + return; + } else { + writeRawByte(((int)value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + /** Compute the number of bytes that would be needed to encode a varint. */ + public static int computeRawVarint64Size(final long value) { + if ((value & (0xffffffffffffffffL << 7)) == 0) return 1; + if ((value & (0xffffffffffffffffL << 14)) == 0) return 2; + if ((value & (0xffffffffffffffffL << 21)) == 0) return 3; + if ((value & (0xffffffffffffffffL << 28)) == 0) return 4; + if ((value & (0xffffffffffffffffL << 35)) == 0) return 5; + if ((value & (0xffffffffffffffffL << 42)) == 0) return 6; + if ((value & (0xffffffffffffffffL << 49)) == 0) return 7; + if ((value & (0xffffffffffffffffL << 56)) == 0) return 8; + if ((value & (0xffffffffffffffffL << 63)) == 0) return 9; + return 10; + } + + /** Write a little-endian 32-bit integer. */ + public void writeRawLittleEndian32(final int value) throws IOException { + writeRawByte((value ) & 0xFF); + writeRawByte((value >> 8) & 0xFF); + writeRawByte((value >> 16) & 0xFF); + writeRawByte((value >> 24) & 0xFF); + } + + public static final int LITTLE_ENDIAN_32_SIZE = 4; + + /** Write a little-endian 64-bit integer. */ + public void writeRawLittleEndian64(final long value) throws IOException { + writeRawByte((int)(value ) & 0xFF); + writeRawByte((int)(value >> 8) & 0xFF); + writeRawByte((int)(value >> 16) & 0xFF); + writeRawByte((int)(value >> 24) & 0xFF); + writeRawByte((int)(value >> 32) & 0xFF); + writeRawByte((int)(value >> 40) & 0xFF); + writeRawByte((int)(value >> 48) & 0xFF); + writeRawByte((int)(value >> 56) & 0xFF); + } + + public static final int LITTLE_ENDIAN_64_SIZE = 8; + + /** + * 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); + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java b/javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java new file mode 100644 index 00000000..46cd86f3 --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java @@ -0,0 +1,187 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.io.IOException; + +/** + * Base class of those Protocol Buffer messages that need to store unknown fields, + * such as extensions. + */ +public abstract class ExtendableMessageNano> + extends MessageNano { + /** + * A container for fields unknown to the message, including extensions. Extension fields can + * can be accessed through the {@link #getExtension} and {@link #setExtension} methods. + */ + protected FieldArray unknownFieldData; + + @Override + protected int computeSerializedSize() { + int size = 0; + if (unknownFieldData != null) { + for (int i = 0; i < unknownFieldData.size(); i++) { + FieldData field = unknownFieldData.dataAt(i); + size += field.computeSerializedSize(); + } + } + return size; + } + + @Override + public void writeTo(CodedOutputByteBufferNano output) throws IOException { + if (unknownFieldData == null) { + return; + } + for (int i = 0; i < unknownFieldData.size(); i++) { + FieldData field = unknownFieldData.dataAt(i); + field.writeTo(output); + } + } + + /** + * Checks if there is a value stored for the specified extension in this + * message. + */ + public final boolean hasExtension(Extension extension) { + if (unknownFieldData == null) { + return false; + } + FieldData field = unknownFieldData.get(WireFormatNano.getTagFieldNumber(extension.tag)); + return field != null; + } + + /** + * Gets the value stored in the specified extension of this message. + */ + public final T getExtension(Extension extension) { + if (unknownFieldData == null) { + return null; + } + FieldData field = unknownFieldData.get(WireFormatNano.getTagFieldNumber(extension.tag)); + return field == null ? null : field.getValue(extension); + } + + /** + * Sets the value of the specified extension of this message. + */ + public final M setExtension(Extension extension, T value) { + int fieldNumber = WireFormatNano.getTagFieldNumber(extension.tag); + if (value == null) { + if (unknownFieldData != null) { + unknownFieldData.remove(fieldNumber); + if (unknownFieldData.isEmpty()) { + unknownFieldData = null; + } + } + } else { + FieldData field = null; + if (unknownFieldData == null) { + unknownFieldData = new FieldArray(); + } else { + field = unknownFieldData.get(fieldNumber); + } + if (field == null) { + unknownFieldData.put(fieldNumber, new FieldData(extension, value)); + } else { + field.setValue(extension, value); + } + } + + @SuppressWarnings("unchecked") // Generated code should guarantee type safety + M typedThis = (M) this; + return typedThis; + } + + /** + * Stores the binary data of an unknown field. + * + *

Generated messages will call this for unknown fields if the store_unknown_fields + * option is on. + * + *

Note that the tag might be a end-group tag (rather than the start of an unknown field) in + * which case we do not want to add an unknown field entry. + * + * @param input the input buffer. + * @param tag the tag of the field. + + * @return {@literal true} unless the tag is an end-group tag. + */ + protected final boolean storeUnknownField(CodedInputByteBufferNano input, int tag) + throws IOException { + int startPos = input.getPosition(); + if (!input.skipField(tag)) { + return false; // This wasn't an unknown field, it's an end-group tag. + } + int fieldNumber = WireFormatNano.getTagFieldNumber(tag); + int endPos = input.getPosition(); + byte[] bytes = input.getData(startPos, endPos - startPos); + UnknownFieldData unknownField = new UnknownFieldData(tag, bytes); + + FieldData field = null; + if (unknownFieldData == null) { + unknownFieldData = new FieldArray(); + } else { + field = unknownFieldData.get(fieldNumber); + } + if (field == null) { + field = new FieldData(); + unknownFieldData.put(fieldNumber, field); + } + field.addUnknownField(unknownField); + return true; + } + + /** + * Returns whether the stored unknown field data in this message is equivalent to that in the + * other message. + * + * @param other the other message. + * @return whether the two sets of unknown field data are equal. + */ + protected final boolean unknownFieldDataEquals(M other) { + if (unknownFieldData == null || unknownFieldData.isEmpty()) { + return other.unknownFieldData == null || other.unknownFieldData.isEmpty(); + } else { + return unknownFieldData.equals(other.unknownFieldData); + } + } + + /** + * Computes the hashcode representing the unknown field data stored in this message. + * + * @return the hashcode for the unknown field data. + */ + protected final int unknownFieldDataHashCode() { + return (unknownFieldData == null || unknownFieldData.isEmpty() + ? 0 : unknownFieldData.hashCode()); + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/Extension.java b/javanano/src/main/java/com/google/protobuf/nano/Extension.java new file mode 100644 index 00000000..a851daf8 --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/Extension.java @@ -0,0 +1,722 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents an extension. + * + * @author bduff@google.com (Brian Duff) + * @author maxtroy@google.com (Max Cai) + * @param the type of the extendable message this extension is for. + * @param the Java type of the extension; see {@link #clazz}. + */ +public class Extension, T> { + + /* + * Because we typically only define message-typed extensions, the Extension class hierarchy is + * designed as follows, to allow a big amount of code in this file to be removed by ProGuard: + * + * Extension // ready to use for message/group typed extensions + * Δ + * | + * PrimitiveExtension // for primitive/enum typed extensions + */ + + public static final int TYPE_DOUBLE = 1; + public static final int TYPE_FLOAT = 2; + public static final int TYPE_INT64 = 3; + public static final int TYPE_UINT64 = 4; + public static final int TYPE_INT32 = 5; + public static final int TYPE_FIXED64 = 6; + public static final int TYPE_FIXED32 = 7; + public static final int TYPE_BOOL = 8; + public static final int TYPE_STRING = 9; + public static final int TYPE_GROUP = 10; + public static final int TYPE_MESSAGE = 11; + public static final int TYPE_BYTES = 12; + public static final int TYPE_UINT32 = 13; + public static final int TYPE_ENUM = 14; + public static final int TYPE_SFIXED32 = 15; + public static final int TYPE_SFIXED64 = 16; + public static final int TYPE_SINT32 = 17; + public static final int TYPE_SINT64 = 18; + + /** + * Creates an {@code Extension} of the given message type and tag number. + * Should be used by the generated code only. + * + * @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP} + */ + public static , T extends MessageNano> + Extension createMessageTyped(int type, Class clazz, int tag) { + return new Extension(type, clazz, tag, false); + } + + /** + * Creates a repeated {@code Extension} of the given message type and tag number. + * Should be used by the generated code only. + * + * @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP} + */ + public static , T extends MessageNano> + Extension createRepeatedMessageTyped(int type, Class clazz, int tag) { + return new Extension(type, clazz, tag, true); + } + + /** + * Creates an {@code Extension} of the given primitive type and tag number. + * Should be used by the generated code only. + * + * @param type one of {@code TYPE_*}, except {@link #TYPE_MESSAGE} and {@link #TYPE_GROUP} + * @param clazz the boxed Java type of this extension + */ + public static , T> + Extension createPrimitiveTyped(int type, Class clazz, int tag) { + return new PrimitiveExtension(type, clazz, tag, false, 0, 0); + } + + /** + * Creates a repeated {@code Extension} of the given primitive type and tag number. + * Should be used by the generated code only. + * + * @param type one of {@code TYPE_*}, except {@link #TYPE_MESSAGE} and {@link #TYPE_GROUP} + * @param clazz the Java array type of this extension, with an unboxed component type + */ + public static , T> + Extension createRepeatedPrimitiveTyped( + int type, Class clazz, int tag, int nonPackedTag, int packedTag) { + return new PrimitiveExtension(type, clazz, tag, true, nonPackedTag, packedTag); + } + + /** + * Protocol Buffer type of this extension; one of the {@code TYPE_} constants. + */ + protected final int type; + + /** + * Java type of this extension. For a singular extension, this is the boxed Java type for the + * Protocol Buffer {@link #type}; for a repeated extension, this is an array type whose + * component type is the unboxed Java type for {@link #type}. For example, for a singular + * {@code int32}/{@link #TYPE_INT32} extension, this equals {@code Integer.class}; for a + * repeated {@code int32} extension, this equals {@code int[].class}. + */ + protected final Class clazz; + + /** + * Tag number of this extension. + */ + public final int tag; + + /** + * Whether this extension is repeated. + */ + protected final boolean repeated; + + private Extension(int type, Class clazz, int tag, boolean repeated) { + this.type = type; + this.clazz = clazz; + this.tag = tag; + this.repeated = repeated; + } + + /** + * Returns the value of this extension stored in the given list of unknown fields, or + * {@code null} if no unknown fields matches this extension. + * + * @param unknownFields a list of {@link UnknownFieldData}. All of the elements must have a tag + * that matches this Extension's tag. + * + */ + final T getValueFrom(List unknownFields) { + if (unknownFields == null) { + return null; + } + return repeated ? getRepeatedValueFrom(unknownFields) : getSingularValueFrom(unknownFields); + } + + private T getRepeatedValueFrom(List unknownFields) { + // For repeated extensions, read all matching unknown fields in their original order. + List resultList = new ArrayList(); + for (int i = 0; i < unknownFields.size(); i++) { + UnknownFieldData data = unknownFields.get(i); + if (data.bytes.length != 0) { + readDataInto(data, resultList); + } + } + + int resultSize = resultList.size(); + if (resultSize == 0) { + return null; + } else { + T result = clazz.cast(Array.newInstance(clazz.getComponentType(), resultSize)); + for (int i = 0; i < resultSize; i++) { + Array.set(result, i, resultList.get(i)); + } + return result; + } + } + + private T getSingularValueFrom(List unknownFields) { + // For singular extensions, get the last piece of data stored under this extension. + if (unknownFields.isEmpty()) { + return null; + } + UnknownFieldData lastData = unknownFields.get(unknownFields.size() - 1); + return clazz.cast(readData(CodedInputByteBufferNano.newInstance(lastData.bytes))); + } + + protected Object readData(CodedInputByteBufferNano input) { + // This implementation is for message/group extensions. + Class messageType = repeated ? clazz.getComponentType() : clazz; + try { + switch (type) { + case TYPE_GROUP: + MessageNano group = (MessageNano) messageType.newInstance(); + input.readGroup(group, WireFormatNano.getTagFieldNumber(tag)); + return group; + case TYPE_MESSAGE: + MessageNano message = (MessageNano) messageType.newInstance(); + input.readMessage(message); + return message; + default: + throw new IllegalArgumentException("Unknown type " + type); + } + } catch (InstantiationException e) { + throw new IllegalArgumentException( + "Error creating instance of class " + messageType, e); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException( + "Error creating instance of class " + messageType, e); + } catch (IOException e) { + throw new IllegalArgumentException("Error reading extension field", e); + } + } + + protected void readDataInto(UnknownFieldData data, List resultList) { + // This implementation is for message/group extensions. + resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes))); + } + + void writeTo(Object value, CodedOutputByteBufferNano output) throws IOException { + if (repeated) { + writeRepeatedData(value, output); + } else { + writeSingularData(value, output); + } + } + + protected void writeSingularData(Object value, CodedOutputByteBufferNano out) { + // This implementation is for message/group extensions. + try { + out.writeRawVarint32(tag); + switch (type) { + case TYPE_GROUP: + MessageNano groupValue = (MessageNano) value; + int fieldNumber = WireFormatNano.getTagFieldNumber(tag); + out.writeGroupNoTag(groupValue); + // The endgroup tag must be included in the data payload. + out.writeTag(fieldNumber, WireFormatNano.WIRETYPE_END_GROUP); + break; + case TYPE_MESSAGE: + MessageNano messageValue = (MessageNano) value; + out.writeMessageNoTag(messageValue); + break; + default: + throw new IllegalArgumentException("Unknown type " + type); + } + } catch (IOException e) { + // Should not happen + throw new IllegalStateException(e); + } + } + + protected void writeRepeatedData(Object array, CodedOutputByteBufferNano output) { + // This implementation is for non-packed extensions. + int arrayLength = Array.getLength(array); + for (int i = 0; i < arrayLength; i++) { + Object element = Array.get(array, i); + if (element != null) { + writeSingularData(element, output); + } + } + } + + int computeSerializedSize(Object value) { + if (repeated) { + return computeRepeatedSerializedSize(value); + } else { + return computeSingularSerializedSize(value); + } + } + + protected int computeRepeatedSerializedSize(Object array) { + // This implementation is for non-packed extensions. + int size = 0; + int arrayLength = Array.getLength(array); + for (int i = 0; i < arrayLength; i++) { + Object element = Array.get(array, i); + if (element != null) { + size += computeSingularSerializedSize(Array.get(array, i)); + } + } + return size; + } + + protected int computeSingularSerializedSize(Object value) { + // This implementation is for message/group extensions. + int fieldNumber = WireFormatNano.getTagFieldNumber(tag); + switch (type) { + case TYPE_GROUP: + MessageNano groupValue = (MessageNano) value; + return CodedOutputByteBufferNano.computeGroupSize(fieldNumber, groupValue); + case TYPE_MESSAGE: + MessageNano messageValue = (MessageNano) value; + return CodedOutputByteBufferNano.computeMessageSize(fieldNumber, messageValue); + default: + throw new IllegalArgumentException("Unknown type " + type); + } + } + + /** + * Represents an extension of a primitive (including enum) type. If there is no primitive + * extensions, this subclass will be removable by ProGuard. + */ + private static class PrimitiveExtension, T> + extends Extension { + + /** + * Tag of a piece of non-packed data from the wire compatible with this extension. + */ + private final int nonPackedTag; + + /** + * Tag of a piece of packed data from the wire compatible with this extension. + * 0 if the type of this extension is not packable. + */ + private final int packedTag; + + public PrimitiveExtension(int type, Class clazz, int tag, boolean repeated, + int nonPackedTag, int packedTag) { + super(type, clazz, tag, repeated); + this.nonPackedTag = nonPackedTag; + this.packedTag = packedTag; + } + + @Override + protected Object readData(CodedInputByteBufferNano input) { + try { + switch (type) { + case TYPE_DOUBLE: + return input.readDouble(); + case TYPE_FLOAT: + return input.readFloat(); + case TYPE_INT64: + return input.readInt64(); + case TYPE_UINT64: + return input.readUInt64(); + case TYPE_INT32: + return input.readInt32(); + case TYPE_FIXED64: + return input.readFixed64(); + case TYPE_FIXED32: + return input.readFixed32(); + case TYPE_BOOL: + return input.readBool(); + case TYPE_STRING: + return input.readString(); + case TYPE_BYTES: + return input.readBytes(); + case TYPE_UINT32: + return input.readUInt32(); + case TYPE_ENUM: + return input.readEnum(); + case TYPE_SFIXED32: + return input.readSFixed32(); + case TYPE_SFIXED64: + return input.readSFixed64(); + case TYPE_SINT32: + return input.readSInt32(); + case TYPE_SINT64: + return input.readSInt64(); + default: + throw new IllegalArgumentException("Unknown type " + type); + } + } catch (IOException e) { + throw new IllegalArgumentException("Error reading extension field", e); + } + } + + @Override + protected void readDataInto(UnknownFieldData data, List resultList) { + // This implementation is for primitive typed extensions, + // which can read both packed and non-packed data. + if (data.tag == nonPackedTag) { + resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes))); + } else { + CodedInputByteBufferNano buffer = + CodedInputByteBufferNano.newInstance(data.bytes); + try { + buffer.pushLimit(buffer.readRawVarint32()); // length limit + } catch (IOException e) { + throw new IllegalArgumentException("Error reading extension field", e); + } + while (!buffer.isAtEnd()) { + resultList.add(readData(buffer)); + } + } + } + + @Override + protected final void writeSingularData(Object value, CodedOutputByteBufferNano output) { + try { + output.writeRawVarint32(tag); + switch (type) { + case TYPE_DOUBLE: + Double doubleValue = (Double) value; + output.writeDoubleNoTag(doubleValue); + break; + case TYPE_FLOAT: + Float floatValue = (Float) value; + output.writeFloatNoTag(floatValue); + break; + case TYPE_INT64: + Long int64Value = (Long) value; + output.writeInt64NoTag(int64Value); + break; + case TYPE_UINT64: + Long uint64Value = (Long) value; + output.writeUInt64NoTag(uint64Value); + break; + case TYPE_INT32: + Integer int32Value = (Integer) value; + output.writeInt32NoTag(int32Value); + break; + case TYPE_FIXED64: + Long fixed64Value = (Long) value; + output.writeFixed64NoTag(fixed64Value); + break; + case TYPE_FIXED32: + Integer fixed32Value = (Integer) value; + output.writeFixed32NoTag(fixed32Value); + break; + case TYPE_BOOL: + Boolean boolValue = (Boolean) value; + output.writeBoolNoTag(boolValue); + break; + case TYPE_STRING: + String stringValue = (String) value; + output.writeStringNoTag(stringValue); + break; + case TYPE_BYTES: + byte[] bytesValue = (byte[]) value; + output.writeBytesNoTag(bytesValue); + break; + case TYPE_UINT32: + Integer uint32Value = (Integer) value; + output.writeUInt32NoTag(uint32Value); + break; + case TYPE_ENUM: + Integer enumValue = (Integer) value; + output.writeEnumNoTag(enumValue); + break; + case TYPE_SFIXED32: + Integer sfixed32Value = (Integer) value; + output.writeSFixed32NoTag(sfixed32Value); + break; + case TYPE_SFIXED64: + Long sfixed64Value = (Long) value; + output.writeSFixed64NoTag(sfixed64Value); + break; + case TYPE_SINT32: + Integer sint32Value = (Integer) value; + output.writeSInt32NoTag(sint32Value); + break; + case TYPE_SINT64: + Long sint64Value = (Long) value; + output.writeSInt64NoTag(sint64Value); + break; + default: + throw new IllegalArgumentException("Unknown type " + type); + } + } catch (IOException e) { + // Should not happen + throw new IllegalStateException(e); + } + } + + @Override + protected void writeRepeatedData(Object array, CodedOutputByteBufferNano output) { + if (tag == nonPackedTag) { + // Use base implementation for non-packed data + super.writeRepeatedData(array, output); + } else if (tag == packedTag) { + // Packed. Note that the array element type is guaranteed to be primitive, so there + // won't be any null elements, so no null check in this block. + int arrayLength = Array.getLength(array); + int dataSize = computePackedDataSize(array); + + try { + output.writeRawVarint32(tag); + output.writeRawVarint32(dataSize); + switch (type) { + case TYPE_BOOL: + for (int i = 0; i < arrayLength; i++) { + output.writeBoolNoTag(Array.getBoolean(array, i)); + } + break; + case TYPE_FIXED32: + for (int i = 0; i < arrayLength; i++) { + output.writeFixed32NoTag(Array.getInt(array, i)); + } + break; + case TYPE_SFIXED32: + for (int i = 0; i < arrayLength; i++) { + output.writeSFixed32NoTag(Array.getInt(array, i)); + } + break; + case TYPE_FLOAT: + for (int i = 0; i < arrayLength; i++) { + output.writeFloatNoTag(Array.getFloat(array, i)); + } + break; + case TYPE_FIXED64: + for (int i = 0; i < arrayLength; i++) { + output.writeFixed64NoTag(Array.getLong(array, i)); + } + break; + case TYPE_SFIXED64: + for (int i = 0; i < arrayLength; i++) { + output.writeSFixed64NoTag(Array.getLong(array, i)); + } + break; + case TYPE_DOUBLE: + for (int i = 0; i < arrayLength; i++) { + output.writeDoubleNoTag(Array.getDouble(array, i)); + } + break; + case TYPE_INT32: + for (int i = 0; i < arrayLength; i++) { + output.writeInt32NoTag(Array.getInt(array, i)); + } + break; + case TYPE_SINT32: + for (int i = 0; i < arrayLength; i++) { + output.writeSInt32NoTag(Array.getInt(array, i)); + } + break; + case TYPE_UINT32: + for (int i = 0; i < arrayLength; i++) { + output.writeUInt32NoTag(Array.getInt(array, i)); + } + break; + case TYPE_INT64: + for (int i = 0; i < arrayLength; i++) { + output.writeInt64NoTag(Array.getLong(array, i)); + } + break; + case TYPE_SINT64: + for (int i = 0; i < arrayLength; i++) { + output.writeSInt64NoTag(Array.getLong(array, i)); + } + break; + case TYPE_UINT64: + for (int i = 0; i < arrayLength; i++) { + output.writeUInt64NoTag(Array.getLong(array, i)); + } + break; + case TYPE_ENUM: + for (int i = 0; i < arrayLength; i++) { + output.writeEnumNoTag(Array.getInt(array, i)); + } + break; + default: + throw new IllegalArgumentException("Unpackable type " + type); + } + } catch (IOException e) { + // Should not happen. + throw new IllegalStateException(e); + } + } else { + throw new IllegalArgumentException("Unexpected repeated extension tag " + tag + + ", unequal to both non-packed variant " + nonPackedTag + + " and packed variant " + packedTag); + } + } + + private int computePackedDataSize(Object array) { + int dataSize = 0; + int arrayLength = Array.getLength(array); + switch (type) { + case TYPE_BOOL: + // Bools are stored as int32 but just as 0 or 1, so 1 byte each. + dataSize = arrayLength; + break; + case TYPE_FIXED32: + case TYPE_SFIXED32: + case TYPE_FLOAT: + dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_32_SIZE; + break; + case TYPE_FIXED64: + case TYPE_SFIXED64: + case TYPE_DOUBLE: + dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_64_SIZE; + break; + case TYPE_INT32: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeInt32SizeNoTag( + Array.getInt(array, i)); + } + break; + case TYPE_SINT32: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeSInt32SizeNoTag( + Array.getInt(array, i)); + } + break; + case TYPE_UINT32: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeUInt32SizeNoTag( + Array.getInt(array, i)); + } + break; + case TYPE_INT64: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeInt64SizeNoTag( + Array.getLong(array, i)); + } + break; + case TYPE_SINT64: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeSInt64SizeNoTag( + Array.getLong(array, i)); + } + break; + case TYPE_UINT64: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeUInt64SizeNoTag( + Array.getLong(array, i)); + } + break; + case TYPE_ENUM: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeEnumSizeNoTag( + Array.getInt(array, i)); + } + break; + default: + throw new IllegalArgumentException("Unexpected non-packable type " + type); + } + return dataSize; + } + + @Override + protected int computeRepeatedSerializedSize(Object array) { + if (tag == nonPackedTag) { + // Use base implementation for non-packed data + return super.computeRepeatedSerializedSize(array); + } else if (tag == packedTag) { + // Packed. + int dataSize = computePackedDataSize(array); + int payloadSize = + dataSize + CodedOutputByteBufferNano.computeRawVarint32Size(dataSize); + return payloadSize + CodedOutputByteBufferNano.computeRawVarint32Size(tag); + } else { + throw new IllegalArgumentException("Unexpected repeated extension tag " + tag + + ", unequal to both non-packed variant " + nonPackedTag + + " and packed variant " + packedTag); + } + } + + @Override + protected final int computeSingularSerializedSize(Object value) { + int fieldNumber = WireFormatNano.getTagFieldNumber(tag); + switch (type) { + case TYPE_DOUBLE: + Double doubleValue = (Double) value; + return CodedOutputByteBufferNano.computeDoubleSize(fieldNumber, doubleValue); + case TYPE_FLOAT: + Float floatValue = (Float) value; + return CodedOutputByteBufferNano.computeFloatSize(fieldNumber, floatValue); + case TYPE_INT64: + Long int64Value = (Long) value; + return CodedOutputByteBufferNano.computeInt64Size(fieldNumber, int64Value); + case TYPE_UINT64: + Long uint64Value = (Long) value; + return CodedOutputByteBufferNano.computeUInt64Size(fieldNumber, uint64Value); + case TYPE_INT32: + Integer int32Value = (Integer) value; + return CodedOutputByteBufferNano.computeInt32Size(fieldNumber, int32Value); + case TYPE_FIXED64: + Long fixed64Value = (Long) value; + return CodedOutputByteBufferNano.computeFixed64Size(fieldNumber, fixed64Value); + case TYPE_FIXED32: + Integer fixed32Value = (Integer) value; + return CodedOutputByteBufferNano.computeFixed32Size(fieldNumber, fixed32Value); + case TYPE_BOOL: + Boolean boolValue = (Boolean) value; + return CodedOutputByteBufferNano.computeBoolSize(fieldNumber, boolValue); + case TYPE_STRING: + String stringValue = (String) value; + return CodedOutputByteBufferNano.computeStringSize(fieldNumber, stringValue); + case TYPE_BYTES: + byte[] bytesValue = (byte[]) value; + return CodedOutputByteBufferNano.computeBytesSize(fieldNumber, bytesValue); + case TYPE_UINT32: + Integer uint32Value = (Integer) value; + return CodedOutputByteBufferNano.computeUInt32Size(fieldNumber, uint32Value); + case TYPE_ENUM: + Integer enumValue = (Integer) value; + return CodedOutputByteBufferNano.computeEnumSize(fieldNumber, enumValue); + case TYPE_SFIXED32: + Integer sfixed32Value = (Integer) value; + return CodedOutputByteBufferNano.computeSFixed32Size(fieldNumber, + sfixed32Value); + case TYPE_SFIXED64: + Long sfixed64Value = (Long) value; + return CodedOutputByteBufferNano.computeSFixed64Size(fieldNumber, + sfixed64Value); + case TYPE_SINT32: + Integer sint32Value = (Integer) value; + return CodedOutputByteBufferNano.computeSInt32Size(fieldNumber, sint32Value); + case TYPE_SINT64: + Long sint64Value = (Long) value; + return CodedOutputByteBufferNano.computeSInt64Size(fieldNumber, sint64Value); + default: + throw new IllegalArgumentException("Unknown type " + type); + } + } + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/FieldArray.java b/javanano/src/main/java/com/google/protobuf/nano/FieldArray.java new file mode 100644 index 00000000..ab923a4d --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/FieldArray.java @@ -0,0 +1,273 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2014 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.nano; + + +/** + * A custom version of {@link android.util.SparseArray} with the minimal API + * for storing {@link FieldData} objects. + * + * Based on {@link android.support.v4.util.SpareArrayCompat}. + */ +class FieldArray { + private static final FieldData DELETED = new FieldData(); + private boolean mGarbage = false; + + private int[] mFieldNumbers; + private FieldData[] mData; + private int mSize; + + /** + * Creates a new FieldArray containing no fields. + */ + public FieldArray() { + this(10); + } + + /** + * Creates a new FieldArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. + */ + public FieldArray(int initialCapacity) { + initialCapacity = idealIntArraySize(initialCapacity); + mFieldNumbers = new int[initialCapacity]; + mData = new FieldData[initialCapacity]; + mSize = 0; + } + + /** + * Gets the FieldData mapped from the specified fieldNumber, or null + * if no such mapping has been made. + */ + public FieldData get(int fieldNumber) { + int i = binarySearch(fieldNumber); + + if (i < 0 || mData[i] == DELETED) { + return null; + } else { + return mData[i]; + } + } + + /** + * Removes the data from the specified fieldNumber, if there was any. + */ + public void remove(int fieldNumber) { + int i = binarySearch(fieldNumber); + + if (i >= 0 && mData[i] != DELETED) { + mData[i] = DELETED; + mGarbage = true; + } + } + + private void gc() { + int n = mSize; + int o = 0; + int[] keys = mFieldNumbers; + FieldData[] values = mData; + + for (int i = 0; i < n; i++) { + FieldData val = values[i]; + + if (val != DELETED) { + if (i != o) { + keys[o] = keys[i]; + values[o] = val; + values[i] = null; + } + + o++; + } + } + + mGarbage = false; + mSize = o; + } + + /** + * Adds a mapping from the specified fieldNumber to the specified data, + * replacing the previous mapping if there was one. + */ + public void put(int fieldNumber, FieldData data) { + int i = binarySearch(fieldNumber); + + if (i >= 0) { + mData[i] = data; + } else { + i = ~i; + + if (i < mSize && mData[i] == DELETED) { + mFieldNumbers[i] = fieldNumber; + mData[i] = data; + return; + } + + if (mGarbage && mSize >= mFieldNumbers.length) { + gc(); + + // Search again because indices may have changed. + i = ~ binarySearch(fieldNumber); + } + + if (mSize >= mFieldNumbers.length) { + int n = idealIntArraySize(mSize + 1); + + int[] nkeys = new int[n]; + FieldData[] nvalues = new FieldData[n]; + + System.arraycopy(mFieldNumbers, 0, nkeys, 0, mFieldNumbers.length); + System.arraycopy(mData, 0, nvalues, 0, mData.length); + + mFieldNumbers = nkeys; + mData = nvalues; + } + + if (mSize - i != 0) { + System.arraycopy(mFieldNumbers, i, mFieldNumbers, i + 1, mSize - i); + System.arraycopy(mData, i, mData, i + 1, mSize - i); + } + + mFieldNumbers[i] = fieldNumber; + mData[i] = data; + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this FieldArray + * currently stores. + */ + public int size() { + if (mGarbage) { + gc(); + } + + return mSize; + } + + public boolean isEmpty() { + return size() == 0; + } + + /** + * Given an index in the range 0...size()-1, returns + * the value from the indexth key-value mapping that this + * FieldArray stores. + */ + public FieldData dataAt(int index) { + if (mGarbage) { + gc(); + } + + return mData[index]; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof FieldArray)) { + return false; + } + + FieldArray other = (FieldArray) o; + if (size() != other.size()) { // size() will call gc() if necessary. + return false; + } + return arrayEquals(mFieldNumbers, other.mFieldNumbers, mSize) && + arrayEquals(mData, other.mData, mSize); + } + + @Override + public int hashCode() { + if (mGarbage) { + gc(); + } + int result = 17; + for (int i = 0; i < mSize; i++) { + result = 31 * result + mFieldNumbers[i]; + result = 31 * result + mData[i].hashCode(); + } + return result; + } + + private int idealIntArraySize(int need) { + return idealByteArraySize(need * 4) / 4; + } + + private int idealByteArraySize(int need) { + for (int i = 4; i < 32; i++) + if (need <= (1 << i) - 12) + return (1 << i) - 12; + + return need; + } + + private int binarySearch(int value) { + int lo = 0; + int hi = mSize - 1; + + while (lo <= hi) { + int mid = (lo + hi) >>> 1; + int midVal = mFieldNumbers[mid]; + + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } + + private boolean arrayEquals(int[] a, int[] b, int size) { + for (int i = 0; i < size; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + private boolean arrayEquals(FieldData[] a, FieldData[] b, int size) { + for (int i = 0; i < size; i++) { + if (!a[i].equals(b[i])) { + return false; + } + } + return true; + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/FieldData.java b/javanano/src/main/java/com/google/protobuf/nano/FieldData.java new file mode 100644 index 00000000..e5b69aad --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/FieldData.java @@ -0,0 +1,190 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2014 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.nano; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Stores unknown fields. These might be extensions or fields that the generated API doesn't + * know about yet. + */ +class FieldData { + private Extension cachedExtension; + private Object value; + /** The serialised values for this object. Will be cleared if getValue is called */ + private List unknownFieldData; + + FieldData(Extension extension, T newValue) { + cachedExtension = extension; + value = newValue; + } + + FieldData() { + unknownFieldData = new ArrayList(); + } + + void addUnknownField(UnknownFieldData unknownField) { + unknownFieldData.add(unknownField); + } + + UnknownFieldData getUnknownField(int index) { + if (unknownFieldData == null) { + return null; + } + if (index < unknownFieldData.size()) { + return unknownFieldData.get(index); + } + return null; + } + + int getUnknownFieldSize() { + if (unknownFieldData == null) { + return 0; + } + return unknownFieldData.size(); + } + + T getValue(Extension extension) { + if (value != null){ + if (cachedExtension != extension) { // Extension objects are singletons. + throw new IllegalStateException( + "Tried to getExtension with a differernt Extension."); + } + } else { + cachedExtension = extension; + value = extension.getValueFrom(unknownFieldData); + unknownFieldData = null; + } + return (T) value; + } + + void setValue(Extension extension, T newValue) { + cachedExtension = extension; + value = newValue; + unknownFieldData = null; + } + + int computeSerializedSize() { + int size = 0; + if (value != null) { + size = cachedExtension.computeSerializedSize(value); + } else { + for (UnknownFieldData unknownField : unknownFieldData) { + size += unknownField.computeSerializedSize(); + } + } + return size; + } + + void writeTo(CodedOutputByteBufferNano output) throws IOException { + if (value != null) { + cachedExtension.writeTo(value, output); + } else { + for (UnknownFieldData unknownField : unknownFieldData) { + unknownField.writeTo(output); + } + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof FieldData)) { + return false; + } + + FieldData other = (FieldData) o; + if (value != null && other.value != null) { + // If both objects have deserialized values, compare those. + // Since unknown fields are only compared if messages have generated equals methods + // we know this will be a meaningful comparison (not identity) for all values. + if (cachedExtension != other.cachedExtension) { // Extension objects are singletons. + return false; + } + if (!cachedExtension.clazz.isArray()) { + // Can't test (!cachedExtension.repeated) due to 'bytes' -> 'byte[]' + return value.equals(other.value); + } + if (value instanceof byte[]) { + return Arrays.equals((byte[]) value, (byte[]) other.value); + } else if (value instanceof int[]) { + return Arrays.equals((int[]) value, (int[]) other.value); + } else if (value instanceof long[]) { + return Arrays.equals((long[]) value, (long[]) other.value); + } else if (value instanceof float[]) { + return Arrays.equals((float[]) value, (float[]) other.value); + } else if (value instanceof double[]) { + return Arrays.equals((double[]) value, (double[]) other.value); + } else if (value instanceof boolean[]) { + return Arrays.equals((boolean[]) value, (boolean[]) other.value); + } else { + return Arrays.deepEquals((Object[]) value, (Object[]) other.value); + } + } + if (unknownFieldData != null && other.unknownFieldData != null) { + // If both objects have byte arrays compare those directly. + return unknownFieldData.equals(other.unknownFieldData); + } + try { + // As a last resort, serialize and compare the resulting byte arrays. + return Arrays.equals(toByteArray(), other.toByteArray()); + } catch (IOException e) { + // Should not happen. + throw new IllegalStateException(e); + } + } + + @Override + public int hashCode() { + int result = 17; + try { + // The only way to generate a consistent hash is to use the serialized form. + result = 31 * result + Arrays.hashCode(toByteArray()); + } catch (IOException e) { + // Should not happen. + throw new IllegalStateException(e); + } + return result; + } + + private byte[] toByteArray() throws IOException { + byte[] result = new byte[computeSerializedSize()]; + CodedOutputByteBufferNano output = CodedOutputByteBufferNano.newInstance(result); + writeTo(output); + return result; + } + +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java new file mode 100644 index 00000000..90ca11d5 --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java @@ -0,0 +1,333 @@ +// 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.nano; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +/** + * The classes contained within are used internally by the Protocol Buffer + * library and generated message implementations. They are public only because + * those generated messages do not reside in the {@code protobuf} package. + * Others should not use this class directly. + * + * @author kenton@google.com (Kenton Varda) + */ +public final class InternalNano { + + private InternalNano() {} + + /** + * An object to provide synchronization when lazily initializing static fields + * of {@link MessageNano} subclasses. + *

+ * To enable earlier versions of ProGuard to inline short methods from a + * generated MessageNano subclass to the call sites, that class must not have + * a class initializer, which will be created if there is any static variable + * initializers. To lazily initialize the static variables in a thread-safe + * manner, the initialization code will synchronize on this object. + */ + public static final Object LAZY_INIT_LOCK = new Object(); + + /** + * Helper called by generated code to construct default values for string + * fields. + *

+ * The protocol compiler does not actually contain a UTF-8 decoder -- it + * just pushes UTF-8-encoded text around without touching it. The one place + * where this presents a problem is when generating Java string literals. + * Unicode characters in the string literal would normally need to be encoded + * using a Unicode escape sequence, which would require decoding them. + * To get around this, protoc instead embeds the UTF-8 bytes into the + * generated code and leaves it to the runtime library to decode them. + *

+ * It gets worse, though. If protoc just generated a byte array, like: + * new byte[] {0x12, 0x34, 0x56, 0x78} + * Java actually generates *code* which allocates an array and then fills + * in each value. This is much less efficient than just embedding the bytes + * directly into the bytecode. To get around this, we need another + * work-around. String literals are embedded directly, so protoc actually + * generates a string literal corresponding to the bytes. The easiest way + * to do this is to use the ISO-8859-1 character set, which corresponds to + * the first 256 characters of the Unicode range. Protoc can then use + * good old CEscape to generate the string. + *

+ * So we have a string literal which represents a set of bytes which + * represents another string. This function -- stringDefaultValue -- + * converts from the generated string to the string we actually want. The + * generated code calls this automatically. + */ + public static String stringDefaultValue(String bytes) { + try { + return new String(bytes.getBytes("ISO-8859-1"), "UTF-8"); + } catch (UnsupportedEncodingException e) { + // This should never happen since all JVMs are required to implement + // both of the above character sets. + throw new IllegalStateException( + "Java VM does not support a standard character set.", e); + } + } + + /** + * Helper called by generated code to construct default values for bytes + * fields. + *

+ * This is a lot like {@link #stringDefaultValue}, but for bytes fields. + * In this case we only need the second of the two hacks -- allowing us to + * embed raw bytes as a string literal with ISO-8859-1 encoding. + */ + public static byte[] bytesDefaultValue(String bytes) { + try { + return bytes.getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + // This should never happen since all JVMs are required to implement + // ISO-8859-1. + throw new IllegalStateException( + "Java VM does not support a standard character set.", e); + } + } + + /** + * Helper function to convert a string into UTF-8 while turning the + * UnsupportedEncodingException to a RuntimeException. + */ + public static byte[] copyFromUtf8(final String text) { + try { + return text.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported?"); + } + } + + /** + * Checks repeated int field equality; null-value and 0-length fields are + * considered equal. + */ + public static boolean equals(int[] field1, int[] field2) { + if (field1 == null || field1.length == 0) { + return field2 == null || field2.length == 0; + } else { + return Arrays.equals(field1, field2); + } + } + + /** + * Checks repeated long field equality; null-value and 0-length fields are + * considered equal. + */ + public static boolean equals(long[] field1, long[] field2) { + if (field1 == null || field1.length == 0) { + return field2 == null || field2.length == 0; + } else { + return Arrays.equals(field1, field2); + } + } + + /** + * Checks repeated float field equality; null-value and 0-length fields are + * considered equal. + */ + public static boolean equals(float[] field1, float[] field2) { + if (field1 == null || field1.length == 0) { + return field2 == null || field2.length == 0; + } else { + return Arrays.equals(field1, field2); + } + } + + /** + * Checks repeated double field equality; null-value and 0-length fields are + * considered equal. + */ + public static boolean equals(double[] field1, double[] field2) { + if (field1 == null || field1.length == 0) { + return field2 == null || field2.length == 0; + } else { + return Arrays.equals(field1, field2); + } + } + + /** + * Checks repeated boolean field equality; null-value and 0-length fields are + * considered equal. + */ + public static boolean equals(boolean[] field1, boolean[] field2) { + if (field1 == null || field1.length == 0) { + return field2 == null || field2.length == 0; + } else { + return Arrays.equals(field1, field2); + } + } + + /** + * Checks repeated bytes field equality. Only non-null elements are tested. + * Returns true if the two fields have the same sequence of non-null + * elements. Null-value fields and fields of any length with only null + * elements are considered equal. + */ + public static boolean equals(byte[][] field1, byte[][] field2) { + int index1 = 0; + int length1 = field1 == null ? 0 : field1.length; + int index2 = 0; + int length2 = field2 == null ? 0 : field2.length; + while (true) { + while (index1 < length1 && field1[index1] == null) { + index1++; + } + while (index2 < length2 && field2[index2] == null) { + index2++; + } + boolean atEndOf1 = index1 >= length1; + boolean atEndOf2 = index2 >= length2; + if (atEndOf1 && atEndOf2) { + // no more non-null elements to test in both arrays + return true; + } else if (atEndOf1 != atEndOf2) { + // one of the arrays have extra non-null elements + return false; + } else if (!Arrays.equals(field1[index1], field2[index2])) { + // element mismatch + return false; + } + index1++; + index2++; + } + } + + /** + * Checks repeated string/message field equality. Only non-null elements are + * tested. Returns true if the two fields have the same sequence of non-null + * elements. Null-value fields and fields of any length with only null + * elements are considered equal. + */ + public static boolean equals(Object[] field1, Object[] field2) { + int index1 = 0; + int length1 = field1 == null ? 0 : field1.length; + int index2 = 0; + int length2 = field2 == null ? 0 : field2.length; + while (true) { + while (index1 < length1 && field1[index1] == null) { + index1++; + } + while (index2 < length2 && field2[index2] == null) { + index2++; + } + boolean atEndOf1 = index1 >= length1; + boolean atEndOf2 = index2 >= length2; + if (atEndOf1 && atEndOf2) { + // no more non-null elements to test in both arrays + return true; + } else if (atEndOf1 != atEndOf2) { + // one of the arrays have extra non-null elements + return false; + } else if (!field1[index1].equals(field2[index2])) { + // element mismatch + return false; + } + index1++; + index2++; + } + } + + /** + * Computes the hash code of a repeated int field. Null-value and 0-length + * fields have the same hash code. + */ + public static int hashCode(int[] field) { + return field == null || field.length == 0 ? 0 : Arrays.hashCode(field); + } + + /** + * Computes the hash code of a repeated long field. Null-value and 0-length + * fields have the same hash code. + */ + public static int hashCode(long[] field) { + return field == null || field.length == 0 ? 0 : Arrays.hashCode(field); + } + + /** + * Computes the hash code of a repeated float field. Null-value and 0-length + * fields have the same hash code. + */ + public static int hashCode(float[] field) { + return field == null || field.length == 0 ? 0 : Arrays.hashCode(field); + } + + /** + * Computes the hash code of a repeated double field. Null-value and 0-length + * fields have the same hash code. + */ + public static int hashCode(double[] field) { + return field == null || field.length == 0 ? 0 : Arrays.hashCode(field); + } + + /** + * Computes the hash code of a repeated boolean field. Null-value and 0-length + * fields have the same hash code. + */ + public static int hashCode(boolean[] field) { + return field == null || field.length == 0 ? 0 : Arrays.hashCode(field); + } + + /** + * Computes the hash code of a repeated bytes field. Only the sequence of all + * non-null elements are used in the computation. Null-value fields and fields + * of any length with only null elements have the same hash code. + */ + public static int hashCode(byte[][] field) { + int result = 0; + for (int i = 0, size = field == null ? 0 : field.length; i < size; i++) { + byte[] element = field[i]; + if (element != null) { + result = 31 * result + Arrays.hashCode(element); + } + } + return result; + } + + /** + * Computes the hash code of a repeated string/message field. Only the + * sequence of all non-null elements are used in the computation. Null-value + * fields and fields of any length with only null elements have the same hash + * code. + */ + public static int hashCode(Object[] field) { + int result = 0; + for (int i = 0, size = field == null ? 0 : field.length; i < size; i++) { + Object element = field[i]; + if (element != null) { + result = 31 * result + element.hashCode(); + } + } + return result; + } + +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/InvalidProtocolBufferNanoException.java b/javanano/src/main/java/com/google/protobuf/nano/InvalidProtocolBufferNanoException.java new file mode 100644 index 00000000..ff0af9df --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/InvalidProtocolBufferNanoException.java @@ -0,0 +1,93 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.io.IOException; + +/** + * Thrown when a protocol message being parsed is invalid in some way, + * e.g. it contains a malformed varint or a negative byte length. + * + * @author kenton@google.com Kenton Varda + */ +public class InvalidProtocolBufferNanoException extends IOException { + private static final long serialVersionUID = -1616151763072450476L; + + public InvalidProtocolBufferNanoException(final String description) { + super(description); + } + + static InvalidProtocolBufferNanoException truncatedMessage() { + return new InvalidProtocolBufferNanoException( + "While parsing a protocol message, the input ended unexpectedly " + + "in the middle of a field. This could mean either than the " + + "input has been truncated or that an embedded message " + + "misreported its own length."); + } + + static InvalidProtocolBufferNanoException negativeSize() { + return new InvalidProtocolBufferNanoException( + "CodedInputStream encountered an embedded string or message " + + "which claimed to have negative size."); + } + + static InvalidProtocolBufferNanoException malformedVarint() { + return new InvalidProtocolBufferNanoException( + "CodedInputStream encountered a malformed varint."); + } + + static InvalidProtocolBufferNanoException invalidTag() { + return new InvalidProtocolBufferNanoException( + "Protocol message contained an invalid tag (zero)."); + } + + static InvalidProtocolBufferNanoException invalidEndTag() { + return new InvalidProtocolBufferNanoException( + "Protocol message end-group tag did not match expected tag."); + } + + static InvalidProtocolBufferNanoException invalidWireType() { + return new InvalidProtocolBufferNanoException( + "Protocol message tag had invalid wire type."); + } + + static InvalidProtocolBufferNanoException recursionLimitExceeded() { + return new InvalidProtocolBufferNanoException( + "Protocol message had too many levels of nesting. May be malicious. " + + "Use CodedInputStream.setRecursionLimit() to increase the depth limit."); + } + + static InvalidProtocolBufferNanoException sizeLimitExceeded() { + return new InvalidProtocolBufferNanoException( + "Protocol message was too large. May be malicious. " + + "Use CodedInputStream.setSizeLimit() to increase the size limit."); + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/MessageNano.java b/javanano/src/main/java/com/google/protobuf/nano/MessageNano.java new file mode 100644 index 00000000..164f317f --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/MessageNano.java @@ -0,0 +1,190 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Abstract interface implemented by Protocol Message objects. + * + * @author wink@google.com Wink Saville + */ +public abstract class MessageNano { + protected volatile int cachedSize = -1; + + /** + * Get the number of bytes required to encode this message. + * Returns the cached size or calls getSerializedSize which + * sets the cached size. This is used internally when serializing + * so the size is only computed once. If a member is modified + * then this could be stale call getSerializedSize if in doubt. + */ + public int getCachedSize() { + if (cachedSize < 0) { + // getSerializedSize sets cachedSize + getSerializedSize(); + } + return cachedSize; + } + + /** + * Computes the number of bytes required to encode this message. + * The size is cached and the cached result can be retrieved + * using getCachedSize(). + */ + public int getSerializedSize() { + int size = computeSerializedSize(); + cachedSize = size; + return size; + } + + /** + * Computes the number of bytes required to encode this message. This does not update the + * cached size. + */ + protected int computeSerializedSize() { + // This is overridden if the generated message has serialized fields. + return 0; + } + + /** + * Serializes the message and writes it to {@code output}. + * + * @param output the output to receive the serialized form. + * @throws IOException if an error occurred writing to {@code output}. + */ + public void writeTo(CodedOutputByteBufferNano output) throws IOException { + // Does nothing by default. Overridden by subclasses which have data to write. + } + + /** + * Parse {@code input} as a message of this type and merge it with the + * message being built. + */ + public abstract MessageNano mergeFrom(CodedInputByteBufferNano input) throws IOException; + + /** + * Serialize to a byte array. + * @return byte array with the serialized data. + */ + public static final byte[] toByteArray(MessageNano msg) { + final byte[] result = new byte[msg.getSerializedSize()]; + toByteArray(msg, result, 0, result.length); + return result; + } + + /** + * Serialize to a byte array starting at offset through length. The + * method getSerializedSize must have been called prior to calling + * this method so the proper length is know. If an attempt to + * write more than length bytes OutOfSpaceException will be thrown + * and if length bytes are not written then IllegalStateException + * is thrown. + */ + public static final void toByteArray(MessageNano msg, byte[] data, int offset, int length) { + try { + final CodedOutputByteBufferNano output = + CodedOutputByteBufferNano.newInstance(data, offset, length); + msg.writeTo(output); + output.checkNoSpaceLeft(); + } catch (IOException e) { + throw new RuntimeException("Serializing to a byte array threw an IOException " + + "(should never happen).", e); + } + } + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. + */ + public static final T mergeFrom(T msg, final byte[] data) + throws InvalidProtocolBufferNanoException { + return mergeFrom(msg, data, 0, data.length); + } + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. + */ + public static final T mergeFrom(T msg, final byte[] data, + final int off, final int len) throws InvalidProtocolBufferNanoException { + try { + final CodedInputByteBufferNano input = + CodedInputByteBufferNano.newInstance(data, off, len); + msg.mergeFrom(input); + input.checkLastTagWas(0); + return msg; + } catch (InvalidProtocolBufferNanoException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException("Reading from a byte array threw an IOException (should " + + "never happen)."); + } + } + + /** + * Compares two {@code MessageNano}s and returns true if the message's are the same class and + * have serialized form equality (i.e. all of the field values are the same). + */ + public static final boolean messageNanoEquals(MessageNano a, MessageNano b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + if (a.getClass() != b.getClass()) { + return false; + } + final int serializedSize = a.getSerializedSize(); + if (b.getSerializedSize() != serializedSize) { + return false; + } + final byte[] aByteArray = new byte[serializedSize]; + final byte[] bByteArray = new byte[serializedSize]; + toByteArray(a, aByteArray, 0, serializedSize); + toByteArray(b, bByteArray, 0, serializedSize); + return Arrays.equals(aByteArray, bByteArray); + } + + /** + * Returns a string that is (mostly) compatible with ProtoBuffer's TextFormat. Note that groups + * (which are deprecated) are not serialized with the correct field name. + * + *

This is implemented using reflection, so it is not especially fast nor is it guaranteed + * to find all fields if you have method removal turned on for proguard. + */ + @Override + public String toString() { + return MessageNanoPrinter.print(this); + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java b/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java new file mode 100644 index 00000000..4cca3d5e --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java @@ -0,0 +1,257 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Static helper methods for printing nano protos. + * + * @author flynn@google.com Andrew Flynn + */ +public final class MessageNanoPrinter { + // Do not allow instantiation + private MessageNanoPrinter() {} + + private static final String INDENT = " "; + private static final int MAX_STRING_LEN = 200; + + /** + * Returns an text representation of a MessageNano suitable for debugging. The returned string + * is mostly compatible with Protocol Buffer's TextFormat (as provided by non-nano protocol + * buffers) -- groups (which are deprecated) are output with an underscore name (e.g. foo_bar + * instead of FooBar) and will thus not parse. + * + *

Employs Java reflection on the given object and recursively prints primitive fields, + * groups, and messages.

+ */ + public static String print(T message) { + if (message == null) { + return ""; + } + + StringBuffer buf = new StringBuffer(); + try { + print(null, message, new StringBuffer(), buf); + } catch (IllegalAccessException e) { + return "Error printing proto: " + e.getMessage(); + } catch (InvocationTargetException e) { + return "Error printing proto: " + e.getMessage(); + } + return buf.toString(); + } + + /** + * Function that will print the given message/field into the StringBuffer. + * Meant to be called recursively. + * + * @param identifier the identifier to use, or {@code null} if this is the root message to + * print. + * @param object the value to print. May in fact be a primitive value or byte array and not a + * message. + * @param indentBuf the indentation each line should begin with. + * @param buf the output buffer. + */ + private static void print(String identifier, Object object, + StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException, + InvocationTargetException { + if (object == null) { + // This can happen if... + // - we're about to print a message, String, or byte[], but it not present; + // - we're about to print a primitive, but "reftype" optional style is enabled, and + // the field is unset. + // In both cases the appropriate behavior is to output nothing. + } else if (object instanceof MessageNano) { // Nano proto message + int origIndentBufLength = indentBuf.length(); + if (identifier != null) { + buf.append(indentBuf).append(deCamelCaseify(identifier)).append(" <\n"); + indentBuf.append(INDENT); + } + Class clazz = object.getClass(); + + // Proto fields follow one of two formats: + // + // 1) Public, non-static variables that do not begin or end with '_' + // Find and print these using declared public fields + for (Field field : clazz.getFields()) { + int modifiers = field.getModifiers(); + String fieldName = field.getName(); + + if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC + && (modifiers & Modifier.STATIC) != Modifier.STATIC + && !fieldName.startsWith("_") + && !fieldName.endsWith("_")) { + Class fieldType = field.getType(); + Object value = field.get(object); + + if (fieldType.isArray()) { + Class arrayType = fieldType.getComponentType(); + + // bytes is special since it's not repeated, but is represented by an array + if (arrayType == byte.class) { + print(fieldName, value, indentBuf, buf); + } else { + int len = value == null ? 0 : Array.getLength(value); + for (int i = 0; i < len; i++) { + Object elem = Array.get(value, i); + print(fieldName, elem, indentBuf, buf); + } + } + } else { + print(fieldName, value, indentBuf, buf); + } + } + } + + // 2) Fields that are accessed via getter methods (when accessors + // mode is turned on) + // Find and print these using getter methods. + for (Method method : clazz.getMethods()) { + String name = method.getName(); + // Check for the setter accessor method since getters and hazzers both have + // non-proto-field name collisions (hashCode() and getSerializedSize()) + if (name.startsWith("set")) { + String subfieldName = name.substring(3); + + Method hazzer = null; + try { + hazzer = clazz.getMethod("has" + subfieldName); + } catch (NoSuchMethodException e) { + continue; + } + // If hazzer does't exist or returns false, no need to continue + if (!(Boolean) hazzer.invoke(object)) { + continue; + } + + Method getter = null; + try { + getter = clazz.getMethod("get" + subfieldName); + } catch (NoSuchMethodException e) { + continue; + } + + print(subfieldName, getter.invoke(object), indentBuf, buf); + } + } + if (identifier != null) { + indentBuf.setLength(origIndentBufLength); + buf.append(indentBuf).append(">\n"); + } + } else { + // Non-null primitive value + identifier = deCamelCaseify(identifier); + buf.append(indentBuf).append(identifier).append(": "); + if (object instanceof String) { + String stringMessage = sanitizeString((String) object); + buf.append("\"").append(stringMessage).append("\""); + } else if (object instanceof byte[]) { + appendQuotedBytes((byte[]) object, buf); + } else { + buf.append(object); + } + buf.append("\n"); + } + } + + /** + * Converts an identifier of the format "FieldName" into "field_name". + */ + private static String deCamelCaseify(String identifier) { + StringBuffer out = new StringBuffer(); + for (int i = 0; i < identifier.length(); i++) { + char currentChar = identifier.charAt(i); + if (i == 0) { + out.append(Character.toLowerCase(currentChar)); + } else if (Character.isUpperCase(currentChar)) { + out.append('_').append(Character.toLowerCase(currentChar)); + } else { + out.append(currentChar); + } + } + return out.toString(); + } + + /** + * Shortens and escapes the given string. + */ + private static String sanitizeString(String str) { + if (!str.startsWith("http") && str.length() > MAX_STRING_LEN) { + // Trim non-URL strings. + str = str.substring(0, MAX_STRING_LEN) + "[...]"; + } + return escapeString(str); + } + + /** + * Escape everything except for low ASCII code points. + */ + private static String escapeString(String str) { + int strLen = str.length(); + StringBuilder b = new StringBuilder(strLen); + for (int i = 0; i < strLen; i++) { + char original = str.charAt(i); + if (original >= ' ' && original <= '~' && original != '"' && original != '\'') { + b.append(original); + } else { + b.append(String.format("\\u%04x", (int) original)); + } + } + return b.toString(); + } + + /** + * Appends a quoted byte array to the provided {@code StringBuffer}. + */ + private static void appendQuotedBytes(byte[] bytes, StringBuffer builder) { + if (bytes == null) { + builder.append("\"\""); + return; + } + + builder.append('"'); + for (int i = 0; i < bytes.length; ++i) { + int ch = bytes[i] & 0xff; + if (ch == '\\' || ch == '"') { + builder.append('\\').append((char) ch); + } else if (ch >= 32 && ch < 127) { + builder.append((char) ch); + } else { + builder.append(String.format("\\%03o", ch)); + } + } + builder.append('"'); + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java b/javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java new file mode 100644 index 00000000..2032e1a6 --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java @@ -0,0 +1,84 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Stores unknown fields. These might be extensions or fields that the generated + * API doesn't know about yet. + * + * @author bduff@google.com (Brian Duff) + */ +final class UnknownFieldData { + + final int tag; + final byte[] bytes; + + UnknownFieldData(int tag, byte[] bytes) { + this.tag = tag; + this.bytes = bytes; + } + + int computeSerializedSize() { + int size = 0; + size += CodedOutputByteBufferNano.computeRawVarint32Size(tag); + size += bytes.length; + return size; + } + + void writeTo(CodedOutputByteBufferNano output) throws IOException { + output.writeRawVarint32(tag); + output.writeRawBytes(bytes); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof UnknownFieldData)) { + return false; + } + + UnknownFieldData other = (UnknownFieldData) o; + return tag == other.tag && Arrays.equals(bytes, other.bytes); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + tag; + result = 31 * result + Arrays.hashCode(bytes); + return result; + } +} diff --git a/javanano/src/main/java/com/google/protobuf/nano/WireFormatNano.java b/javanano/src/main/java/com/google/protobuf/nano/WireFormatNano.java new file mode 100644 index 00000000..a3405e55 --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/WireFormatNano.java @@ -0,0 +1,124 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 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.nano; + +import java.io.IOException; + +/** + * This class is used internally by the Protocol Buffer library and generated + * message implementations. It is public only because those generated messages + * do not reside in the {@code protobuf} package. Others should not use this + * class directly. + * + * This class contains constants and helper functions useful for dealing with + * the Protocol Buffer wire format. + * + * @author kenton@google.com Kenton Varda + */ +public final class WireFormatNano { + // Do not allow instantiation. + private WireFormatNano() {} + + static final int WIRETYPE_VARINT = 0; + static final int WIRETYPE_FIXED64 = 1; + static final int WIRETYPE_LENGTH_DELIMITED = 2; + static final int WIRETYPE_START_GROUP = 3; + static final int WIRETYPE_END_GROUP = 4; + static final int WIRETYPE_FIXED32 = 5; + + static final int TAG_TYPE_BITS = 3; + static final int TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1; + + /** Given a tag value, determines the wire type (the lower 3 bits). */ + static int getTagWireType(final int tag) { + return tag & TAG_TYPE_MASK; + } + + /** Given a tag value, determines the field number (the upper 29 bits). */ + public static int getTagFieldNumber(final int tag) { + return tag >>> TAG_TYPE_BITS; + } + + /** Makes a tag value given a field number and wire type. */ + static int makeTag(final int fieldNumber, final int wireType) { + return (fieldNumber << TAG_TYPE_BITS) | wireType; + } + + public static final int EMPTY_INT_ARRAY[] = {}; + public static final long EMPTY_LONG_ARRAY[] = {}; + public static final float EMPTY_FLOAT_ARRAY[] = {}; + public static final double EMPTY_DOUBLE_ARRAY[] = {}; + public static final boolean EMPTY_BOOLEAN_ARRAY[] = {}; + public static final String EMPTY_STRING_ARRAY[] = {}; + public static final byte[] EMPTY_BYTES_ARRAY[] = {}; + public static final byte[] EMPTY_BYTES = {}; + + /** + * Parses an unknown field. This implementation skips the field. + * + *

Generated messages will call this for unknown fields if the store_unknown_fields + * option is off. + * + * @return {@literal true} unless the tag is an end-group tag. + */ + public static boolean parseUnknownField( + final CodedInputByteBufferNano input, + final int tag) throws IOException { + return input.skipField(tag); + } + + /** + * Computes the array length of a repeated field. We assume that in the common case repeated + * fields are contiguously serialized but we still correctly handle interspersed values of a + * repeated field (but with extra allocations). + * + * Rewinds to current input position before returning. + * + * @param input stream input, pointing to the byte after the first tag + * @param tag repeated field tag just read + * @return length of array + * @throws IOException + */ + public static final int getRepeatedFieldArrayLength( + final CodedInputByteBufferNano input, + final int tag) throws IOException { + int arrayLength = 1; + int startPos = input.getPosition(); + input.skipField(tag); + while (input.readTag() == tag) { + input.skipField(tag); + arrayLength++; + } + input.rewindToPosition(startPos); + return arrayLength; + } + +} -- cgit v1.2.3