瀏覽代碼

Initial Contribution

The Android Open Source Project 17 年之前
當前提交
35be73bfeb

+ 1218 - 0
src/com/google/common/io/protocol/ProtoBuf.java

@@ -0,0 +1,1218 @@
+// 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) &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 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();
+  }
+
+}

+ 124 - 0
src/com/google/common/io/protocol/ProtoBufType.java

@@ -0,0 +1,124 @@
+// Copyright 2007 The Android Open Source Project
+// 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;
+  }
+}

+ 97 - 0
src/com/google/common/io/protocol/ProtoBufUtil.java

@@ -0,0 +1,97 @@
+// Copyright 2008 The Android Open Source Project
+
+package com.google.common.io.protocol;
+
+/**
+ * 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 {
+      ProtoBuf subProto =
+          (proto != null && proto.has(sub)) ? proto.getProtoBuf(sub) : null;
+      return getProtoValueOrEmpty(subProto, tag);
+    } catch (ClassCastException e) {
+      return "";
+    }
+  }
+
+  /**
+   * 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) {
+    try {
+      return (proto != null && proto.has(tag)) ? proto.getInt(tag) : 0;
+    } catch (IllegalArgumentException e) {
+      return 0;
+    } catch (ClassCastException e) {
+      return 0;
+    }
+  }
+
+  /**
+   * 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;
+    }
+  }
+
+  /**
+   * 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 {
+      ProtoBuf subProto =
+          (proto != null && proto.has(sub)) ? proto.getProtoBuf(sub) : null;
+      return getProtoValueOrNegativeOne(subProto, tag);
+    } catch (IllegalArgumentException e) {
+      return -1;
+    } catch (ClassCastException e) {
+      return -1; 
+    }
+  }
+}

+ 5 - 0
src/com/google/common/io/protocol/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+    {@hide}
+</body>
+</html>