格式化显示ProtocolBuffer数据

1:为什么要用Protocol Buffer?

在回答这个问题之前,我们还是先给出一个在实际开发中经常会遇到的系统场景。比如:我们的客户端程序是使用Java开发的,可能运行自不同的平台,如:Linux、Windows或者是Android,而我们的服务器程序通常是基于Linux平台并使用C++开发完成的。在这两种程序之间进行数据通讯时存在多种方式用于设计消息格式,如:
  1. 直接传递C/C++语言中一字节对齐的结构体数据,只要结构体的声明为定长格式,那么该方式对于C/C++程序而言就非常方便了,仅需将接收到的数据按照结构体类型强行转换即可。事实上对于变长结构体也不会非常麻烦。在发送数据时,也只需定义一个结构体变量并设置各个成员变量的值之后,再以char*的方式将该二进制数据发送到远端。反之,该方式对于Java开发者而言就会非常繁琐,首先需要将接收到的数据存于ByteBuffer之中,再根据约定的字节序逐个读取每个字段,并将读取后的值再赋值给另外一个值对象中的域变量,以便于程序中其他代码逻辑的编写。对于该类型程序而言,联调的基准是必须客户端和服务器双方均完成了消息报文构建程序的编写后才能展开,而该设计方式将会直接导致Java程序开发的进度过慢。即便是Debug阶段,也会经常遇到Java程序中出现各种域字段拼接的小错误。
  2. 使用SOAP协议(WebService)作为消息报文的格式载体,由该方式生成的报文是基于文本格式的,同时还存在大量的XML描述信息,因此将会大大增加网络IO的负担。又由于XML解析的复杂性,这也会大幅降低报文解析的性能。总之,使用该设计方式将会使系统的整体运行性能明显下降。
  对于以上两种方式所产生的问题,ProtocolBuffer均可以很好的解决,不仅如此,Protocol Buffer还有一个非常重要的优点就是可以保证同一消息报文新旧版本之间的兼容性。

注意:本文使用的版本为protoc-2.5.0,注意代码中引用的版本和根据.proto文件生成java文件版本的一致性,不然可能出现这篇博客中的问题http://blog.csdn.net/gao36951/article/details/45029729

2:下面来介绍相关代码

2.1首先介绍三个工具类

WireFormat.java

package com.test.util;

import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors;


/**
 * This class is used internally by the Protocol Buffer library and generated
 * message implementations.  It is public only because those generated messages
 * do not reside in the {@code protobuf} package.  Others should not use this
 * class directly.
 *
 * This class contains constants and helper functions useful for dealing with
 * the Protocol Buffer wire format.
 *
 * @author kenton@google.com Kenton Varda
 */
public final class WireFormat {
  // Do not allow instantiation.
  private WireFormat() {}

  public static final int WIRETYPE_VARINT           = 0;
  public static final int WIRETYPE_FIXED64          = 1;
  public static final int WIRETYPE_LENGTH_DELIMITED = 2;
  public static final int WIRETYPE_START_GROUP      = 3;
  public static final int WIRETYPE_END_GROUP        = 4;
  public static final int WIRETYPE_FIXED32          = 5;

  static final int TAG_TYPE_BITS = 3;
  static final int TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1;

  /** Given a tag value, determines the wire type (the lower 3 bits). */
  static int getTagWireType(final int tag) {
    return tag & TAG_TYPE_MASK;
  }

  /** Given a tag value, determines the field number (the upper 29 bits). */
  public static int getTagFieldNumber(final int tag) {
    return tag >>> TAG_TYPE_BITS;
  }

  /** Makes a tag value given a field number and wire type. */
  static int makeTag(final int fieldNumber, final int wireType) {
    return (fieldNumber << TAG_TYPE_BITS) | wireType;
  }

  /**
   * Lite equivalent to {@link Descriptors.FieldDescriptor.JavaType}.  This is
   * only here to support the lite runtime and should not be used by users.
   */
  public enum JavaType {
    INT(0),
    LONG(0L),
    FLOAT(0F),
    DOUBLE(0D),
    BOOLEAN(false),
    STRING(""),
    BYTE_STRING(ByteString.EMPTY),
    ENUM(null),
    MESSAGE(null);

    JavaType(final Object defaultDefault) {
      this.defaultDefault = defaultDefault;
    }

    /**
     * The default default value for fields of this type, if it's a primitive
     * type.
     */
    Object getDefaultDefault() {
      return defaultDefault;
    }

    private final Object defaultDefault;
  }

  /**
   * Lite equivalent to {@link Descriptors.FieldDescriptor.Type}.  This is
   * only here to support the lite runtime and should not be used by users.
   */
  public enum FieldType {
    DOUBLE  (JavaType.DOUBLE     , WIRETYPE_FIXED64         ),
    FLOAT   (JavaType.FLOAT      , WIRETYPE_FIXED32         ),
    INT64   (JavaType.LONG       , WIRETYPE_VARINT          ),
    UINT64  (JavaType.LONG       , WIRETYPE_VARINT          ),
    INT32   (JavaType.INT        , WIRETYPE_VARINT          ),
    FIXED64 (JavaType.LONG       , WIRETYPE_FIXED64         ),
    FIXED32 (JavaType.INT        , WIRETYPE_FIXED32         ),
    BOOL    (JavaType.BOOLEAN    , WIRETYPE_VARINT          ),
    STRING  (JavaType.STRING     , WIRETYPE_LENGTH_DELIMITED) {
      public boolean isPackable() { return false; }
    },
    GROUP   (JavaType.MESSAGE    , WIRETYPE_START_GROUP     ) {
      public boolean isPackable() { return false; }
    },
    MESSAGE (JavaType.MESSAGE    , WIRETYPE_LENGTH_DELIMITED) {
      public boolean isPackable() { return false; }
    },
    BYTES   (JavaType.BYTE_STRING, WIRETYPE_LENGTH_DELIMITED) {
      public boolean isPackable() { return false; }
    },
    UINT32  (JavaType.INT        , WIRETYPE_VARINT          ),
    ENUM    (JavaType.ENUM       , WIRETYPE_VARINT          ),
    SFIXED32(JavaType.INT        , WIRETYPE_FIXED32         ),
    SFIXED64(JavaType.LONG       , WIRETYPE_FIXED64         ),
    SINT32  (JavaType.INT        , WIRETYPE_VARINT          ),
    SINT64  (JavaType.LONG       , WIRETYPE_VARINT          );

    FieldType(final JavaType javaType, final int wireType) {
      this.javaType = javaType;
      this.wireType = wireType;
    }

    private final JavaType javaType;
    private final int wireType;

    public JavaType getJavaType() { return javaType; }
    public int getWireType() { return wireType; }

    public boolean isPackable() { return true; }
  }

  // Field numbers for fields in MessageSet wire format.
  static final int MESSAGE_SET_ITEM    = 1;
  static final int MESSAGE_SET_TYPE_ID = 2;
  static final int MESSAGE_SET_MESSAGE = 3;

  // Tag numbers.
  static final int MESSAGE_SET_ITEM_TAG =
    makeTag(MESSAGE_SET_ITEM, WIRETYPE_START_GROUP);
  static final int MESSAGE_SET_ITEM_END_TAG =
    makeTag(MESSAGE_SET_ITEM, WIRETYPE_END_GROUP);
  static final int MESSAGE_SET_TYPE_ID_TAG =
    makeTag(MESSAGE_SET_TYPE_ID, WIRETYPE_VARINT);
  static final int MESSAGE_SET_MESSAGE_TAG =
    makeTag(MESSAGE_SET_MESSAGE, WIRETYPE_LENGTH_DELIMITED);
}

TextFormat.java

package com.test.util;


import java.io.IOException;
import java.math.BigInteger;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.protobuf.ByteString;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.UnknownFieldSet;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;

/**
 * Provide text parsing and formatting support for proto2 instances.
 * The implementation largely follows google/protobuf/text_format.cc.
 *
 * @author wenboz@google.com Wenbo Zhu
 * @author kenton@google.com Kenton Varda
 */
public final class TextFormat {
  private TextFormat() {}

  private static final Printer DEFAULT_PRINTER = new Printer();
  private static final Printer SINGLE_LINE_PRINTER =
      (new Printer()).setSingleLineMode(true);
  private static final Printer UNICODE_PRINTER =
      (new Printer()).setEscapeNonAscii(false);

  /**
   * Outputs a textual representation of the Protocol Message supplied into
   * the parameter output. (This representation is the new version of the
   * classic "ProtocolPrinter" output from the original Protocol Buffer system)
   */
  public static void print(final MessageOrBuilder message, final Appendable output)
                           throws IOException {
    DEFAULT_PRINTER.print(message, new TextGenerator(output));
  }

  /** Outputs a textual representation of {@code fields} to {@code output}. */
  public static void print(final UnknownFieldSet fields,
                           final Appendable output)
                           throws IOException {
    DEFAULT_PRINTER.printUnknownFields(fields, new TextGenerator(output));
  }

