auto import from //depot/cupcake/@135843

parent a3cc5f17
// Copyright 2007 The Android Open Source Project
// All Rights Reserved.
package com.google.common.io.protocol;
import java.io.*;
import java.util.*;
/**
* Protocol buffer message object. Currently, it is assumed that tags ids are
* not large. This could be improved by storing a start offset, reducing the
* assumption to a dense number space.
* <p>
* ProtoBuf instances may or may not reference a ProtoBufType instance,
* representing information from a corresponding .proto file, which defines tag
* data types. The type can only be set in the constructor, it cannot be
* changed later.
* <p>
* If the type is null, the ProtoBuffer should be used only for reading or
* as a local persistent storage buffer. An untyped Protocol Buffer must never
* be sent to a server.
* <p>
* If a ProtoBufType is set, unknown values are read from the stream and
* preserved, but it is not possible to add values for undefined tags using
* this API. Attempts to set undefined tags will result in an exception.
* <p>
* This class provides two different sets of access methods for simple and
* repeated tags. Simple access methods are has(tag), getXXX(tag),
* and setXXX(tag, value). Access methods for repeated tags are getCount(tag),
* getXXX(tag, index), remove(tag, index), insert(tag, index, value) and
* addXXX(tag, value). Note that both sets of methods can be used in both cases,
* but only the simple methods take default values into account. The reason for
* this behavior is that default values cannot be removed -- they would reappear
* after a serialization cycle. If a tag has repeated values, setXXX(tag, value)
* will overwrite all of them and getXXX(tag) will throw an exception.
*
*/
public class ProtoBuf {
public static final Boolean FALSE = new Boolean(false);
public static final Boolean TRUE = new Boolean(true);
private static final String MSG_EOF = "Unexp.EOF";
private static final String MSG_MISMATCH = "Type mismatch";
private static final String MSG_UNSUPPORTED = "Unsupp.Type";
// names copied from //net/proto2/internal/wire_format.cc
static final int WIRETYPE_END_GROUP = 4;
static final int WIRETYPE_FIXED32 = 5;
static final int WIRETYPE_FIXED64 = 1;
static final int WIRETYPE_LENGTH_DELIMITED = 2;
static final int WIRETYPE_START_GROUP = 3;
static final int WIRETYPE_VARINT = 0;
/** Maximum number of bytes for VARINT wire format (64 bit, 7 bit/byte) */
private static final int VARINT_MAX_BYTES = 10;
private static Long[] SMALL_NUMBERS = {
new Long(0), new Long(1), new Long(2), new Long(3), new Long(4),
new Long(5), new Long(6), new Long(7), new Long(8), new Long(9),
new Long(10), new Long(11), new Long(12), new Long(13), new Long(14),
new Long(15)};
private ProtoBufType msgType;
private final Vector values = new Vector();
/**
* Wire types picked up on the wire or implied by setters (if no other
* type information is available.
*/
private final StringBuffer wireTypes = new StringBuffer();
/**
* Creates a protocol message according to the given description. The
* description is required if it is necessary to write the protocol buffer for
* data exchange with other systems relying on the .proto file.
*/
public ProtoBuf(ProtoBufType type) {
this.msgType = type;
}
/**
* Clears all data stored in this ProtoBuf.
*/
public void clear() {
values.setSize(0);
wireTypes.setLength(0);
}
/**
* Creates a new instance of the group with the given tag.
*/
public ProtoBuf createGroup(int tag) {
return new ProtoBuf((ProtoBufType) getType().getData(tag));
}
/**
* Appends the given (repeated) tag with the given boolean value.
*/
public void addBool(int tag, boolean value){
insertBool(tag, getCount(tag), value);
}
/**
* Appends the given (repeated) tag with the given byte[] value.
*/
public void addBytes(int tag, byte[] value){
insertBytes(tag, getCount(tag), value);
}
/**
* Appends the given (repeated) tag with the given int value.
*/
public void addInt(int tag, int value){
insertInt(tag, getCount(tag), value);
}
/**
* Appends the given (repeated) tag with the given long value.
*/
public void addLong(int tag, long value){
insertLong(tag, getCount(tag), value);
}
/**
* Appends the given (repeated) tag with the given float value.
*/
public void addFloat(int tag, float value) {
insertFloat(tag, getCount(tag), value);
}
/**
* Appends the given (repeated) tag with the given double value.
*/
public void addDouble(int tag, double value) {
insertDouble(tag, getCount(tag), value);
}
/**
* Appends the given (repeated) tag with the given group or message value.
*/
public void addProtoBuf(int tag, ProtoBuf value){
insertProtoBuf(tag, getCount(tag), value);
}
/**
* Appends the given (repeated) tag with the given String value.
*/
public void addString(int tag, String value){
insertString(tag, getCount(tag), value);
}
/**
* Returns the boolean value for the given tag.
*/
public boolean getBool(int tag) {
return ((Boolean) getObject(tag, ProtoBufType.TYPE_BOOL))
.booleanValue();
}
/**
* Returns the boolean value for the given repeated tag at the given index.
*/
public boolean getBool(int tag, int index) {
return ((Boolean) getObject(tag, index, ProtoBufType.TYPE_BOOL))
.booleanValue();
}
/**
* Returns the given string tag as byte array.
*/
public byte[] getBytes(int tag) {
return (byte[]) getObject(tag, ProtoBufType.TYPE_DATA);
}
/**
* Returns the given repeated string tag at the given index as byte array.
*/
public byte[] getBytes(int tag, int index) {
return (byte[]) getObject(tag, index, ProtoBufType.TYPE_DATA);
}
/**
* Returns the integer value for the given tag.
*/
public int getInt(int tag) {
return (int) ((Long) getObject(tag, ProtoBufType.TYPE_INT32)).longValue();
}
/**
* Returns the integer value for the given repeated tag at the given index.
*/
public int getInt(int tag, int index) {
return (int) ((Long) getObject(tag, index,
ProtoBufType.TYPE_INT32)).longValue();
}
/**
* Returns the long value for the given tag.
*/
public long getLong(int tag) {
return ((Long) getObject(tag, ProtoBufType.TYPE_INT64)).longValue();
}
/**
* Returns the long value for the given repeated tag at the given index.
*/
public long getLong(int tag, int index) {
return ((Long) getObject(tag, index, ProtoBufType.TYPE_INT64)).longValue();
}
/**
* Returns the float value for the given tag.
*/
public float getFloat(int tag) {
return Float.intBitsToFloat(getInt(tag));
}
/**
* Returns the float value for the given repeated tag at the given index.
*/
public float getFloat(int tag, int index) {
return Float.intBitsToFloat(getInt(tag, index));
}
/**
* Returns the double value for the given tag.
*/
public double getDouble(int tag) {
return Double.longBitsToDouble(getLong(tag));
}
/**
* Returns the double value for the given repeated tag at the given index.
*/
public double getDouble(int tag, int index) {
return Double.longBitsToDouble(getLong(tag, index));
}
/**
* Returns the group or nested message for the given tag.
*/
public ProtoBuf getProtoBuf(int tag) {
return (ProtoBuf) getObject(tag, ProtoBufType.TYPE_GROUP);
}
/**
* Returns the group or nested message for the given repeated tag at the given
* index.
*/
public ProtoBuf getProtoBuf(int tag, int index) {
return (ProtoBuf) getObject(tag, index, ProtoBufType.TYPE_GROUP);
}
/**
* Returns the string value for a given tag converted to a Java String
* assuming UTF-8 encoding.
*/
public String getString(int tag) {
return (String) getObject(tag, ProtoBufType.TYPE_TEXT);
}
/**
* Returns the string value for a given repeated tag at the given index
* converted to a Java String assuming UTF-8 encoding.
*/
public String getString(int tag, int index) {
return (String) getObject(tag, index, ProtoBufType.TYPE_TEXT);
}
/**
* Returns the type definition of this protocol buffer or group -- if set.
*/
public ProtoBufType getType() {
return msgType;
}
/**
* Sets the type definition of this protocol buffer. Used internally in
* ProtoBufUtil for incremental reading.
*
* @param type the new type
*/
void setType(ProtoBufType type) {
if (values.size() != 0 ||
(msgType != null && type != null && type != msgType)) {
throw new IllegalArgumentException();
}
this.msgType = type;
}
/**
* Convenience method for determining whether a tag has a value. Note: in
* contrast to getCount(tag) &gt; 0, this method takes the default value
* into account.
*/
public boolean has(int tag){
return getCount(tag) > 0 || getDefault(tag) != null;
}
/**
* Reads the contents of this ProtocolMessage from the given byte array.
* Currently, this is a shortcut for parse(new ByteArrayInputStream(data)).
* However, this may change in future versions for efficiency reasons.
*
* @param data the byte array the ProtocolMessage is read from
* @throws IOException if an unexpected "End of file" is encountered in
* the byte array
*/
public ProtoBuf parse(byte[] data) throws IOException {
parse(new ByteArrayInputStream(data), data.length);
return this;
}
/**
* Reads the contents of this ProtocolMessage from the given stream.
*
* @param is the input stream providing the contents
* @return this
* @throws IOException raised if an IO exception occurs in the underlying
* stream or the end of the stream is reached at an unexpected
* position
*/
public ProtoBuf parse(InputStream is) throws IOException {
parse(is, Integer.MAX_VALUE);
return this;
}
/**
* Reads the contents of this ProtocolMessage from the given stream, consuming
* at most the given number of bytes.
*
* @param is the input stream providing the contents
* @param available maximum number of bytes to read
* @return this
* @throws IOException raised if an IO exception occurs in the
* underlying stream or the end of the stream is reached at
* an unexpected position
*/
public int parse(InputStream is, int available) throws IOException {
clear();
while (available > 0) {
long tagAndType = readVarInt(is, true /* permits EOF */);
if (tagAndType == -1){
break;
}
available -= getVarIntSize(tagAndType);
int wireType = ((int) tagAndType) & 0x07;
if (wireType == WIRETYPE_END_GROUP) {
break;
}
int tag = (int) (tagAndType >>> 3);
while (wireTypes.length() <= tag){
wireTypes.append((char) ProtoBufType.TYPE_UNDEFINED);
}
wireTypes.setCharAt(tag, (char) wireType);
// first step: decode tag value
Object value;
switch (wireType) {
case WIRETYPE_VARINT:
long v = readVarInt(is, false);
available -= getVarIntSize(v);
if (isZigZagEncodedType(tag)) {
v = zigZagDecode(v);
}
value = (v >= 0 && v < SMALL_NUMBERS.length) ?
SMALL_NUMBERS[(int) v] : new Long(v);
break;
// also used for fixed values
case WIRETYPE_FIXED32:
case WIRETYPE_FIXED64:
v = 0;
int shift = 0;
int count = (wireType == WIRETYPE_FIXED32) ? 4 : 8;
available -= count;
while (count-- > 0) {
long l = is.read();
v |= l << shift;
shift += 8;
}
value = (v >= 0 && v < SMALL_NUMBERS.length)
? SMALL_NUMBERS[(int) v]
: new Long(v);
break;
case WIRETYPE_LENGTH_DELIMITED:
int total = (int) readVarInt(is, false);
available -= getVarIntSize(total);
available -= total;
if (getType(tag) == ProtoBufType.TYPE_MESSAGE) {
ProtoBuf msg = new ProtoBuf((ProtoBufType) msgType.getData(tag));
msg.parse(is, total);
value = msg;
} else {
byte[] data = new byte[total];
int pos = 0;
while (pos < total) {
count = is.read(data, pos, total - pos);
if (count <= 0) {
throw new IOException(MSG_EOF);
}
pos += count;
}
value = data;
}
break;
case WIRETYPE_START_GROUP:
ProtoBuf group = new ProtoBuf(msgType == null
? null
: ((ProtoBufType) msgType.getData(tag)));
available = group.parse(is, available);
value = group;
break;
default:
throw new RuntimeException(MSG_UNSUPPORTED + wireType);
}
insertObject(tag, getCount(tag), value);
}
if (available < 0){
throw new IOException();
}
return available;
}
/**
* Removes the tag value at the given index.
*/
public void remove(int tag, int index){
int count = getCount(tag);
if (index >= count){
throw new ArrayIndexOutOfBoundsException();
}
if (count == 1){
values.setElementAt(null, tag);
} else {
Vector v = (Vector) values.elementAt(tag);
v.removeElementAt(index);
}
}
/**
* Returns the number of repeated and optional (0..1) values for a given tag.
* Note: Default values are not counted (and in general not considered in
* access methods for repeated tags), but considered for has(tag).
*/
public int getCount(int tag) {
if (tag >= values.size()){
return 0;
}
Object o = values.elementAt(tag);
if (o == null){
return 0;
}
return (o instanceof Vector) ? ((Vector) o).size() : 1;
}
/**
* Returns the tag type of the given tag (one of the ProtoBufType.TYPE_XXX
* constants). If no ProtoBufType is set, the wire type is returned. If no
* wire type is available, the wire type is determined by looking at the
* tag value (making sure the wire type is consistent for all values). If
* no value is set, TYPE_UNDEFINED is returned.
*/
public int getType(int tag){
int tagType = ProtoBufType.TYPE_UNDEFINED;
if (msgType != null){
tagType = msgType.getType(tag);
}
if (tagType == ProtoBufType.TYPE_UNDEFINED && tag < wireTypes.length()) {
tagType = wireTypes.charAt(tag);
}
if (tagType == ProtoBufType.TYPE_UNDEFINED && getCount(tag) > 0) {
Object o = getObject(tag, 0, ProtoBufType.TYPE_UNDEFINED);
tagType = (o instanceof Long) || (o instanceof Boolean)
? WIRETYPE_VARINT : WIRETYPE_LENGTH_DELIMITED;
}
return tagType;
}
/**
* Returns the number of bytes needed to store this protocol buffer
*/
public int getDataSize() {
int size = 0;
for (int tag = 0; tag <= maxTag(); tag++) {
for (int i = 0; i < getCount(tag); i++) {
size += getDataSize(tag, i);
}
}
return size;
}
/**
* Returns the size of the given value
*/
private int getDataSize(int tag, int i) {
int tagSize = getVarIntSize(tag << 3);
switch(getWireType(tag)){
case WIRETYPE_FIXED32:
return tagSize + 4;
case WIRETYPE_FIXED64:
return tagSize + 8;
case WIRETYPE_VARINT:
long value = getLong(tag, i);
if (isZigZagEncodedType(tag)) {
value = zigZagEncode(value);
}
return tagSize + getVarIntSize(value);
case WIRETYPE_START_GROUP:
// take end group into account....
return tagSize + getProtoBuf(tag, i).getDataSize() + tagSize;
}
// take the object as stored
Object o = getObject(tag, i, ProtoBufType.TYPE_UNDEFINED);
int contentSize;
if (o instanceof byte[]){
contentSize = ((byte[]) o).length;
} else if (o instanceof String) {
contentSize = encodeUtf8((String) o, null, 0);
} else {
contentSize = ((ProtoBuf) o).getDataSize();
}
return tagSize + getVarIntSize(contentSize) + contentSize;
}
/**
* Returns the number of bytes needed to encode the given value using
* WIRETYPE_VARINT
*/
private static int getVarIntSize(long i) {
if (i < 0) {
return 10;
}
int size = 1;
while (i >= 128) {
size++;
i >>= 7;
}
return size;
}
/**
* Writes this and nested protocol buffers to the given output stream.
*
* @param os target output stream
* @throws IOException thrown if there is an IOException
*/
public void outputTo(OutputStream os) throws IOException {
for (int tag = 0; tag <= maxTag(); tag++) {
int size = getCount(tag);
int wireType = getWireType(tag);
// ignore default values
for (int i = 0; i < size; i++) {
writeVarInt(os, (tag << 3) | wireType);
switch (wireType) {
case WIRETYPE_FIXED32:
case WIRETYPE_FIXED64:
long v = ((Long) getObject(tag, i, ProtoBufType.TYPE_INT64))
.longValue();
int cnt = (wireType == WIRETYPE_FIXED32) ? 4 : 8;
for (int b = 0; b < cnt; b++) {
os.write((int) (v & 0x0ff));
v >>= 8;
}
break;
case WIRETYPE_VARINT:
v = ((Long) getObject(tag, i, ProtoBufType.TYPE_INT64)).longValue();
if (isZigZagEncodedType(tag)) {
v = zigZagEncode(v);
}
writeVarInt(os, v);
break;
case WIRETYPE_LENGTH_DELIMITED:
Object o = getObject(tag, i,
getType(tag) == ProtoBufType.TYPE_MESSAGE
? ProtoBufType.TYPE_UNDEFINED
: ProtoBufType.TYPE_DATA);
if (o instanceof byte[]){
byte[] data = (byte[]) o;
writeVarInt(os, data.length);
os.write(data);
} else {
ProtoBuf msg = (ProtoBuf) o;
writeVarInt(os, msg.getDataSize());
msg.outputTo(os);
}
break;
case WIRETYPE_START_GROUP:
((ProtoBuf) getObject(tag, i, ProtoBufType.TYPE_GROUP))
.outputTo(os);
writeVarInt(os, (tag << 3) | WIRETYPE_END_GROUP);
break;
default:
throw new IllegalArgumentException();
}
}
}
}
/**
* Returns true if the given tag has a signed type that should be ZigZag-
* encoded on the wire.
*
* ZigZag encoding turns a signed number into
* a non-negative number by mapping negative input numbers to positive odd
* numbers in the output space, and positive input numbers to positive even
* numbers in the output space. This is useful because the wire format
* for protocol buffers requires a large number of bytes to encode
* negative integers, while positive integers take up a smaller number
* of bytes proportional to their magnitude.
*/
private boolean isZigZagEncodedType(int tag) {
int declaredType = getType(tag);
return declaredType == ProtoBufType.TYPE_SINT32 ||
declaredType == ProtoBufType.TYPE_SINT64;
}
/**
* Converts a signed number into a non-negative ZigZag-encoded number.
*/
private static long zigZagEncode(long v) {
v = ((v << 1) ^ -(v >>> 63));
return v;
}
/**
* Converts a non-negative ZigZag-encoded number back into a signed number.
*/
private static long zigZagDecode(long v) {
v = (v >>> 1) ^ -(v & 1);
return v;
}
/**
* Writes this and nested protocol buffers to a byte array.
*
* @throws IOException thrown if there is problem writing the byte array
*/
public byte[] toByteArray() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
outputTo(baos);
return baos.toByteArray();
}
/**
* Returns the largest tag id used in this message (to simplify testing).
*/
public int maxTag() {
return values.size() - 1;
}
/**
* Sets the given tag to the given boolean value.
*/
public void setBool(int tag, boolean value) {
setObject(tag, value ? TRUE : FALSE);
}
/**
* Sets the given tag to the given data bytes.
*/
public void setBytes(int tag, byte[] value) {
setObject(tag, value);
}
/**
* Sets the given tag to the given integer value.
*/
public void setInt(int tag, int value) {
setLong(tag, value);
}
/**
* Sets the given tag to the given long value.
*/
public void setLong(int tag, long value) {
setObject(tag, value >= 0 && value < SMALL_NUMBERS.length
? SMALL_NUMBERS[(int) value] : new Long(value));
}
/**
* Sets the given tag to the given double value.
*/
public void setDouble(int tag, double value) {
setLong(tag, Double.doubleToLongBits(value));
}
/**
* Sets the given tag to the given float value.
*/
public void setFloat(int tag, float value) {
setInt(tag, Float.floatToIntBits(value));
}
/**
* Sets the given tag to the given Group or nested Message.
*/
public void setProtoBuf(int tag, ProtoBuf pb) {
setObject(tag, pb);
}
/**
* Sets the given tag to the given String value.
*/
public void setString(int tag, String value) {
setObject(tag, value);
}
/**
* Inserts the given boolean value for the given tag at the given index.
*/
public void insertBool(int tag, int index, boolean value) {
insertObject(tag, index, value ? TRUE : FALSE);
}
/**
* Inserts the given byte array value for the given tag at the given index.
*/
public void insertBytes(int tag, int index, byte[] value) {
insertObject(tag, index, value);
}
/**
* Inserts the given int value for the given tag at the given index.
*/
public void insertInt(int tag, int index, int value) {
insertLong(tag, index, value);
}
/**
* Inserts the given long value for the given tag at the given index.
*/
public void insertLong(int tag, int index, long value) {
insertObject(tag, index, value >= 0 && value < SMALL_NUMBERS.length
? SMALL_NUMBERS[(int) value] : new Long(value));
}
/**
* Inserts the given float value for the given tag at the given index.
*/
public void insertFloat(int tag, int index, float value) {
insertInt(tag, index, Float.floatToIntBits(value));
}
/**
* Inserts the given double value for the given tag at the given index.
*/
public void insertDouble(int tag, int index, double value) {
insertLong(tag, index, Double.doubleToLongBits(value));
}
/**
* Inserts the given group or message for the given tag at the given index.
*/
public void insertProtoBuf(int tag, int index, ProtoBuf pb) {
insertObject(tag, index, pb);
}
/**
* Inserts the given string value for the given tag at the given index.
*/
public void insertString(int tag, int index, String value) {
insertObject(tag, index, value);
}
// ----------------- private stuff below this line ------------------------
private void assertTypeMatch(int tag, Object object){
int tagType = getType(tag);
if (tagType == ProtoBufType.TYPE_UNDEFINED && msgType == null) {
return;
}
if (object instanceof Boolean) {
if (tagType == ProtoBufType.TYPE_BOOL
|| tagType == WIRETYPE_VARINT) {
return;
}
} else if (object instanceof Long) {
switch(tagType){
case WIRETYPE_FIXED32:
case WIRETYPE_FIXED64:
case WIRETYPE_VARINT:
case ProtoBufType.TYPE_BOOL:
case ProtoBufType.TYPE_ENUM:
case ProtoBufType.TYPE_FIXED32:
case ProtoBufType.TYPE_FIXED64:
case ProtoBufType.TYPE_INT32:
case ProtoBufType.TYPE_INT64:
case ProtoBufType.TYPE_SFIXED32:
case ProtoBufType.TYPE_SFIXED64:
case ProtoBufType.TYPE_UINT32:
case ProtoBufType.TYPE_UINT64:
case ProtoBufType.TYPE_SINT32:
case ProtoBufType.TYPE_SINT64:
case ProtoBufType.TYPE_FLOAT:
case ProtoBufType.TYPE_DOUBLE:
return;
}
} else if (object instanceof byte[]){
switch (tagType){
case WIRETYPE_LENGTH_DELIMITED:
case ProtoBufType.TYPE_DATA:
case ProtoBufType.TYPE_MESSAGE:
case ProtoBufType.TYPE_TEXT:
case ProtoBufType.TYPE_BYTES:
return;
}
} else if (object instanceof ProtoBuf) {
switch (tagType){
case WIRETYPE_LENGTH_DELIMITED:
case WIRETYPE_START_GROUP:
case ProtoBufType.TYPE_DATA:
case ProtoBufType.TYPE_GROUP:
case ProtoBufType.TYPE_MESSAGE:
if (msgType == null || msgType.getData(tag) == null ||
((ProtoBuf) object).msgType == null ||
((ProtoBuf) object).msgType == msgType.getData(tag)) {
return;
}
}
} else if (object instanceof String){
switch (tagType){
case WIRETYPE_LENGTH_DELIMITED:
case ProtoBufType.TYPE_DATA:
case ProtoBufType.TYPE_TEXT:
case ProtoBufType.TYPE_STRING:
return;
}
}
throw new IllegalArgumentException(MSG_MISMATCH + " type:" + msgType +
" tag:" + tag);
}
/**
* Returns the default value for the given tag.
*/
private Object getDefault(int tag){
switch(getType(tag)){
case ProtoBufType.TYPE_UNDEFINED:
case ProtoBufType.TYPE_GROUP:
case ProtoBufType.TYPE_MESSAGE:
return null;
default:
return msgType.getData(tag);
}
}
/**
* Returns the indicated value converted to the given type.
*
* @throws ArrayIndexOutOfBoundsException for invalid tags and indices
* @throws IllegalArgumentException if count is greater than one.
*/
private Object getObject(int tag, int desiredType) {
int count = getCount(tag);
if (count == 0){
return getDefault(tag);
}
if (count > 1){
throw new IllegalArgumentException();
}
return getObject(tag, 0, desiredType);
}
/**
* Returns the indicated value converted to the given type.
*
* @throws ArrayIndexOutOfBoundsException for invalid tags and indices
*/
private Object getObject(int tag, int index, int desiredType) {
if (index >= getCount(tag)) {
throw new ArrayIndexOutOfBoundsException();
}
Object o = values.elementAt(tag);
Vector v = null;
if (o instanceof Vector) {
v = (Vector) o;
o = v.elementAt(index);
}
Object o2 = convert(o, desiredType);
if (o2 != o && o != null) {
if (v == null){
setObject(tag, o2);
} else {
v.setElementAt(o2, index);
}
}
return o2;
}
/**
* Returns the wire type for the given tag. Calls getType() internally,
* so a wire type should be found for all non-empty tags, even if no
* message type is set and the tag was not previously read.
*/
private final int getWireType(int tag) {
int tagType = getType(tag);
switch (tagType) {
case WIRETYPE_VARINT:
case WIRETYPE_FIXED32:
case WIRETYPE_FIXED64:
case WIRETYPE_LENGTH_DELIMITED:
case WIRETYPE_START_GROUP:
case ProtoBufType.TYPE_UNDEFINED:
return tagType;
case ProtoBufType.TYPE_BOOL:
case ProtoBufType.TYPE_INT32:
case ProtoBufType.TYPE_INT64:
case ProtoBufType.TYPE_UINT32:
case ProtoBufType.TYPE_UINT64:
case ProtoBufType.TYPE_SINT32:
case ProtoBufType.TYPE_SINT64:
case ProtoBufType.TYPE_ENUM:
return WIRETYPE_VARINT;
case ProtoBufType.TYPE_DATA:
case ProtoBufType.TYPE_MESSAGE:
case ProtoBufType.TYPE_TEXT:
case ProtoBufType.TYPE_BYTES:
case ProtoBufType.TYPE_STRING:
return WIRETYPE_LENGTH_DELIMITED;
case ProtoBufType.TYPE_DOUBLE:
case ProtoBufType.TYPE_FIXED64:
case ProtoBufType.TYPE_SFIXED64:
return WIRETYPE_FIXED64;
case ProtoBufType.TYPE_FLOAT:
case ProtoBufType.TYPE_FIXED32:
case ProtoBufType.TYPE_SFIXED32:
return WIRETYPE_FIXED32;
case ProtoBufType.TYPE_GROUP:
return WIRETYPE_START_GROUP;
default:
throw new RuntimeException(MSG_UNSUPPORTED + ':' + msgType + '/' +
tag + '/' + tagType);
}
}
/**
* Inserts a value.
*/
private void insertObject(int tag, int index, Object o) {
assertTypeMatch(tag, o);
int count = getCount(tag);
if (count == 0) {
setObject(tag, o);
} else {
Object curr = values.elementAt(tag);
Vector v;
if (curr instanceof Vector) {
v = (Vector) curr;
} else {
v = new Vector();
v.addElement(curr);
values.setElementAt(v, tag);
}
v.insertElementAt(o, index);
}
}
/**
* Converts the object if a better suited class exists for the given .proto
* type. If the formats are not compatible, an exception is thrown.
*/
private Object convert(Object obj, int tagType) {
switch (tagType) {
case ProtoBufType.TYPE_UNDEFINED:
return obj;
case ProtoBufType.TYPE_BOOL:
if (obj instanceof Boolean) {
return obj;
}
switch ((int) ((Long) obj).longValue()) {
case 0:
return FALSE;
case 1:
return TRUE;
default:
throw new IllegalArgumentException(MSG_MISMATCH);
}
case ProtoBufType.TYPE_FIXED32:
case ProtoBufType.TYPE_FIXED64:
case ProtoBufType.TYPE_INT32:
case ProtoBufType.TYPE_INT64:
case ProtoBufType.TYPE_SFIXED32:
case ProtoBufType.TYPE_SFIXED64:
case ProtoBufType.TYPE_SINT32:
case ProtoBufType.TYPE_SINT64:
if (obj instanceof Boolean) {
return SMALL_NUMBERS[((Boolean) obj).booleanValue() ? 1 : 0];
}
return obj;
case ProtoBufType.TYPE_DATA:
case ProtoBufType.TYPE_BYTES:
if (obj instanceof String) {
return encodeUtf8((String) obj);
} else if (obj instanceof ProtoBuf) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
try {
((ProtoBuf) obj).outputTo(buf);
return buf.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e.toString());
}
}
return obj;
case ProtoBufType.TYPE_TEXT:
case ProtoBufType.TYPE_STRING:
if (obj instanceof byte[]) {
byte[] data = (byte[]) obj;
return decodeUtf8(data, 0, data.length, true);
}
return obj;
case ProtoBufType.TYPE_GROUP:
case ProtoBufType.TYPE_MESSAGE:
if (obj instanceof byte[]) {
try {
return new ProtoBuf(null).parse((byte[]) obj);
} catch (IOException e) {
throw new RuntimeException(e.toString());
}
}
return obj;
default:
// default includes FLOAT and DOUBLE
throw new RuntimeException(MSG_UNSUPPORTED);
}
}
/**
* Reads a variable-size integer (up to 10 bytes for 64 bit) from the
* given input stream.
*
* @param is the stream to read from
* @param permitEOF if true, -1 is returned when EOF is reached instead of
* throwing an IOException
* @return the integer value read from the stream, or -1 if EOF is
* reached and permitEOF is true
* @throws IOException thrown for underlying IO issues and if EOF
* is reached and permitEOF is false
*/
static long readVarInt(InputStream is, boolean permitEOF) throws IOException {
long result = 0;
int shift = 0;
// max 10 byte wire format for 64 bit integer (7 bit data per byte)
for (int i = 0; i < VARINT_MAX_BYTES; i++) {
int in = is.read();
if (in == -1) {
if (i == 0 && permitEOF) {
return -1;
} else {
throw new IOException("EOF");
}
}
result |= ((long) (in & 0x07f)) << shift;
if ((in & 0x80) == 0){
break; // get out early
}
shift += 7;
}
return result;
}
/**
* Internal helper method to set a (single) value. Overwrites all existing
* values.
*/
private void setObject(int tag, Object o) {
if (values.size() <= tag) {
values.setSize(tag + 1);
}
if (o != null) {
assertTypeMatch(tag, o);
}
values.setElementAt(o, tag);
}
/**
* Write a variable-size integer to the given output stream.
*/
static void writeVarInt(OutputStream os, long value) throws IOException {
for (int i = 0; i < VARINT_MAX_BYTES; i++) {
int toWrite = (int) (value & 0x7f);
value >>>= 7;
if (value == 0) {
os.write(toWrite);
break;
} else {
os.write(toWrite | 0x080);
}
}
}
/**
* Returns a byte array containing the given string, encoded as UTF-8. The
* returned byte array contains at least s.length() bytes and at most
* 4 * s.length() bytes. UTF-16 surrogates are transcoded to UTF-8.
*
* @param s input string to be encoded
* @return UTF-8 encoded input string
*/
static byte[] encodeUtf8(String s) {
int len = encodeUtf8(s, null, 0);
byte[] result = new byte[len];
encodeUtf8(s, result, 0);
return result;
}
/**
* Encodes the given string to UTF-8 in the given buffer or calculates
* the space needed if the buffer is null.
*
* @param s the string to be UTF-8 encoded
* @param buf byte array to write to
* @return new buffer position after writing (which equals the required size
* if pos is 0)
*/
static int encodeUtf8(String s, byte[] buf, int pos){
int len = s.length();
for (int i = 0; i < len; i++){
int code = s.charAt(i);
// surrogate 0xd800 .. 0xdfff?
if (code >= 0x0d800 && code <= 0x0dfff && i + 1 < len){
int codeLo = s.charAt(i + 1);
// 0xfc00 is the surrogate id mask (first six bit of 16 set)
// 0x03ff is the surrogate data mask (remaining 10 bit)
// check if actually a surrogate pair (d800 ^ dc00 == 0400)
if (((codeLo & 0xfc00) ^ (code & 0x0fc00)) == 0x0400){
i += 1;
int codeHi;
if ((codeLo & 0xfc00) == 0x0d800){
codeHi = codeLo;
codeLo = code;
} else {
codeHi = code;
}
code = (((codeHi & 0x3ff) << 10) | (codeLo & 0x3ff)) + 0x10000;
}
}
if (code <= 0x007f) {
if (buf != null){
buf[pos] = (byte) code;
}
pos += 1;
} else if (code <= 0x07FF) {
// non-ASCII <= 0x7FF
if (buf != null){
buf[pos] = (byte) (0xc0 | (code >> 6));
buf[pos + 1] = (byte) (0x80 | (code & 0x3F));
}
pos += 2;
} else if (code <= 0xFFFF){
// 0x7FF < code <= 0xFFFF
if (buf != null){
buf[pos] = (byte) ((0xe0 | (code >> 12)));
buf[pos + 1] = (byte) ((0x80 | ((code >> 6) & 0x3F)));
buf[pos + 2] = (byte) ((0x80 | (code & 0x3F)));
}
pos += 3;
} else {
if (buf != null){
buf[pos] = (byte) ((0xf0 | (code >> 18)));
buf[pos + 1] = (byte) ((0x80 | ((code >> 12) & 0x3F)));
buf[pos + 2] = (byte) ((0x80 | ((code >> 6) & 0x3F)));
buf[pos + 3] = (byte) ((0x80 | (code & 0x3F)));
}
pos += 4;
}
}
return pos;
}
/**
* Decodes an array of UTF-8 bytes to a Java string (UTF-16). The tolerant
* flag determines what to do in case of illegal or unsupported sequences.
*
* @param data input byte array containing UTF-8 data
* @param start decoding start position in byte array
* @param end decoding end position in byte array
* @param tolerant if true, an IllegalArgumentException is thrown for illegal
* UTF-8 codes
* @return the string containing the UTF-8 decoding result
*/
static String decodeUtf8(byte[] data, int start, int end,
boolean tolerant){
StringBuffer sb = new StringBuffer(end - start);
int pos = start;
while (pos < end){
int b = data[pos++] & 0x0ff;
if (b <= 0x7f){
sb.append((char) b);
} else if (b >= 0xf5){ // byte sequence too long
if (!tolerant){
throw new IllegalArgumentException("Invalid UTF8");
}
sb.append((char) b);
} else {
int border = 0xe0;
int count = 1;
int minCode = 128;
int mask = 0x01f;
while (b >= border){
border = (border >> 1) | 0x80;
minCode = minCode << (count == 1 ? 4 : 5);
count++;
mask = mask >> 1;
}
int code = b & mask;
for (int i = 0; i < count; i++){
code = code << 6;
if (pos >= end){
if (!tolerant){
throw new IllegalArgumentException("Invalid UTF8");
}
// otherwise, assume zeroes
} else {
if (!tolerant && (data[pos] & 0xc0) != 0x80){
throw new IllegalArgumentException("Invalid UTF8");
}
code |= (data[pos++] & 0x3f); // six bit
}
}
// illegal code or surrogate code
if (!tolerant && code < minCode || (code >= 0xd800 && code <= 0xdfff)){
throw new IllegalArgumentException("Invalid UTF8");
}
if (code <= 0x0ffff){
sb.append((char) code);
} else { // surrogate UTF16
code -= 0x10000;
sb.append((char) (0xd800 | (code >> 10))); // high 10 bit
sb.append((char) (0xdc00 | (code & 0x3ff))); // low 10 bit
}
}
}
return sb.toString();
}
}
// Copyright 2007 Google Inc.
// All Rights Reserved.
package com.google.common.io.protocol;
import java.util.*;
/**
* This class can be used to create a memory model of a .proto file. Currently,
* it is assumed that tags ids are not large. This could be improved by storing
* a start offset, relaxing the assumption to a dense number space.
*/
public class ProtoBufType {
// Note: Values 0..15 are reserved for wire types!
public static final int TYPE_UNDEFINED = 16;
public static final int TYPE_DOUBLE = 17;
public static final int TYPE_FLOAT = 18;
public static final int TYPE_INT64 = 19;
public static final int TYPE_UINT64 = 20;
public static final int TYPE_INT32 = 21;
public static final int TYPE_FIXED64 = 22;
public static final int TYPE_FIXED32 = 23;
public static final int TYPE_BOOL = 24;
public static final int TYPE_DATA = 25;
public static final int TYPE_GROUP = 26;
public static final int TYPE_MESSAGE = 27;
public static final int TYPE_TEXT = 28;
public static final int TYPE_UINT32 = 29;
public static final int TYPE_ENUM = 30;
public static final int TYPE_SFIXED32 = 31;
public static final int TYPE_SFIXED64 = 32;
// new protobuf 2 types
public static final int TYPE_SINT32 = 33;
public static final int TYPE_SINT64 = 34;
public static final int TYPE_BYTES = 35;
public static final int TYPE_STRING = 36;
public static final int MASK_TYPE = 0x0ff;
public static final int MASK_MODIFIER = 0x0ff00;
public static final int REQUIRED = 0x100;
public static final int OPTIONAL = 0x200;
public static final int REPEATED = 0x400;
private final StringBuffer types = new StringBuffer();
private final Vector data = new Vector();
private final String typeName;
/**
* Empty constructor.
*/
public ProtoBufType() {
typeName = null;
}
/**
* Constructor including a type name for debugging purposes.
*/
public ProtoBufType(String typeName) {
this.typeName = typeName;
}
/**
* Adds a tag description. The data parameter contains the group definition
* for group elements and the default value for regular elements.
*
* @param optionsAndType any legal combination (bitwise or) of REQUIRED
* or OPTIONAL and REPEATED and one of the TYPE_
* constants
* @param tag the tag id
* @param data the type for group elements (or the default value for
* regular elements in future versions)
* @return this is returned to permit cascading
*/
public ProtoBufType addElement(int optionsAndType, int tag, Object data) {
while (types.length() <= tag) {
types.append((char) TYPE_UNDEFINED);
this.data.addElement(null);
}
types.setCharAt(tag, (char) optionsAndType);
this.data.setElementAt(data, tag);
return this;
}
/**
* Returns the type for the given tag id (without modifiers such as OPTIONAL,
* REPEATED). For undefined tags, TYPE_UNDEFINED is returned.
*/
public int getType(int tag) {
return (tag < 0 || tag >= types.length())
? TYPE_UNDEFINED
: (types.charAt(tag) & MASK_TYPE);
}
/**
* Returns a bit combination of the modifiers for the given tag id
* (OPTIONAL, REPEATED, REQUIRED). For undefined tags, OPTIONAL|REPEATED
* is returned.
*/
public int getModifiers(int tag) {
return (tag < 0 || tag >= types.length())
? (OPTIONAL | REPEATED)
: (types.charAt(tag) & MASK_MODIFIER);
}
/**
* Returns the data associated to a given tag (either the default value for
* regular elements or a ProtoBufType for groups and messages). For undefined
* tags, null is returned.
*/
public Object getData(int tag) {
return (tag < 0 || tag >= data.size()) ? null : data.elementAt(tag);
}
/**
* Returns the type name set in the constructor for debugging purposes.
*/
public String toString() {
return typeName;
}
/**
* {@inheritDoc}
* <p>Two ProtoBufTypes are equals if the fields types are the same.
*/
public boolean equals(Object object) {
if (null == object) {
// trivial check
return false;
} else if (this == object) {
// trivial check
return true;
} else if (this.getClass() != object.getClass()) {
// different class
return false;
}
ProtoBufType other = (ProtoBufType) object;
return stringEquals(types, other.types);
}
/**
* {@inheritDoc}
*/
public int hashCode() {
if (types != null) {
return types.hashCode();
} else {
return super.hashCode();
}
}
public static boolean stringEquals(CharSequence a, CharSequence b) {
if (a == b) return true;
int length;
if (a != null && b != null && (length = a.length()) == b.length()) {
if (a instanceof String && b instanceof String) {
return a.equals(b);
} else {
for (int i = 0; i < length; i++) {
if (a.charAt(i) != b.charAt(i)) return false;
}
return true;
}
}
return false;
}
}
// Copyright 2008 Google Inc. All Rights Reserved.
package com.google.common.io.protocol;
import java.io.*;
/**
* Utility functions for dealing with ProtoBuf objects consolidated from
* previous spot implementations across the codebase.
*
*/
public final class ProtoBufUtil {
private ProtoBufUtil() {
}
/** Convenience method to return a string value from of a proto or "". */
public static String getProtoValueOrEmpty(ProtoBuf proto, int tag) {
try {
return (proto != null && proto.has(tag)) ? proto.getString(tag) : "";
} catch (ClassCastException e) {
return "";
}
}
/** Convenience method to return a string value from of a sub-proto or "". */
public static String getSubProtoValueOrEmpty(
ProtoBuf proto, int sub, int tag) {
try {
return getProtoValueOrEmpty(getSubProtoOrNull(proto, sub), tag);
} catch (ClassCastException e) {
return "";
}
}
/** Convenience method to get a subproto if the proto has it. */
public static ProtoBuf getSubProtoOrNull(ProtoBuf proto, int sub) {
return (proto != null && proto.has(sub)) ? proto.getProtoBuf(sub) : null;
}
/**
* Get an int with "tag" from the proto buffer. If the given field can't be
* retrieved, return the provided default value.
*
* @param proto The proto buffer.
* @param tag The tag value that identifies which protocol buffer field to
* retrieve.
* @param defaultValue The value to return if the field can't be retrieved.
* @return The result which should be an integer.
*/
public static int getProtoValueOrDefault(ProtoBuf proto, int tag,
int defaultValue) {
try {
return (proto != null && proto.has(tag))
? proto.getInt(tag) : defaultValue;
} catch (IllegalArgumentException e) {
return defaultValue;
} catch (ClassCastException e) {
return defaultValue;
}
}
/**
* Get an Int with "tag" from the proto buffer.
* If the given field can't be retrieved, return 0.
*
* @param proto The proto buffer.
* @param tag The tag value that identifies which protocol buffer field to
* retrieve.
* @return The result which should be an integer.
*/
public static int getProtoValueOrZero(ProtoBuf proto, int tag) {
return getProtoValueOrDefault(proto, tag, 0);
}
/**
* Get an Long with "tag" from the proto buffer.
* If the given field can't be retrieved, return 0.
*
* @param proto The proto buffer.
* @param tag The tag value that identifies which protocol buffer field to
* retrieve.
* @return The result which should be an integer.
*/
public static long getProtoLongValueOrZero(ProtoBuf proto, int tag) {
try {
return (proto != null && proto.has(tag)) ? proto.getLong(tag) : 0L;
} catch (IllegalArgumentException e) {
return 0L;
} catch (ClassCastException e) {
return 0L;
}
}
/**
* Get an Int with "tag" from the proto buffer.
* If the given field can't be retrieved, return -1.
*
* @param proto The proto buffer.
* @param tag The tag value that identifies which protocol buffer field to
* retrieve.
* @return The result which should be a long.
*/
public static long getProtoValueOrNegativeOne(ProtoBuf proto, int tag) {
try {
return (proto != null && proto.has(tag)) ? proto.getLong(tag) : -1;
} catch (IllegalArgumentException e) {
return -1;
} catch (ClassCastException e) {
return -1;
}
}
/**
* Reads a single protocol buffer from the given input stream. This method is
* provided where the client needs incremental access to the contents of a
* protocol buffer which contains a sequence of protocol buffers.
* <p />
* Please use {@link #getInputStreamForProtoBufResponse} to obtain an input
* stream suitable for this method.
*
* @param umbrellaType the type of the "outer" protocol buffer containing
* the message to read
* @param is the stream to read the protocol buffer from
* @param result the result protocol buffer (must be empty, will be filled
* with the data read and the type will be set)
* @return the tag id of the message, -1 at the end of the stream
*/
public static int readNextProtoBuf(ProtoBufType umbrellaType,
InputStream is, ProtoBuf result) throws IOException {
long tagAndType = ProtoBuf.readVarInt(is, true /* permits EOF */);
if (tagAndType == -1) {
return -1;
}
if ((tagAndType & 7) != ProtoBuf.WIRETYPE_LENGTH_DELIMITED) {
throw new IOException("Message expected");
}
int tag = (int) (tagAndType >>> 3);
result.setType((ProtoBufType) umbrellaType.getData(tag));
int length = (int) ProtoBuf.readVarInt(is, false);
result.parse(is, length);
return tag;
}
/**
* A wrapper for <code> getProtoValueOrNegativeOne </code> that drills into
* a sub message returning the long value if it exists, returning -1 if it
* does not.
*
* @param proto The proto buffer.
* @param tag The tag value that identifies which protocol buffer field to
* retrieve.
* @param sub The sub tag value that identifies which protocol buffer
* sub-field to retrieve.n
* @return The result which should be a long.
*/
public static long getSubProtoValueOrNegativeOne(
ProtoBuf proto, int sub, int tag) {
try {
return getProtoValueOrNegativeOne(getSubProtoOrNull(proto, sub), tag);
} catch (IllegalArgumentException e) {
return -1;
} catch (ClassCastException e) {
return -1;
}
}
/**
* A wrapper for {@link #getProtoValueOrDefault(ProtoBuf, int, int)} that
* drills into a sub message returning the int value if it exists, returning
* the given default if it does not.
*
* @param proto The proto buffer.
* @param tag The tag value that identifies which protocol buffer field to
* retrieve.
* @param sub The sub tag value that identifies which protocol buffer
* sub-field to retrieve.
* @param defaultValue The value to return if the field is not present.
* @return The result which should be a long.
*/
public static int getSubProtoValueOrDefault(ProtoBuf proto, int sub, int tag,
int defaultValue) {
try {
return getProtoValueOrDefault(getSubProtoOrNull(proto, sub), tag,
defaultValue);
} catch (IllegalArgumentException e) {
return defaultValue;
} catch (ClassCastException e) {
return defaultValue;
}
}
/**
* Creates a sub ProtoBuf of the given Protobuf and sets it.
*
* @param proto The proto buffer.
* @param tag The tag value that identifies which protocol buffer field to
* create.
* @return the sub ProtoBuf generated.
*/
public static ProtoBuf createProtoBuf(ProtoBuf proto, int tag) {
ProtoBuf child = proto.createGroup(tag);
proto.setProtoBuf(tag, child);
return child;
}
/**
* Creates a sub ProtoBuf of the given Protobuf and adds it.
*
* @param proto The proto buffer.
* @param tag The tag value that identifies which protocol buffer field to
* add.
* @return the sub ProtoBuf generated.
*/
public static ProtoBuf addProtoBuf(ProtoBuf proto, int tag) {
ProtoBuf child = proto.createGroup(tag);
proto.addProtoBuf(tag, child);
return child;
}
/**
* Writes the ProtoBuf to the given DataOutput. This is useful for unit
* tests.
*
* @param output The data output to write to.
* @param protoBuf The proto buffer.
*/
public static void writeProtoBufToOutput(DataOutput output, ProtoBuf protoBuf)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
protoBuf.outputTo(baos);
byte[] bytes = baos.toByteArray();
output.writeInt(bytes.length);
output.write(bytes);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment