| // 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 |
| private static final int WIRETYPE_END_GROUP = 4; |
| private static final int WIRETYPE_FIXED32 = 5; |
| private static final int WIRETYPE_FIXED64 = 1; |
| private static final int WIRETYPE_LENGTH_DELIMITED = 2; |
| private static final int WIRETYPE_START_GROUP = 3; |
| private 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 final 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 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 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; |
| } |
| |
| /** |
| * Convenience method for determining whether a tag has a value. Note: in |
| * contrast to getCount(tag) > 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 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 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: |
| 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(); |
| } |
| |
| } |