  /**
   * Generates a human readable form of this message, useful for debugging and
   * other purposes, with no newline characters.
   */
  public static String shortDebugString(final MessageOrBuilder message) {
    try {
      final StringBuilder sb = new StringBuilder();
      SINGLE_LINE_PRINTER.print(message, new TextGenerator(sb));
      // Single line mode currently might have an extra space at the end.
      return sb.toString().trim();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  /**
   * Generates a human readable form of the unknown fields, useful for debugging
   * and other purposes, with no newline characters.
   */
  public static String shortDebugString(final UnknownFieldSet fields) {
    try {
      final StringBuilder sb = new StringBuilder();
      SINGLE_LINE_PRINTER.printUnknownFields(fields, new TextGenerator(sb));
      // Single line mode currently might have an extra space at the end.
      return sb.toString().trim();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  /**
   * Like {@code print()}, but writes directly to a {@code String} and
   * returns it.
   */
  public static String printToString(final MessageOrBuilder message) {
    try {
      final StringBuilder text = new StringBuilder();
      print(message, text);
      return text.toString();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  /**
   * Like {@code print()}, but writes directly to a {@code String} and
   * returns it.
   */
  public static String printToString(final UnknownFieldSet fields) {
    try {
      final StringBuilder text = new StringBuilder();
      print(fields, text);
      return text.toString();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  /**
   * Same as {@code printToString()}, except that non-ASCII characters
   * in string type fields are not escaped in backslash+octals.
   */
  public static String printToUnicodeString(final MessageOrBuilder message) {
    try {
      final StringBuilder text = new StringBuilder();
      UNICODE_PRINTER.print(message, new TextGenerator(text));
      return text.toString();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  /**
   * Same as {@code printToString()}, except that non-ASCII characters
   * in string type fields are not escaped in backslash+octals.
   */
  public static String printToUnicodeString(final UnknownFieldSet fields) {
    try {
      final StringBuilder text = new StringBuilder();
      UNICODE_PRINTER.printUnknownFields(fields, new TextGenerator(text));
      return text.toString();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  public static void printField(final FieldDescriptor field,
                                final Object value,
                                final Appendable output)
                                throws IOException {
    DEFAULT_PRINTER.printField(field, value, new TextGenerator(output));
  }

  public static String printFieldToString(final FieldDescriptor field,
                                          final Object value) {
    try {
      final StringBuilder text = new StringBuilder();
      printField(field, value, text);
      return text.toString();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  /**
   * Outputs a textual representation of the value of given field value.
   *
   * @param field the descriptor of the field
   * @param value the value of the field
   * @param output the output to which to append the formatted value
   * @throws ClassCastException if the value is not appropriate for the
   *     given field descriptor
   * @throws IOException if there is an exception writing to the output
   */
  public static void printFieldValue(final FieldDescriptor field,
                                     final Object value,
                                     final Appendable output)
                                     throws IOException {
    DEFAULT_PRINTER.printFieldValue(field, value, new TextGenerator(output));
  }

  /**
   * Outputs a textual representation of the value of an unknown field.
   *
   * @param tag the field's tag number
   * @param value the value of the field
   * @param output the output to which to append the formatted value
   * @throws ClassCastException if the value is not appropriate for the
   *     given field descriptor
   * @throws IOException if there is an exception writing to the output
   */
  public static void printUnknownFieldValue(final int tag,
                                            final Object value,
                                            final Appendable output)
                                            throws IOException {
    printUnknownFieldValue(tag, value, new TextGenerator(output));
  }

  private static void printUnknownFieldValue(final int tag,
                                             final Object value,
                                             final TextGenerator generator)
                                             throws IOException {
    switch (WireFormat.getTagWireType(tag)) {
      case WireFormat.WIRETYPE_VARINT:
        generator.print(unsignedToString((Long) value));
        break;
      case WireFormat.WIRETYPE_FIXED32:
        generator.print(
            String.format((Locale) null, "0x%08x", (Integer) value));
        break;
      case WireFormat.WIRETYPE_FIXED64:
        generator.print(String.format((Locale) null, "0x%016x", (Long) value));
        break;
      case WireFormat.WIRETYPE_LENGTH_DELIMITED:
        generator.print("\"");
        generator.print(escapeBytes((ByteString) value));
        generator.print("\"");
        break;
      case WireFormat.WIRETYPE_START_GROUP:
        DEFAULT_PRINTER.printUnknownFields((UnknownFieldSet) value, generator);
        break;
      default:
        throw new IllegalArgumentException("Bad tag: " + tag);
    }
  }

  /** Helper class for converting protobufs to text. */
  public static final class Printer {
    /** Whether to omit newlines from the output. */
    boolean singleLineMode = false;

    /** Whether to escape non ASCII characters with backslash and octal. */
    boolean escapeNonAscii = true;

    public Printer() {}

    /** Setter of singleLineMode */
    public Printer setSingleLineMode(boolean singleLineMode) {
      this.singleLineMode = singleLineMode;
      return this;
    }

    /** Setter of escapeNonAscii */
   public  Printer setEscapeNonAscii(boolean escapeNonAscii) {
      this.escapeNonAscii = escapeNonAscii;
      return this;
    }

    void print(final MessageOrBuilder message, final TextGenerator generator)
        throws IOException {
      for (Map.Entry<FieldDescriptor, Object> field
          : message.getAllFields().entrySet()) {
        printField(field.getKey(), field.getValue(), generator);
      }
      printUnknownFields(message.getUnknownFields(), generator);
    }

    private void printField(final FieldDescriptor field, final Object value,
        final TextGenerator generator) throws IOException {
      if (field.isRepeated()) {
        // Repeated field.  Print each element.
        for (Object element : (List<?>) value) {
          printSingleField(field, element, generator);
        }
      } else {
        printSingleField(field, value, generator);
      }
    }

    private void printSingleField(final FieldDescriptor field,
                                  final Object value,
                                  final TextGenerator generator)
                                  throws IOException {
      if (field.isExtension()) {
        generator.print("[");
        // We special-case MessageSet elements for compatibility with proto1.
        if (field.getContainingType().getOptions().getMessageSetWireFormat()
            && (field.getType() == FieldDescriptor.Type.MESSAGE)
            && (field.isOptional())
            // object equality
            && (field.getExtensionScope() == field.getMessageType())) {
          generator.print(field.getMessageType().getFullName());
        } else {
          generator.print(field.getFullName());
        }
        generator.print("]");
      } else {
        if (field.getType() == FieldDescriptor.Type.GROUP) {
          // Groups must be serialized with their original capitalization.
          generator.print(field.getMessageType().getName());
        } else {
          generator.print(field.getName());
        }
      }

      if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
        if (singleLineMode) {
          generator.print(" { ");
        } else {
          generator.print(" {\n");
          generator.indent();
        }
      } else {
        generator.print(": ");
      }

      printFieldValue(field, value, generator);

      if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
        if (singleLineMode) {
          generator.print("} ");
        } else {
          generator.outdent();
          generator.print("}\n");
        }
      } else {
        if (singleLineMode) {
          generator.print(" ");
        } else {
          generator.print("\n");
        }
      }
    }

    private void printFieldValue(final FieldDescriptor field,
                                 final Object value,
                                 final TextGenerator generator)
                                 throws IOException {
      switch (field.getType()) {
        case INT32:
        case SINT32:
        case SFIXED32:
          generator.print(((Integer) value).toString());
          break;

        case INT64:
        case SINT64:
        case SFIXED64:
          generator.print(((Long) value).toString());
          break;

        case BOOL:
          generator.print(((Boolean) value).toString());
          break;

        case FLOAT:
          generator.print(((Float) value).toString());
          break;

        case DOUBLE:
          generator.print(((Double) value).toString());
          break;

        case UINT32:
        case FIXED32:
          generator.print(unsignedToString((Integer) value));
          break;

        case UINT64:
        case FIXED64:
          generator.print(unsignedToString((Long) value));
          break;

        case STRING:
          generator.print("\"");
          generator.print(escapeNonAscii ?
              escapeText((String) value) :
              (String) value);
          generator.print("\"");
          break;

        case BYTES:
          generator.print("\"");
          generator.print(escapeBytes((ByteString) value));
          generator.print("\"");
          break;

        case ENUM:
          generator.print(((EnumValueDescriptor) value).getName());
          break;

        case MESSAGE:
        case GROUP:
          print((Message) value, generator);
          break;
      }
    }

    private void printUnknownFields(final UnknownFieldSet unknownFields,
                                    final TextGenerator generator)
                                    throws IOException {
      for (Map.Entry<Integer, UnknownFieldSet.Field> entry :
               unknownFields.asMap().entrySet()) {
        final int number = entry.getKey();
        final UnknownFieldSet.Field field = entry.getValue();
        printUnknownField(number, WireFormat.WIRETYPE_VARINT,
            field.getVarintList(), generator);
        printUnknownField(number, WireFormat.WIRETYPE_FIXED32,
            field.getFixed32List(), generator);
        printUnknownField(number, WireFormat.WIRETYPE_FIXED64,
            field.getFixed64List(), generator);
        printUnknownField(number, WireFormat.WIRETYPE_LENGTH_DELIMITED,
            field.getLengthDelimitedList(), generator);
        for (final UnknownFieldSet value : field.getGroupList()) {
          generator.print(entry.getKey().toString());
          if (singleLineMode) {
            generator.print(" { ");
          } else {
            generator.print(" {\n");
            generator.indent();
          }
          printUnknownFields(value, generator);
          if (singleLineMode) {
            generator.print("} ");
          } else {
            generator.outdent();
            generator.print("}\n");
          }
        }
      }
    }

    private void printUnknownField(final int number,
                                   final int wireType,
                                   final List<?> values,
                                   final TextGenerator generator)
                                   throws IOException {
      for (final Object value : values) {
        generator.print(String.valueOf(number));
        generator.print(": ");
        printUnknownFieldValue(wireType, value, generator);
        generator.print(singleLineMode ? " " : "\n");
      }
    }
  }

  /** Convert an unsigned 32-bit integer to a string. */
  private static String unsignedToString(final int value) {
    if (value >= 0) {
      return Integer.toString(value);
    } else {
      return Long.toString(((long) value) & 0x00000000FFFFFFFFL);
    }
  }

  /** Convert an unsigned 64-bit integer to a string. */
  private static String unsignedToString(final long value) {
    if (value >= 0) {
      return Long.toString(value);
    } else {
      // Pull off the most-significant bit so that BigInteger doesn't think
      // the number is negative, then set it again using setBit().
      return BigInteger.valueOf(value & 0x7FFFFFFFFFFFFFFFL)
                       .setBit(63).toString();
    }
  }

  /**
   * An inner class for writing text to the output stream.
   */
  public static final class TextGenerator {
    private final Appendable output;
    private final StringBuilder indent = new StringBuilder();
    private boolean atStartOfLine = true;

    public TextGenerator(final Appendable output) {
      this.output = output;
    }

    /**
     * Indent text by two spaces.  After calling Indent(), two spaces will be
     * inserted at the beginning of each line of text.  Indent() may be called
     * multiple times to produce deeper indents.
     */
    public void indent() {
      indent.append("  ");
    }

    /**
     * Reduces the current indent level by two spaces, or crashes if the indent
     * level is zero.
     */
    public void outdent() {
      final int length = indent.length();
      if (length == 0) {
        throw new IllegalArgumentException(
            " Outdent() without matching Indent().");
      }
      indent.delete(length - 2, length);
    }

    /**
     * Print text to the output stream.
     */
    public void print(final CharSequence text) throws IOException {
      final int size = text.length();
      int pos = 0;

      for (int i = 0; i < size; i++) {
        if (text.charAt(i) == '\n') {
          write(text.subSequence(pos, size), i - pos + 1);
          pos = i + 1;
          atStartOfLine = true;
        }
      }
      write(text.subSequence(pos, size), size - pos);
    }

    private void write(final CharSequence data, final int size)
                       throws IOException {
      if (size == 0) {
        return;
      }
      if (atStartOfLine) {
        atStartOfLine = false;
        output.append(indent);
      }
      output.append(data);
    }
  }

  // =================================================================
  // Parsing

  /**
   * Represents a stream of tokens parsed from a {@code String}.
   *
   * <p>The Java standard library provides many classes that you might think
   * would be useful for implementing this, but aren't.  For example:
   *
   * <ul>
   * <li>{@code java.io.StreamTokenizer}:  This almost does what we want -- or,
   *   at least, something that would get us close to what we want -- except
   *   for one fatal flaw:  It automatically un-escapes strings using Java
   *   escape sequences, which do not include all the escape sequences we
   *   need to support (e.g. '\x').
   * <li>{@code java.util.Scanner}:  This seems like a great way at least to
   *   parse regular expressions out of a stream (so we wouldn't have to load
   *   the entire input into a single string before parsing).  Sadly,
   *   {@code Scanner} requires that tokens be delimited with some delimiter.
   *   Thus, although the text "foo:" should parse to two tokens ("foo" and
   *   ":"), {@code Scanner} would recognize it only as a single token.
   *   Furthermore, {@code Scanner} provides no way to inspect the contents
   *   of delimiters, making it impossible to keep track of line and column
   *   numbers.
   * </ul>
   *
   * <p>Luckily, Java's regular expression support does manage to be useful to
   * us.  (Barely:  We need {@code Matcher.usePattern()}, which is new in
   * Java 1.5.)  So, we can use that, at least.  Unfortunately, this implies
   * that we need to have the entire input in one contiguous string.
   */
  private static final class Tokenizer {
    private final CharSequence text;
    private final Matcher matcher;
    private String currentToken;

    // The character index within this.text at which the current token begins.
    private int pos = 0;

    // The line and column numbers of the current token.
    private int line = 0;
    private int column = 0;

    // The line and column numbers of the previous token (allows throwing
    // errors *after* consuming).
    private int previousLine = 0;
    private int previousColumn = 0;

    // We use possessive quantifiers (*+ and ++) because otherwise the Java
    // regex matcher has stack overflows on large inputs.
    private static final Pattern WHITESPACE =
      Pattern.compile("(\\s|(#.*$))++", Pattern.MULTILINE);
    private static final Pattern TOKEN = Pattern.compile(
      "[a-zA-Z_][0-9a-zA-Z_+-]*+|" +                // an identifier
      "[.]?[0-9+-][0-9a-zA-Z_.+-]*+|" +             // a number
      "\"([^\"\n\\\\]|\\\\.)*+(\"|\\\\?$)|" +       // a double-quoted string
      "\'([^\'\n\\\\]|\\\\.)*+(\'|\\\\?$)",         // a single-quoted string
      Pattern.MULTILINE);

    private static final Pattern DOUBLE_INFINITY = Pattern.compile(
      "-?inf(inity)?",
      Pattern.CASE_INSENSITIVE);
    private static final Pattern FLOAT_INFINITY = Pattern.compile(
      "-?inf(inity)?f?",
      Pattern.CASE_INSENSITIVE);
    private static final Pattern FLOAT_NAN = Pattern.compile(
      "nanf?",
      Pattern.CASE_INSENSITIVE);

    /** Construct a tokenizer that parses tokens from the given text. */
    private Tokenizer(final CharSequence text) {
      this.text = text;
      this.matcher = WHITESPACE.matcher(text);
      skipWhitespace();
      nextToken();
    }

    /** Are we at the end of the input? */
    public boolean atEnd() {
      return currentToken.length() == 0;
    }

    /** Advance to the next token. */
    public void nextToken() {
      previousLine = line;
      previousColumn = column;

      // Advance the line counter to the current position.
      while (pos < matcher.regionStart()) {
        if (text.charAt(pos) == '\n') {
          ++line;
          column = 0;
        } else {
          ++column;
        }
        ++pos;
      }

      // Match the next token.
      if (matcher.regionStart() == matcher.regionEnd()) {
        // EOF
        currentToken = "";
      } else {
        matcher.usePattern(TOKEN);
        if (matcher.lookingAt()) {
          currentToken = matcher.group();
          matcher.region(matcher.end(), matcher.regionEnd());
        } else {
          // Take one character.
          currentToken = String.valueOf(text.charAt(pos));
          matcher.region(pos + 1, matcher.regionEnd());
        }

        skipWhitespace();
      }
    }

    /**
     * Skip over any whitespace so that the matcher region starts at the next
     * token.
     */
    private void skipWhitespace() {
      matcher.usePattern(WHITESPACE);
      if (matcher.lookingAt()) {
        matcher.region(matcher.end(), matcher.regionEnd());
      }
    }

    /**
     * If the next token exactly matches {@code token}, consume it and return
     * {@code true}.  Otherwise, return {@code false} without doing anything.
     */
    public boolean tryConsume(final String token) {
      if (currentToken.equals(token)) {
        nextToken();
        return true;
      } else {
        return false;
      }
    }

    /**
     * If the next token exactly matches {@code token}, consume it.  Otherwise,
     * throw a {@link ParseException}.
     */
    public void consume(final String token) throws ParseException {
      if (!tryConsume(token)) {
        throw parseException("Expected \"" + token + "\".");
      }
    }

    /**
     * Returns {@code true} if the next token is an integer, but does
     * not consume it.
     */
    public boolean lookingAtInteger() {
      if (currentToken.length() == 0) {
        return false;
      }

      final char c = currentToken.charAt(0);
      return ('0' <= c && c <= '9') ||
             c == '-' || c == '+';
    }

    /**
     * If the next token is an identifier, consume it and return its value.
     * Otherwise, throw a {@link ParseException}.
     */
    public String consumeIdentifier() throws ParseException {
      for (int i = 0; i < currentToken.length(); i++) {
        final char c = currentToken.charAt(i);
        if (('a' <= c && c <= 'z') ||
            ('A' <= c && c <= 'Z') ||
            ('0' <= c && c <= '9') ||
            (c == '_') || (c == '.')) {
          // OK
        } else {
          throw parseException("Expected identifier.");
        }
      }

      final String result = currentToken;
      nextToken();
      return result;
    }

    /**
     * If the next token is a 32-bit signed integer, consume it and return its
     * value.  Otherwise, throw a {@link ParseException}.
     */
    public int consumeInt32() throws ParseException {
      try {
        final int result = parseInt32(currentToken);
        nextToken();
        return result;
      } catch (NumberFormatException e) {
        throw integerParseException(e);
      }
    }

    /**
     * If the next token is a 32-bit unsigned integer, consume it and return its
     * value.  Otherwise, throw a {@link ParseException}.
     */
    public int consumeUInt32() throws ParseException {
      try {
        final int result = parseUInt32(currentToken);
        nextToken();
        return result;
      } catch (NumberFormatException e) {
        throw integerParseException(e);
      }
    }

    /**
     * If the next token is a 64-bit signed integer, consume it and return its
     * value.  Otherwise, throw a {@link ParseException}.
     */
    public long consumeInt64() throws ParseException {
      try {
        final long result = parseInt64(currentToken);
        nextToken();
        return result;
      } catch (NumberFormatException e) {
        throw integerParseException(e);
      }
    }

    /**
     * If the next token is a 64-bit unsigned integer, consume it and return its
     * value.  Otherwise, throw a {@link ParseException}.
     */
    public long consumeUInt64() throws ParseException {
      try {
        final long result = parseUInt64(currentToken);
        nextToken();
        return result;
      } catch (NumberFormatException e) {
        throw integerParseException(e);
      }
    }

    /**
     * If the next token is a double, consume it and return its value.
     * Otherwise, throw a {@link ParseException}.
     */
    public double consumeDouble() throws ParseException {
      // We need to parse infinity and nan separately because
      // Double.parseDouble() does not accept "inf", "infinity", or "nan".
      if (DOUBLE_INFINITY.matcher(currentToken).matches()) {
        final boolean negative = currentToken.startsWith("-");
        nextToken();
        return negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
      }
      if (currentToken.equalsIgnoreCase("nan")) {
        nextToken();
        return Double.NaN;
      }
      try {
        final double result = Double.parseDouble(currentToken);
        nextToken();
        return result;
      } catch (NumberFormatException e) {
        throw floatParseException(e);
      }
    }

    /**
     * If the next token is a float, consume it and return its value.
     * Otherwise, throw a {@link ParseException}.
     */
    public float consumeFloat() throws ParseException {
      // We need to parse infinity and nan separately because
      // Float.parseFloat() does not accept "inf", "infinity", or "nan".
      if (FLOAT_INFINITY.matcher(currentToken).matches()) {
        final boolean negative = currentToken.startsWith("-");
        nextToken();
        return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
      }
      if (FLOAT_NAN.matcher(currentToken).matches()) {
        nextToken();
        return Float.NaN;
      }
      try {
        final float result = Float.parseFloat(currentToken);
        nextToken();
        return result;
      } catch (NumberFormatException e) {
        throw floatParseException(e);
      }
    }

    /**
     * If the next token is a boolean, consume it and return its value.
     * Otherwise, throw a {@link ParseException}.
     */
    public boolean consumeBoolean() throws ParseException {
      if (currentToken.equals("true") ||
          currentToken.equals("t") ||
          currentToken.equals("1")) {
        nextToken();
        return true;
      } else if (currentToken.equals("false") ||
                 currentToken.equals("f") ||
                 currentToken.equals("0")) {
        nextToken();
        return false;
      } else {
        throw parseException("Expected \"true\" or \"false\".");
      }
    }

    /**
     * If the next token is a string, consume it and return its (unescaped)
     * value.  Otherwise, throw a {@link ParseException}.
     */
    public String consumeString() throws ParseException {
      return consumeByteString().toStringUtf8();
    }

    /**
     * If the next token is a string, consume it, unescape it as a
     * {@link ByteString}, and return it.  Otherwise, throw a
     * {@link ParseException}.
     */
    public ByteString consumeByteString() throws ParseException {
      List<ByteString> list = new ArrayList<ByteString>();
      consumeByteString(list);
      while (currentToken.startsWith("'") || currentToken.startsWith("\"")) {
        consumeByteString(list);
      }
      return ByteString.copyFrom(list);
    }

    /**
     * Like {@link #consumeByteString()} but adds each token of the string to
     * the given list.  String literals (whether bytes or text) may come in
     * multiple adjacent tokens which are automatically concatenated, like in
     * C or Python.
     */
    private void consumeByteString(List<ByteString> list) throws ParseException {
      final char quote = currentToken.length() > 0 ? currentToken.charAt(0)
                                                   : '\0';
      if (quote != '\"' && quote != '\'') {
        throw parseException("Expected string.");
      }

      if (currentToken.length() < 2 ||
          currentToken.charAt(currentToken.length() - 1) != quote) {
        throw parseException("String missing ending quote.");
      }

      try {
        final String escaped =
            currentToken.substring(1, currentToken.length() - 1);
        final ByteString result = unescapeBytes(escaped);
        nextToken();
        list.add(result);
      } catch (InvalidEscapeSequenceException e) {
        throw parseException(e.getMessage());
      }
    }

    /**
     * Returns a {@link ParseException} with the current line and column
     * numbers in the description, suitable for throwing.
     */
    public ParseException parseException(final String description) {
      // Note:  People generally prefer one-based line and column numbers.
      return new ParseException(
        line + 1, column + 1, description);
    }

    /**
     * Returns a {@link ParseException} with the line and column numbers of
     * the previous token in the description, suitable for throwing.
     */
    public ParseException parseExceptionPreviousToken(
        final String description) {
      // Note:  People generally prefer one-based line and column numbers.
      return new ParseException(
        previousLine + 1, previousColumn + 1, description);
    }

    /**
     * Constructs an appropriate {@link ParseException} for the given
     * {@code NumberFormatException} when trying to parse an integer.
     */
    private ParseException integerParseException(
        final NumberFormatException e) {
      return parseException("Couldn't parse integer: " + e.getMessage());
    }

    /**
     * Constructs an appropriate {@link ParseException} for the given
     * {@code NumberFormatException} when trying to parse a float or double.
     */
    private ParseException floatParseException(final NumberFormatException e) {
      return parseException("Couldn't parse number: " + e.getMessage());
    }
  }

  /** Thrown when parsing an invalid text format message. */
  public static class ParseException extends IOException {
    private static final long serialVersionUID = 3196188060225107702L;

    private final int line;
    private final int column;

    /** Create a new instance, with -1 as the line and column numbers. */
    public ParseException(final String message) {
      this(-1, -1, message);
    }

    /**
     * Create a new instance
     *
     * @param line the line number where the parse error occurred,
     * using 1-offset.
     * @param column the column number where the parser error occurred,
     * using 1-offset.
     */
    public ParseException(final int line, final int column,
        final String message) {
      super(Integer.toString(line) + ":" + column + ": " + message);
      this.line = line;
      this.column = column;
    }

    /**
     * Return the line where the parse exception occurred, or -1 when
     * none is provided. The value is specified as 1-offset, so the first
     * line is line 1.
     */
    public int getLine() {
      return line;
    }

    /**
     * Return the column where the parse exception occurred, or -1 when
     * none is provided. The value is specified as 1-offset, so the first
     * line is line 1.
     */
    public int getColumn() {
      return column;
    }
  }

  /**
   * Parse a text-format message from {@code input} and merge the contents
   * into {@code builder}.
   */
  public static void merge(final Readable input,
                           final Message.Builder builder)
                           throws IOException {
    merge(input, ExtensionRegistry.getEmptyRegistry(), builder);
  }

  /**
   * Parse a text-format message from {@code input} and merge the contents
   * into {@code builder}.
   */
  public static void merge(final CharSequence input,
                           final Message.Builder builder)
                           throws ParseException {
    merge(input, ExtensionRegistry.getEmptyRegistry(), builder);
  }

  /**
   * Parse a text-format message from {@code input} and merge the contents
   * into {@code builder}.  Extensions will be recognized if they are
   * registered in {@code extensionRegistry}.
   */
  public static void merge(final Readable input,
                           final ExtensionRegistry extensionRegistry,
                           final Message.Builder builder)
                           throws IOException {
    // Read the entire input to a String then parse that.

    // If StreamTokenizer were not quite so crippled, or if there were a kind
    // of Reader that could read in chunks that match some particular regex,
    // or if we wanted to write a custom Reader to tokenize our stream, then
    // we would not have to read to one big String.  Alas, none of these is
    // the case.  Oh well.

    merge(toStringBuilder(input), extensionRegistry, builder);
  }

  private static final int BUFFER_SIZE = 4096;

  // TODO(chrisn): See if working around java.io.Reader#read(CharBuffer)
  // overhead is worthwhile
  private static StringBuilder toStringBuilder(final Readable input)
      throws IOException {
    final StringBuilder text = new StringBuilder();
    final CharBuffer buffer = CharBuffer.allocate(BUFFER_SIZE);
    while (true) {
      final int n = input.read(buffer);
      if (n == -1) {
        break;
      }
      buffer.flip();
      text.append(buffer, 0, n);
    }
    return text;
  }

  /**
   * Parse a text-format message from {@code input} and merge the contents
   * into {@code builder}.  Extensions will be recognized if they are
   * registered in {@code extensionRegistry}.
   */
  public static void merge(final CharSequence input,
                           final ExtensionRegistry extensionRegistry,
                           final Message.Builder builder)
                           throws ParseException {
    final Tokenizer tokenizer = new Tokenizer(input);

    while (!tokenizer.atEnd()) {
      mergeField(tokenizer, extensionRegistry, builder);
    }
  }

  /**
   * Parse a single field from {@code tokenizer} and merge it into
   * {@code builder}.
   */
  private static void mergeField(final Tokenizer tokenizer,
                                 final ExtensionRegistry extensionRegistry,
                                 final Message.Builder builder)
                                 throws ParseException {
    FieldDescriptor field;
    final Descriptor type = builder.getDescriptorForType();
    ExtensionRegistry.ExtensionInfo extension = null;

    if (tokenizer.tryConsume("[")) {
      // An extension.
      final StringBuilder name =
          new StringBuilder(tokenizer.consumeIdentifier());
      while (tokenizer.tryConsume(".")) {
        name.append('.');
        name.append(tokenizer.consumeIdentifier());
      }

      extension = extensionRegistry.findExtensionByName(name.toString());

      if (extension == null) {
        throw tokenizer.parseExceptionPreviousToken(
          "Extension \"" + name + "\" not found in the ExtensionRegistry.");
      } else if (extension.descriptor.getContainingType() != type) {
        throw tokenizer.parseExceptionPreviousToken(
          "Extension \"" + name + "\" does not extend message type \"" +
          type.getFullName() + "\".");
      }

      tokenizer.consume("]");

      field = extension.descriptor;
    } else {
      final String name = tokenizer.consumeIdentifier();
      field = type.findFieldByName(name);

      // Group names are expected to be capitalized as they appear in the
      // .proto file, which actually matches their type names, not their field
      // names.
      if (field == null) {
        // Explicitly specify US locale so that this code does not break when
        // executing in Turkey.
        final String lowerName = name.toLowerCase(Locale.US);
        field = type.findFieldByName(lowerName);
        // If the case-insensitive match worked but the field is NOT a group,
        if (field != null && field.getType() != FieldDescriptor.Type.GROUP) {
          field = null;
        }
      }
      // Again, special-case group names as described above.
      if (field != null && field.getType() == FieldDescriptor.Type.GROUP &&
          !field.getMessageType().getName().equals(name)) {
        field = null;
      }

      if (field == null) {
        throw tokenizer.parseExceptionPreviousToken(
          "Message type \"" + type.getFullName() +
          "\" has no field named \"" + name + "\".");
      }
    }

    Object value = null;

    if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
      tokenizer.tryConsume(":");  // optional

      final String endToken;
      if (tokenizer.tryConsume("<")) {
        endToken = ">";
      } else {
        tokenizer.consume("{");
        endToken = "}";
      }

      final Message.Builder subBuilder;
      if (extension == null) {
        subBuilder = builder.newBuilderForField(field);
      } else {
        subBuilder = extension.defaultInstance.newBuilderForType();
      }

      while (!tokenizer.tryConsume(endToken)) {
        if (tokenizer.atEnd()) {
          throw tokenizer.parseException(
            "Expected \"" + endToken + "\".");
        }
        mergeField(tokenizer, extensionRegistry, subBuilder);
      }

      value = subBuilder.buildPartial();

    } else {
      tokenizer.consume(":");

      switch (field.getType()) {
        case INT32:
        case SINT32:
        case SFIXED32:
          value = tokenizer.consumeInt32();
          break;

        case INT64:
        case SINT64:
        case SFIXED64:
          value = tokenizer.consumeInt64();
          break;

        case UINT32:
        case FIXED32:
          value = tokenizer.consumeUInt32();
          break;

        case UINT64:
        case FIXED64:
          value = tokenizer.consumeUInt64();
          break;

        case FLOAT:
          value = tokenizer.consumeFloat();
          break;

        case DOUBLE:
          value = tokenizer.consumeDouble();
          break;

        case BOOL:
          value = tokenizer.consumeBoolean();
          break;

        case STRING:
          value = tokenizer.consumeString();
          break;

        case BYTES:
          value = tokenizer.consumeByteString();
          break;

        case ENUM:
          final EnumDescriptor enumType = field.getEnumType();

          if (tokenizer.lookingAtInteger()) {
            final int number = tokenizer.consumeInt32();
            value = enumType.findValueByNumber(number);
            if (value == null) {
              throw tokenizer.parseExceptionPreviousToken(
                "Enum type \"" + enumType.getFullName() +
                "\" has no value with number " + number + '.');
            }
          } else {
            final String id = tokenizer.consumeIdentifier();
            value = enumType.findValueByName(id);
            if (value == null) {
              throw tokenizer.parseExceptionPreviousToken(
                "Enum type \"" + enumType.getFullName() +
                "\" has no value named \"" + id + "\".");
            }
          }

          break;

        case MESSAGE:
        case GROUP:
          throw new RuntimeException("Can't get here.");
      }
    }

    if (field.isRepeated()) {
      builder.addRepeatedField(field, value);
    } else {
      builder.setField(field, value);
    }
  }

  // =================================================================
  // Utility functions
  //
  // Some of these methods are package-private because Descriptors.java uses
  // them.

  /**
   * Escapes bytes in the format used in protocol buffer text format, which
   * is the same as the format used for C string literals.  All bytes
   * that are not printable 7-bit ASCII characters are escaped, as well as
   * backslash, single-quote, and double-quote characters.  Characters for
   * which no defined short-hand escape sequence is defined will be escaped
   * using 3-digit octal sequences.
   */
  static String escapeBytes(final ByteString input) {
    final StringBuilder builder = new StringBuilder(input.size());
    for (int i = 0; i < input.size(); i++) {
      final byte b = input.byteAt(i);
      switch (b) {
        // Java does not recognize \a or \v, apparently.
        case 0x07: builder.append("\\a" ); break;
        case '\b': builder.append("\\b" ); break;
        case '\f': builder.append("\\f" ); break;
        case '\n': builder.append("\\n" ); break;
        case '\r': builder.append("\\r" ); break;
        case '\t': builder.append("\\t" ); break;
        case 0x0b: builder.append("\\v" ); break;
        case '\\': builder.append("\\\\"); break;
        case '\'': builder.append("\\\'"); break;
        case '"' : builder.append("\\\""); break;
        default:
          // Note:  Bytes with the high-order bit set should be escaped.  Since
          //   bytes are signed, such bytes will compare less than 0x20, hence
          //   the following line is correct.
          if (b >= 0x20) {
            builder.append((char) b);
          } else {
            builder.append('\\');
            builder.append((char) ('0' + ((b >>> 6) & 3)));
            builder.append((char) ('0' + ((b >>> 3) & 7)));
            builder.append((char) ('0' + (b & 7)));
          }
          break;
      }
    }
    return builder.toString();
  }

  /**
   * Un-escape a byte sequence as escaped using
   * {@link #escapeBytes(ByteString)}.  Two-digit hex escapes (starting with
   * "\x") are also recognized.
   */
  static ByteString unescapeBytes(final CharSequence charString)
      throws InvalidEscapeSequenceException {
    // First convert the Java character sequence to UTF-8 bytes.
    ByteString input = ByteString.copyFromUtf8(charString.toString());
    // Then unescape certain byte sequences introduced by ASCII '\\'.  The valid
    // escapes can all be expressed with ASCII characters, so it is safe to
    // operate on bytes here.
    //
    // Unescaping the input byte array will result in a byte sequence that's no
    // longer than the input.  That's because each escape sequence is between
    // two and four bytes long and stands for a single byte.
    final byte[] result = new byte[input.size()];
    int pos = 0;
    for (int i = 0; i < input.size(); i++) {
      byte c = input.byteAt(i);
      if (c == '\\') {
        if (i + 1 < input.size()) {
          ++i;
          c = input.byteAt(i);
          if (isOctal(c)) {
            // Octal escape.
            int code = digitValue(c);
            if (i + 1 < input.size() && isOctal(input.byteAt(i + 1))) {
              ++i;
              code = code * 8 + digitValue(input.byteAt(i));
            }
            if (i + 1 < input.size() && isOctal(input.byteAt(i + 1))) {
              ++i;
              code = code * 8 + digitValue(input.byteAt(i));
            }
            // TODO: Check that 0 <= code && code <= 0xFF.
            result[pos++] = (byte)code;
          } else {
            switch (c) {
              case 'a' : result[pos++] = 0x07; break;
              case 'b' : result[pos++] = '\b'; break;
              case 'f' : result[pos++] = '\f'; break;
              case 'n' : result[pos++] = '\n'; break;
              case 'r' : result[pos++] = '\r'; break;
              case 't' : result[pos++] = '\t'; break;
              case 'v' : result[pos++] = 0x0b; break;
              case '\\': result[pos++] = '\\'; break;
              case '\'': result[pos++] = '\''; break;
              case '"' : result[pos++] = '\"'; break;

              case 'x':
                // hex escape
                int code = 0;
                if (i + 1 < input.size() && isHex(input.byteAt(i + 1))) {
                  ++i;
                  code = digitValue(input.byteAt(i));
                } else {
                  throw new InvalidEscapeSequenceException(
                      "Invalid escape sequence: '\\x' with no digits");
                }
                if (i + 1 < input.size() && isHex(input.byteAt(i + 1))) {
                  ++i;
                  code = code * 16 + digitValue(input.byteAt(i));
                }
                result[pos++] = (byte)code;
                break;

              default:
                throw new InvalidEscapeSequenceException(
                    "Invalid escape sequence: '\\" + (char)c + '\'');
            }
          }
        } else {
          throw new InvalidEscapeSequenceException(
              "Invalid escape sequence: '\\' at end of string.");
        }
      } else {
        result[pos++] = c;
      }
    }

    return ByteString.copyFrom(result, 0, pos);
  }

  /**
   * Thrown by {@link TextFormat#unescapeBytes} and
   * {@link TextFormat#unescapeText} when an invalid escape sequence is seen.
   */
  static class InvalidEscapeSequenceException extends IOException {
    private static final long serialVersionUID = -8164033650142593304L;

    InvalidEscapeSequenceException(final String description) {
      super(description);
    }
  }

  /**
   * Like {@link #escapeBytes(ByteString)}, but escapes a text string.
   * Non-ASCII characters are first encoded as UTF-8, then each byte is escaped
   * individually as a 3-digit octal escape.  Yes, it's weird.
   */
  static String escapeText(final String input) {
    return escapeBytes(ByteString.copyFromUtf8(input));
  }

  /**
   * Un-escape a text string as escaped using {@link #escapeText(String)}.
   * Two-digit hex escapes (starting with "\x") are also recognized.
   */
  static String unescapeText(final String input)
                             throws InvalidEscapeSequenceException {
    return unescapeBytes(input).toStringUtf8();
  }

  /** Is this an octal digit? */
  private static boolean isOctal(final byte c) {
    return '0' <= c && c <= '7';
  }

  /** Is this a hex digit? */
  private static boolean isHex(final byte c) {
    return ('0' <= c && c <= '9') ||
           ('a' <= c && c <= 'f') ||
           ('A' <= c && c <= 'F');
  }

  /**
   * Interpret a character as a digit (in any base up to 36) and return the
   * numeric value.  This is like {@code Character.digit()} but we don't accept
   * non-ASCII digits.
   */
  private static int digitValue(final byte c) {
    if ('0' <= c && c <= '9') {
      return c - '0';
    } else if ('a' <= c && c <= 'z') {
      return c - 'a' + 10;
    } else {
      return c - 'A' + 10;
    }
  }

  /**
   * Parse a 32-bit signed integer from the text.  Unlike the Java standard
   * {@code Integer.parseInt()}, this function recognizes the prefixes "0x"
   * and "0" to signify hexadecimal and octal numbers, respectively.
   */
  static int parseInt32(final String text) throws NumberFormatException {
    return (int) parseInteger(text, true, false);
  }

  /**
   * Parse a 32-bit unsigned integer from the text.  Unlike the Java standard
   * {@code Integer.parseInt()}, this function recognizes the prefixes "0x"
   * and "0" to signify hexadecimal and octal numbers, respectively.  The
   * result is coerced to a (signed) {@code int} when returned since Java has
   * no unsigned integer type.
   */
  static int parseUInt32(final String text) throws NumberFormatException {
    return (int) parseInteger(text, false, false);
  }

  /**
   * Parse a 64-bit signed integer from the text.  Unlike the Java standard
   * {@code Integer.parseInt()}, this function recognizes the prefixes "0x"
   * and "0" to signify hexadecimal and octal numbers, respectively.
   */
  static long parseInt64(final String text) throws NumberFormatException {
    return parseInteger(text, true, true);
  }

  /**
   * Parse a 64-bit unsigned integer from the text.  Unlike the Java standard
   * {@code Integer.parseInt()}, this function recognizes the prefixes "0x"
   * and "0" to signify hexadecimal and octal numbers, respectively.  The
   * result is coerced to a (signed) {@code long} when returned since Java has
   * no unsigned long type.
   */
  static long parseUInt64(final String text) throws NumberFormatException {
    return parseInteger(text, false, true);
  }

  private static long parseInteger(final String text,
                                   final boolean isSigned,
                                   final boolean isLong)
                                   throws NumberFormatException {
    int pos = 0;

    boolean negative = false;
    if (text.startsWith("-", pos)) {
      if (!isSigned) {
        throw new NumberFormatException("Number must be positive: " + text);
      }
      ++pos;
      negative = true;
    }

    int radix = 10;
    if (text.startsWith("0x", pos)) {
      pos += 2;
      radix = 16;
    } else if (text.startsWith("0", pos)) {
      radix = 8;
    }

    final String numberText = text.substring(pos);

    long result = 0;
    if (numberText.length() < 16) {
      // Can safely assume no overflow.
      result = Long.parseLong(numberText, radix);
      if (negative) {
        result = -result;
      }

      // Check bounds.
      // No need to check for 64-bit numbers since they'd have to be 16 chars
      // or longer to overflow.
      if (!isLong) {
        if (isSigned) {
          if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) {
            throw new NumberFormatException(
              "Number out of range for 32-bit signed integer: " + text);
          }
        } else {
          if (result >= (1L << 32) || result < 0) {
            throw new NumberFormatException(
              "Number out of range for 32-bit unsigned integer: " + text);
          }
        }
      }
    } else {
      BigInteger bigValue = new BigInteger(numberText, radix);
      if (negative) {
        bigValue = bigValue.negate();
      }

      // Check bounds.
      if (!isLong) {
        if (isSigned) {
          if (bigValue.bitLength() > 31) {
            throw new NumberFormatException(
              "Number out of range for 32-bit signed integer: " + text);
          }
        } else {
          if (bigValue.bitLength() > 32) {
            throw new NumberFormatException(
              "Number out of range for 32-bit unsigned integer: " + text);
          }
        }
      } else {
        if (isSigned) {
          if (bigValue.bitLength() > 63) {
            throw new NumberFormatException(
              "Number out of range for 64-bit signed integer: " + text);
          }
        } else {
          if (bigValue.bitLength() > 64) {
            throw new NumberFormatException(
              "Number out of range for 64-bit unsigned integer: " + text);
          }
        }
      }

      result = bigValue.longValue();
    }

    return result;
  }
}

自定义工具类MessageLiteToStringUtil.java

package com.test.util;

import java.io.IOException;

import com.google.protobuf.MessageLite;
import com.google.protobuf.MessageOrBuilder;
import com.test.util.TextFormat.Printer;
import com.test.util.TextFormat.TextGenerator;

public class MessageLiteToStringUtil {
    private static Printer singleLine = (new Printer()).setSingleLineMode(true)
            .setEscapeNonAscii(false);

    public  String toString(MessageLite lite) {
        if (lite == null) {
            return null;
        }
        final StringBuilder sb = new StringBuilder();
        try {
            singleLine.print((MessageOrBuilder) lite, new TextGenerator(sb));
        } catch (IOException e) {
        }
        return sb.toString();
    }
    private static MessageLiteToStringUtil instance=new MessageLiteToStringUtil();
    public static MessageLiteToStringUtil getInstance(){
        return instance;
    }
}

2.2自定义protocol相关

数据格式定义Test.proto

option java_package = "com.test.protocol";
message LocalStatus{
     required string serverIp = 1;
     required int32 port = 2;
     required int32 connectedCount = 3;
     required int32 loginConnectedCounst=5;
     required sint32 remainCount = 4;
}

使用命令把protoc-2.5.0中的protoc.exe拷贝到C:\Windows\System32下面,然后命令行切换到Test.proto文件所在的目录,执行下面的命令

protoc --java_out=. Test.proto 

生成的java文件如下:

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: Test.proto

package com.test.protocol;

public final class Test {
  private Test() {}
  public static void registerAllExtensions(
      com.google.protobuf.ExtensionRegistry registry) {
  }
  public interface LocalStatusOrBuilder
      extends com.google.protobuf.MessageOrBuilder {

    // required string serverIp = 1;
    /**
     * <code>required string serverIp = 1;</code>
     */
    boolean hasServerIp();
    /**
     * <code>required string serverIp = 1;</code>
     */
    java.lang.String getServerIp();
    /**
     * <code>required string serverIp = 1;</code>
     */
    com.google.protobuf.ByteString
        getServerIpBytes();

    // required int32 port = 2;
    /**
     * <code>required int32 port = 2;</code>
     */
    boolean hasPort();
    /**
     * <code>required int32 port = 2;</code>
     */
    int getPort();

    // required int32 connectedCount = 3;
    /**
     * <code>required int32 connectedCount = 3;</code>
     */
    boolean hasConnectedCount();
    /**
     * <code>required int32 connectedCount = 3;</code>
     */
    int getConnectedCount();

    // required int32 loginConnectedCounst = 5;
    /**
     * <code>required int32 loginConnectedCounst = 5;</code>
     */
    boolean hasLoginConnectedCounst();
    /**
     * <code>required int32 loginConnectedCounst = 5;</code>
     */
    int getLoginConnectedCounst();

    // required sint32 remainCount = 4;
    /**
     * <code>required sint32 remainCount = 4;</code>
     */
    boolean hasRemainCount();
    /**
     * <code>required sint32 remainCount = 4;</code>
     */
    int getRemainCount();
  }
  /**
   * Protobuf type {@code LocalStatus}
   */
  public static final class LocalStatus extends
      com.google.protobuf.GeneratedMessage
      implements LocalStatusOrBuilder {
    // Use LocalStatus.newBuilder() to construct.
    private LocalStatus(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
      super(builder);
      this.unknownFields = builder.getUnknownFields();
    }
    private LocalStatus(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }

    private static final LocalStatus defaultInstance;
    public static LocalStatus getDefaultInstance() {
      return defaultInstance;
    }

    public LocalStatus getDefaultInstanceForType() {
      return defaultInstance;
    }

    private final com.google.protobuf.UnknownFieldSet unknownFields;
    @java.lang.Override
    public final com.google.protobuf.UnknownFieldSet
        getUnknownFields() {
      return this.unknownFields;
    }
    private LocalStatus(
        com.google.protobuf.CodedInputStream input,
        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
        throws com.google.protobuf.InvalidProtocolBufferException {
      initFields();
      int mutable_bitField0_ = 0;
      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
          com.google.protobuf.UnknownFieldSet.newBuilder();
      try {
        boolean done = false;
        while (!done) {
          int tag = input.readTag();
          switch (tag) {
            case 0:
              done = true;
              break;
            default: {
              if (!parseUnknownField(input, unknownFields,
                                     extensionRegistry, tag)) {
                done = true;
              }
              break;
            }
            case 10: {
              bitField0_ |= 0x00000001;
              serverIp_ = input.readBytes();
              break;
            }
            case 16: {
              bitField0_ |= 0x00000002;
              port_ = input.readInt32();
              break;
            }
            case 24: {
              bitField0_ |= 0x00000004;
              connectedCount_ = input.readInt32();
              break;
            }
            case 32: {
              bitField0_ |= 0x00000010;
              remainCount_ = input.readSInt32();
              break;
            }
            case 40: {
              bitField0_ |= 0x00000008;
              loginConnectedCounst_ = input.readInt32();
              break;
            }
          }
        }
      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
        throw e.setUnfinishedMessage(this);
      } catch (java.io.IOException e) {
        throw new com.google.protobuf.InvalidProtocolBufferException(
            e.getMessage()).setUnfinishedMessage(this);
      } finally {
        this.unknownFields = unknownFields.build();
        makeExtensionsImmutable();
      }
    }
    public static final com.google.protobuf.Descriptors.Descriptor
        getDescriptor() {
      return com.test.protocol.Test.internal_static_LocalStatus_descriptor;
    }

    protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
        internalGetFieldAccessorTable() {
      return com.test.protocol.Test.internal_static_LocalStatus_fieldAccessorTable
          .ensureFieldAccessorsInitialized(
              com.test.protocol.Test.LocalStatus.class, com.test.protocol.Test.LocalStatus.Builder.class);
    }

    public static com.google.protobuf.Parser<LocalStatus> PARSER =
        new com.google.protobuf.AbstractParser<LocalStatus>() {
      public LocalStatus parsePartialFrom(
          com.google.protobuf.CodedInputStream input,
          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
          throws com.google.protobuf.InvalidProtocolBufferException {
        return new LocalStatus(input, extensionRegistry);
      }
    };

    @java.lang.Override
    public com.google.protobuf.Parser<LocalStatus> getParserForType() {
      return PARSER;
    }

    private int bitField0_;
    // required string serverIp = 1;
    public static final int SERVERIP_FIELD_NUMBER = 1;
    private java.lang.Object serverIp_;
    /**
     * <code>required string serverIp = 1;</code>
     */
    public boolean hasServerIp() {
      return ((bitField0_ & 0x00000001) == 0x00000001);
    }
    /**
     * <code>required string serverIp = 1;</code>
     */
    public java.lang.String getServerIp() {
      java.lang.Object ref = serverIp_;
      if (ref instanceof java.lang.String) {
        return (java.lang.String) ref;
      } else {
        com.google.protobuf.ByteString bs = 
            (com.google.protobuf.ByteString) ref;
        java.lang.String s = bs.toStringUtf8();
        if (bs.isValidUtf8()) {
          serverIp_ = s;
        }
        return s;
      }
    }
    /**
     * <code>required string serverIp = 1;</code>
     */
    public com.google.protobuf.ByteString
        getServerIpBytes() {
      java.lang.Object ref = serverIp_;
      if (ref instanceof java.lang.String) {
        com.google.protobuf.ByteString b = 
            com.google.protobuf.ByteString.copyFromUtf8(
                (java.lang.String) ref);
        serverIp_ = b;
        return b;
      } else {
        return (com.google.protobuf.ByteString) ref;
      }
    }

    // required int32 port = 2;
    public static final int PORT_FIELD_NUMBER = 2;
    private int port_;
    /**
     * <code>required int32 port = 2;</code>
     */
    public boolean hasPort() {
      return ((bitField0_ & 0x00000002) == 0x00000002);
    }
    /**
     * <code>required int32 port = 2;</code>
     */
    public int getPort() {
      return port_;
    }

    // required int32 connectedCount = 3;
    public static final int CONNECTEDCOUNT_FIELD_NUMBER = 3;
    private int connectedCount_;
    /**
     * <code>required int32 connectedCount = 3;</code>
     */
    public boolean hasConnectedCount() {
      return ((bitField0_ & 0x00000004) == 0x00000004);
    }
    /**
     * <code>required int32 connectedCount = 3;</code>
     */
    public int getConnectedCount() {
      return connectedCount_;
    }

    // required int32 loginConnectedCounst = 5;
    public static final int LOGINCONNECTEDCOUNST_FIELD_NUMBER = 5;
    private int loginConnectedCounst_;
    /**
     * <code>required int32 loginConnectedCounst = 5;</code>
     */
    public boolean hasLoginConnectedCounst() {
      return ((bitField0_ & 0x00000008) == 0x00000008);
    }
    /**
     * <code>required int32 loginConnectedCounst = 5;</code>
     */
    public int getLoginConnectedCounst() {
      return loginConnectedCounst_;
    }

    // required sint32 remainCount = 4;
    public static final int REMAINCOUNT_FIELD_NUMBER = 4;
    private int remainCount_;
    /**
     * <code>required sint32 remainCount = 4;</code>
     */
    public boolean hasRemainCount() {
      return ((bitField0_ & 0x00000010) == 0x00000010);
    }
    /**
     * <code>required sint32 remainCount = 4;</code>
     */
    public int getRemainCount() {
      return remainCount_;
    }

    private void initFields() {
      serverIp_ = "";
      port_ = 0;
      connectedCount_ = 0;
      loginConnectedCounst_ = 0;
      remainCount_ = 0;
    }
    private byte memoizedIsInitialized = -1;
    public final boolean isInitialized() {
      byte isInitialized = memoizedIsInitialized;
      if (isInitialized != -1) return isInitialized == 1;

      if (!hasServerIp()) {
        memoizedIsInitialized = 0;
        return false;
      }
      if (!hasPort()) {
        memoizedIsInitialized = 0;
        return false;
      }
      if (!hasConnectedCount()) {
        memoizedIsInitialized = 0;
        return false;
      }
      if (!hasLoginConnectedCounst()) {
        memoizedIsInitialized = 0;
        return false;
      }
      if (!hasRemainCount()) {
        memoizedIsInitialized = 0;
        return false;
      }
      memoizedIsInitialized = 1;
      return true;
    }

    public void writeTo(com.google.protobuf.CodedOutputStream output)
                        throws java.io.IOException {
      getSerializedSize();
      if (((bitField0_ & 0x00000001) == 0x00000001)) {
        output.writeBytes(1, getServerIpBytes());
      }
      if (((bitField0_ & 0x00000002) == 0x00000002)) {
        output.writeInt32(2, port_);
      }
      if (((bitField0_ & 0x00000004) == 0x00000004)) {
        output.writeInt32(3, connectedCount_);
      }
      if (((bitField0_ & 0x00000010) == 0x00000010)) {
        output.writeSInt32(4, remainCount_);
      }
      if (((bitField0_ & 0x00000008) == 0x00000008)) {
        output.writeInt32(5, loginConnectedCounst_);
      }
      getUnknownFields().writeTo(output);
    }

    private int memoizedSerializedSize = -1;
    public int getSerializedSize() {
      int size = memoizedSerializedSize;
      if (size != -1) return size;

      size = 0;
      if (((bitField0_ & 0x00000001) == 0x00000001)) {
        size += com.google.protobuf.CodedOutputStream
          .computeBytesSize(1, getServerIpBytes());
      }
      if (((bitField0_ & 0x00000002) == 0x00000002)) {
        size += com.google.protobuf.CodedOutputStream
          .computeInt32Size(2, port_);
      }
      if (((bitField0_ & 0x00000004) == 0x00000004)) {
        size += com.google.protobuf.CodedOutputStream
          .computeInt32Size(3, connectedCount_);
      }
      if (((bitField0_ & 0x00000010) == 0x00000010)) {
        size += com.google.protobuf.CodedOutputStream
          .computeSInt32Size(4, remainCount_);
      }
      if (((bitField0_ & 0x00000008) == 0x00000008)) {
        size += com.google.protobuf.CodedOutputStream
          .computeInt32Size(5, loginConnectedCounst_);
      }
      size += getUnknownFields().getSerializedSize();
      memoizedSerializedSize = size;
      return size;
    }

    private static final long serialVersionUID = 0L;
    @java.lang.Override
    protected java.lang.Object writeReplace()
        throws java.io.ObjectStreamException {
      return super.writeReplace();
    }

    public static com.test.protocol.Test.LocalStatus parseFrom(
        com.google.protobuf.ByteString data)
        throws com.google.protobuf.InvalidProtocolBufferException {
      return PARSER.parseFrom(data);
    }
    public static com.test.protocol.Test.LocalStatus parseFrom(
        com.google.protobuf.ByteString data,
        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
        throws com.google.protobuf.InvalidProtocolBufferException {
      return PARSER.parseFrom(data, extensionRegistry);
    }
    public static com.test.protocol.Test.LocalStatus parseFrom(byte[] data)
        throws com.google.protobuf.InvalidProtocolBufferException {
      return PARSER.parseFrom(data);
    }
    public static com.test.protocol.Test.LocalStatus parseFrom(
        byte[] data,
        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
        throws com.google.protobuf.InvalidProtocolBufferException {
      return PARSER.parseFrom(data, extensionRegistry);
    }
    public static com.test.protocol.Test.LocalStatus parseFrom(java.io.InputStream input)
        throws java.io.IOException {
      return PARSER.parseFrom(input);
    }
    public static com.test.protocol.Test.LocalStatus parseFrom(
        java.io.InputStream input,
        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
        throws java.io.IOException {
      return PARSER.parseFrom(input, extensionRegistry);
    }
    public static com.test.protocol.Test.LocalStatus parseDelimitedFrom(java.io.InputStream input)
        throws java.io.IOException {
      return PARSER.parseDelimitedFrom(input);
    }
    public static com.test.protocol.Test.LocalStatus parseDelimitedFrom(
        java.io.InputStream input,
        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
        throws java.io.IOException {
      return PARSER.parseDelimitedFrom(input, extensionRegistry);
    }
    public static com.test.protocol.Test.LocalStatus parseFrom(
        com.google.protobuf.CodedInputStream input)
        throws java.io.IOException {
      return PARSER.parseFrom(input);
    }
    public static com.test.protocol.Test.LocalStatus parseFrom(
        com.google.protobuf.CodedInputStream input,
        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
        throws java.io.IOException {
      return PARSER.parseFrom(input, extensionRegistry);
    }

    public static Builder newBuilder() { return Builder.create(); }
    public Builder newBuilderForType() { return newBuilder(); }
    public static Builder newBuilder(com.test.protocol.Test.LocalStatus prototype) {
      return newBuilder().mergeFrom(prototype);
    }
    public Builder toBuilder() { return newBuilder(this); }

    @java.lang.Override
    protected Builder newBuilderForType(
        com.google.protobuf.GeneratedMessage.BuilderParent parent) {
      Builder builder = new Builder(parent);
      return builder;
    }
    /**
     * Protobuf type {@code LocalStatus}
     */
    public static final class Builder extends
        com.google.protobuf.GeneratedMessage.Builder<Builder>
       implements com.test.protocol.Test.LocalStatusOrBuilder {
      public static final com.google.protobuf.Descriptors.Descriptor
          getDescriptor() {
        return com.test.protocol.Test.internal_static_LocalStatus_descriptor;
      }

      protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
          internalGetFieldAccessorTable() {
        return com.test.protocol.Test.internal_static_LocalStatus_fieldAccessorTable
            .ensureFieldAccessorsInitialized(
                com.test.protocol.Test.LocalStatus.class, com.test.protocol.Test.LocalStatus.Builder.class);
      }

      // Construct using com.test.protocol.Test.LocalStatus.newBuilder()
      private Builder() {
        maybeForceBuilderInitialization();
      }

      private Builder(
          com.google.protobuf.GeneratedMessage.BuilderParent parent) {
        super(parent);
        maybeForceBuilderInitialization();
      }
      private void maybeForceBuilderInitialization() {
        if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
        }
      }
      private static Builder create() {
        return new Builder();
      }

      public Builder clear() {
        super.clear();
        serverIp_ = "";
        bitField0_ = (bitField0_ & ~0x00000001);
        port_ = 0;
        bitField0_ = (bitField0_ & ~0x00000002);
        connectedCount_ = 0;
        bitField0_ = (bitField0_ & ~0x00000004);
        loginConnectedCounst_ = 0;
        bitField0_ = (bitField0_ & ~0x00000008);
        remainCount_ = 0;
        bitField0_ = (bitField0_ & ~0x00000010);
        return this;
      }

      public Builder clone() {
        return create().mergeFrom(buildPartial());
      }

      public com.google.protobuf.Descriptors.Descriptor
          getDescriptorForType() {
        return com.test.protocol.Test.internal_static_LocalStatus_descriptor;
      }

      public com.test.protocol.Test.LocalStatus getDefaultInstanceForType() {
        return com.test.protocol.Test.LocalStatus.getDefaultInstance();
      }

      public com.test.protocol.Test.LocalStatus build() {
        com.test.protocol.Test.LocalStatus result = buildPartial();
        if (!result.isInitialized()) {
          throw newUninitializedMessageException(result);
        }
        return result;
      }

      public com.test.protocol.Test.LocalStatus buildPartial() {
        com.test.protocol.Test.LocalStatus result = new com.test.protocol.Test.LocalStatus(this);
        int from_bitField0_ = bitField0_;
        int to_bitField0_ = 0;
        if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
          to_bitField0_ |= 0x00000001;
        }
        result.serverIp_ = serverIp_;
        if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
          to_bitField0_ |= 0x00000002;
        }
        result.port_ = port_;
        if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
          to_bitField0_ |= 0x00000004;
        }
        result.connectedCount_ = connectedCount_;
        if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
          to_bitField0_ |= 0x00000008;
        }
        result.loginConnectedCounst_ = loginConnectedCounst_;
        if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
          to_bitField0_ |= 0x00000010;
        }
        result.remainCount_ = remainCount_;
        result.bitField0_ = to_bitField0_;
        onBuilt();
        return result;
      }

      public Builder mergeFrom(com.google.protobuf.Message other) {
        if (other instanceof com.test.protocol.Test.LocalStatus) {
          return mergeFrom((com.test.protocol.Test.LocalStatus)other);
        } else {
          super.mergeFrom(other);
          return this;
        }
      }

      public Builder mergeFrom(com.test.protocol.Test.LocalStatus other) {
        if (other == com.test.protocol.Test.LocalStatus.getDefaultInstance()) return this;
        if (other.hasServerIp()) {
          bitField0_ |= 0x00000001;
          serverIp_ = other.serverIp_;
          onChanged();
        }
        if (other.hasPort()) {
          setPort(other.getPort());
        }
        if (other.hasConnectedCount()) {
          setConnectedCount(other.getConnectedCount());
        }
        if (other.hasLoginConnectedCounst()) {
          setLoginConnectedCounst(other.getLoginConnectedCounst());
        }
        if (other.hasRemainCount()) {
          setRemainCount(other.getRemainCount());
        }
        this.mergeUnknownFields(other.getUnknownFields());
        return this;
      }

      public final boolean isInitialized() {
        if (!hasServerIp()) {

          return false;
        }
        if (!hasPort()) {

          return false;
        }
        if (!hasConnectedCount()) {

          return false;
        }
        if (!hasLoginConnectedCounst()) {

          return false;
        }
        if (!hasRemainCount()) {

          return false;
        }
        return true;
      }

      public Builder mergeFrom(
          com.google.protobuf.CodedInputStream input,
          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
          throws java.io.IOException {
        com.test.protocol.Test.LocalStatus parsedMessage = null;
        try {
          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
          parsedMessage = (com.test.protocol.Test.LocalStatus) e.getUnfinishedMessage();
          throw e;
        } finally {
          if (parsedMessage != null) {
            mergeFrom(parsedMessage);
          }
        }
        return this;
      }
      private int bitField0_;

      // required string serverIp = 1;
      private java.lang.Object serverIp_ = "";
      /**
       * <code>required string serverIp = 1;</code>
       */
      public boolean hasServerIp() {
        return ((bitField0_ & 0x00000001) == 0x00000001);
      }
      /**
       * <code>required string serverIp = 1;</code>
       */
      public java.lang.String getServerIp() {
        java.lang.Object ref = serverIp_;
        if (!(ref instanceof java.lang.String)) {
          java.lang.String s = ((com.google.protobuf.ByteString) ref)
              .toStringUtf8();
          serverIp_ = s;
          return s;
        } else {
          return (java.lang.String) ref;
        }
      }
      /**
       * <code>required string serverIp = 1;</code>
       */
      public com.google.protobuf.ByteString
          getServerIpBytes() {
        java.lang.Object ref = serverIp_;
        if (ref instanceof String) {
          com.google.protobuf.ByteString b = 
              com.google.protobuf.ByteString.copyFromUtf8(
                  (java.lang.String) ref);
          serverIp_ = b;
          return b;
        } else {
          return (com.google.protobuf.ByteString) ref;
        }
      }
      /**
       * <code>required string serverIp = 1;</code>
       */
      public Builder setServerIp(
          java.lang.String value) {
        if (value == null) {
    throw new NullPointerException();
  }
  bitField0_ |= 0x00000001;
        serverIp_ = value;
        onChanged();
        return this;
      }
      /**
       * <code>required string serverIp = 1;</code>
       */
      public Builder clearServerIp() {
        bitField0_ = (bitField0_ & ~0x00000001);
        serverIp_ = getDefaultInstance().getServerIp();
        onChanged();
        return this;
      }
      /**
       * <code>required string serverIp = 1;</code>
       */
      public Builder setServerIpBytes(
          com.google.protobuf.ByteString value) {
        if (value == null) {
    throw new NullPointerException();
  }
  bitField0_ |= 0x00000001;
        serverIp_ = value;
        onChanged();
        return this;
      }

      // required int32 port = 2;
      private int port_ ;
      /**
       * <code>required int32 port = 2;</code>
       */
      public boolean hasPort() {
        return ((bitField0_ & 0x00000002) == 0x00000002);
      }
      /**
       * <code>required int32 port = 2;</code>
       */
      public int getPort() {
        return port_;
      }
      /**
       * <code>required int32 port = 2;</code>
       */
      public Builder setPort(int value) {
        bitField0_ |= 0x00000002;
        port_ = value;
        onChanged();
        return this;
      }
      /**
       * <code>required int32 port = 2;</code>
       */
      public Builder clearPort() {
        bitField0_ = (bitField0_ & ~0x00000002);
        port_ = 0;
        onChanged();
        return this;
      }

      // required int32 connectedCount = 3;
      private int connectedCount_ ;
      /**
       * <code>required int32 connectedCount = 3;</code>
       */
      public boolean hasConnectedCount() {
        return ((bitField0_ & 0x00000004) == 0x00000004);
      }
      /**
       * <code>required int32 connectedCount = 3;</code>
       */
      public int getConnectedCount() {
        return connectedCount_;
      }
      /**
       * <code>required int32 connectedCount = 3;</code>
       */
      public Builder setConnectedCount(int value) {
        bitField0_ |= 0x00000004;
        connectedCount_ = value;
        onChanged();
        return this;
      }
      /**
       * <code>required int32 connectedCount = 3;</code>
       */
      public Builder clearConnectedCount() {
        bitField0_ = (bitField0_ & ~0x00000004);
        connectedCount_ = 0;
        onChanged();
        return this;
      }

      // required int32 loginConnectedCounst = 5;
      private int loginConnectedCounst_ ;
      /**
       * <code>required int32 loginConnectedCounst = 5;</code>
       */
      public boolean hasLoginConnectedCounst() {
        return ((bitField0_ & 0x00000008) == 0x00000008);
      }
      /**
       * <code>required int32 loginConnectedCounst = 5;</code>
       */
      public int getLoginConnectedCounst() {
        return loginConnectedCounst_;
      }
      /**
       * <code>required int32 loginConnectedCounst = 5;</code>
       */
      public Builder setLoginConnectedCounst(int value) {
        bitField0_ |= 0x00000008;
        loginConnectedCounst_ = value;
        onChanged();
        return this;
      }
      /**
       * <code>required int32 loginConnectedCounst = 5;</code>
       */
      public Builder clearLoginConnectedCounst() {
        bitField0_ = (bitField0_ & ~0x00000008);
        loginConnectedCounst_ = 0;
        onChanged();
        return this;
      }

      // required sint32 remainCount = 4;
      private int remainCount_ ;
      /**
       * <code>required sint32 remainCount = 4;</code>
       */
      public boolean hasRemainCount() {
        return ((bitField0_ & 0x00000010) == 0x00000010);
      }
      /**
       * <code>required sint32 remainCount = 4;</code>
       */
      public int getRemainCount() {
        return remainCount_;
      }
      /**
       * <code>required sint32 remainCount = 4;</code>
       */
      public Builder setRemainCount(int value) {
        bitField0_ |= 0x00000010;
        remainCount_ = value;
        onChanged();
        return this;
      }
      /**
       * <code>required sint32 remainCount = 4;</code>
       */
      public Builder clearRemainCount() {
        bitField0_ = (bitField0_ & ~0x00000010);
        remainCount_ = 0;
        onChanged();
        return this;
      }

      // @@protoc_insertion_point(builder_scope:LocalStatus)
    }

    static {
      defaultInstance = new LocalStatus(true);
      defaultInstance.initFields();
    }

    // @@protoc_insertion_point(class_scope:LocalStatus)
  }

  private static com.google.protobuf.Descriptors.Descriptor
    internal_static_LocalStatus_descriptor;
  private static
    com.google.protobuf.GeneratedMessage.FieldAccessorTable
      internal_static_LocalStatus_fieldAccessorTable;

  public static com.google.protobuf.Descriptors.FileDescriptor
      getDescriptor() {
    return descriptor;
  }
  private static com.google.protobuf.Descriptors.FileDescriptor
      descriptor;
  static {
    java.lang.String[] descriptorData = {
      "\n\nTest.proto\"x\n\013LocalStatus\022\020\n\010serverIp\030" +
      "\001 \002(\t\022\014\n\004port\030\002 \002(\005\022\026\n\016connectedCount\030\003 " +
      "\002(\005\022\034\n\024loginConnectedCounst\030\005 \002(\005\022\023\n\013rem" +
      "ainCount\030\004 \002(\021B\023\n\021com.test.protocol"
    };
    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
      new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
        public com.google.protobuf.ExtensionRegistry assignDescriptors(
            com.google.protobuf.Descriptors.FileDescriptor root) {
          descriptor = root;
          internal_static_LocalStatus_descriptor =
            getDescriptor().getMessageTypes().get(0);
          internal_static_LocalStatus_fieldAccessorTable = new
            com.google.protobuf.GeneratedMessage.FieldAccessorTable(
              internal_static_LocalStatus_descriptor,
              new java.lang.String[] { "ServerIp", "Port", "ConnectedCount", "LoginConnectedCounst", "RemainCount", });
          return null;
        }
      };
    com.google.protobuf.Descriptors.FileDescriptor
      .internalBuildGeneratedFileFrom(descriptorData,
        new com.google.protobuf.Descriptors.FileDescriptor[] {
        }, assigner);
  }

  // @@protoc_insertion_point(outer_class_scope)
}

2.3测试代码

package com.test.protocol;

import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import com.test.util.MessageLiteToStringUtil;

/**
 * 测试protocol输出
 * @author Administrator
 *
 */
public class ProtocolTest {
    public static ApplicationContext cxt = new ClassPathXmlApplicationContext("spring.xml");
    private static MessageLiteToStringUtil printer =MessageLiteToStringUtil.getInstance();
    /**
     * @param args
     * @throws ParseException 
     */
    public static void main(String[] args) throws ParseException {
        Test.LocalStatus.Builder localStatus = Test.LocalStatus.newBuilder() ;
        localStatus.setServerIp("192.168.2.73");
        localStatus.setPort(2181);
        localStatus.setConnectedCount(5);
        localStatus.setLoginConnectedCounst(6);
        localStatus.setRemainCount(1000);
        System.out.println("输出结果:"+printer.toString(localStatus.build()));

    }
}

输出结果如下
这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值