Index: third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java |
diff --git a/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d13ff0ed04d727b9ced56bc352493ade999d1d5a |
--- /dev/null |
+++ b/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java |
@@ -0,0 +1,1661 @@ |
+// Protocol Buffers - Google's data interchange format |
+// Copyright 2008 Google Inc. All rights reserved. |
+// https://developers.google.com/protocol-buffers/ |
+// |
+// Redistribution and use in source and binary forms, with or without |
+// modification, are permitted provided that the following conditions are |
+// met: |
+// |
+// * Redistributions of source code must retain the above copyright |
+// notice, this list of conditions and the following disclaimer. |
+// * Redistributions in binary form must reproduce the above |
+// copyright notice, this list of conditions and the following disclaimer |
+// in the documentation and/or other materials provided with the |
+// distribution. |
+// * Neither the name of Google Inc. nor the names of its |
+// contributors may be used to endorse or promote products derived from |
+// this software without specific prior written permission. |
+// |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ |
+package com.google.protobuf.util; |
+ |
+import com.google.common.io.BaseEncoding; |
+import com.google.gson.Gson; |
+import com.google.gson.JsonArray; |
+import com.google.gson.JsonElement; |
+import com.google.gson.JsonNull; |
+import com.google.gson.JsonObject; |
+import com.google.gson.JsonParser; |
+import com.google.gson.JsonPrimitive; |
+import com.google.gson.stream.JsonReader; |
+import com.google.protobuf.Any; |
+import com.google.protobuf.BoolValue; |
+import com.google.protobuf.ByteString; |
+import com.google.protobuf.BytesValue; |
+import com.google.protobuf.Descriptors.Descriptor; |
+import com.google.protobuf.Descriptors.EnumDescriptor; |
+import com.google.protobuf.Descriptors.EnumValueDescriptor; |
+import com.google.protobuf.Descriptors.FieldDescriptor; |
+import com.google.protobuf.Descriptors.FileDescriptor; |
+import com.google.protobuf.DoubleValue; |
+import com.google.protobuf.Duration; |
+import com.google.protobuf.DynamicMessage; |
+import com.google.protobuf.FieldMask; |
+import com.google.protobuf.FloatValue; |
+import com.google.protobuf.Int32Value; |
+import com.google.protobuf.Int64Value; |
+import com.google.protobuf.InvalidProtocolBufferException; |
+import com.google.protobuf.ListValue; |
+import com.google.protobuf.Message; |
+import com.google.protobuf.MessageOrBuilder; |
+import com.google.protobuf.StringValue; |
+import com.google.protobuf.Struct; |
+import com.google.protobuf.Timestamp; |
+import com.google.protobuf.UInt32Value; |
+import com.google.protobuf.UInt64Value; |
+import com.google.protobuf.Value; |
+ |
+import java.io.IOException; |
+import java.io.Reader; |
+import java.io.StringReader; |
+import java.math.BigDecimal; |
+import java.math.BigInteger; |
+import java.text.ParseException; |
+import java.util.Collections; |
+import java.util.HashMap; |
+import java.util.HashSet; |
+import java.util.List; |
+import java.util.Map; |
+import java.util.Set; |
+import java.util.TreeMap; |
+import java.util.logging.Logger; |
+ |
+/** |
+ * Utility classes to convert protobuf messages to/from JSON format. The JSON |
+ * format follows Proto3 JSON specification and only proto3 features are |
+ * supported. Proto2 only features (e.g., extensions and unknown fields) will |
+ * be discarded in the conversion. That is, when converting proto2 messages |
+ * to JSON format, extensions and unknown fields will be treated as if they |
+ * do not exist. This applies to proto2 messages embedded in proto3 messages |
+ * as well. |
+ */ |
+public class JsonFormat { |
+ private static final Logger logger = |
+ Logger.getLogger(JsonFormat.class.getName()); |
+ |
+ private JsonFormat() {} |
+ |
+ /** |
+ * Creates a {@link Printer} with default configurations. |
+ */ |
+ public static Printer printer() { |
+ return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false); |
+ } |
+ |
+ /** |
+ * A Printer converts protobuf message to JSON format. |
+ */ |
+ public static class Printer { |
+ private final TypeRegistry registry; |
+ private final boolean includingDefaultValueFields; |
+ private final boolean preservingProtoFieldNames; |
+ |
+ private Printer( |
+ TypeRegistry registry, |
+ boolean includingDefaultValueFields, |
+ boolean preservingProtoFieldNames) { |
+ this.registry = registry; |
+ this.includingDefaultValueFields = includingDefaultValueFields; |
+ this.preservingProtoFieldNames = preservingProtoFieldNames; |
+ } |
+ |
+ /** |
+ * Creates a new {@link Printer} using the given registry. The new Printer |
+ * clones all other configurations from the current {@link Printer}. |
+ * |
+ * @throws IllegalArgumentException if a registry is already set. |
+ */ |
+ public Printer usingTypeRegistry(TypeRegistry registry) { |
+ if (this.registry != TypeRegistry.getEmptyTypeRegistry()) { |
+ throw new IllegalArgumentException("Only one registry is allowed."); |
+ } |
+ return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames); |
+ } |
+ |
+ /** |
+ * Creates a new {@link Printer} that will also print fields set to their |
+ * defaults. Empty repeated fields and map fields will be printed as well. |
+ * The new Printer clones all other configurations from the current |
+ * {@link Printer}. |
+ */ |
+ public Printer includingDefaultValueFields() { |
+ return new Printer(registry, true, preservingProtoFieldNames); |
+ } |
+ |
+ /** |
+ * Creates a new {@link Printer} that is configured to use the original proto |
+ * field names as defined in the .proto file rather than converting them to |
+ * lowerCamelCase. The new Printer clones all other configurations from the |
+ * current {@link Printer}. |
+ */ |
+ public Printer preservingProtoFieldNames() { |
+ return new Printer(registry, includingDefaultValueFields, true); |
+ } |
+ |
+ /** |
+ * Converts a protobuf message to JSON format. |
+ * |
+ * @throws InvalidProtocolBufferException if the message contains Any types |
+ * that can't be resolved. |
+ * @throws IOException if writing to the output fails. |
+ */ |
+ public void appendTo(MessageOrBuilder message, Appendable output) |
+ throws IOException { |
+ // TODO(xiaofeng): Investigate the allocation overhead and optimize for |
+ // mobile. |
+ new PrinterImpl(registry, includingDefaultValueFields, preservingProtoFieldNames, output) |
+ .print(message); |
+ } |
+ |
+ /** |
+ * Converts a protobuf message to JSON format. Throws exceptions if there |
+ * are unknown Any types in the message. |
+ */ |
+ public String print(MessageOrBuilder message) |
+ throws InvalidProtocolBufferException { |
+ try { |
+ StringBuilder builder = new StringBuilder(); |
+ appendTo(message, builder); |
+ return builder.toString(); |
+ } catch (InvalidProtocolBufferException e) { |
+ throw e; |
+ } catch (IOException e) { |
+ // Unexpected IOException. |
+ throw new IllegalStateException(e); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Creates a {@link Parser} with default configuration. |
+ */ |
+ public static Parser parser() { |
+ return new Parser(TypeRegistry.getEmptyTypeRegistry()); |
+ } |
+ |
+ /** |
+ * A Parser parses JSON to protobuf message. |
+ */ |
+ public static class Parser { |
+ private final TypeRegistry registry; |
+ |
+ private Parser(TypeRegistry registry) { |
+ this.registry = registry; |
+ } |
+ |
+ /** |
+ * Creates a new {@link Parser} using the given registry. The new Parser |
+ * clones all other configurations from this Parser. |
+ * |
+ * @throws IllegalArgumentException if a registry is already set. |
+ */ |
+ public Parser usingTypeRegistry(TypeRegistry registry) { |
+ if (this.registry != TypeRegistry.getEmptyTypeRegistry()) { |
+ throw new IllegalArgumentException("Only one registry is allowed."); |
+ } |
+ return new Parser(registry); |
+ } |
+ |
+ /** |
+ * Parses from JSON into a protobuf message. |
+ * |
+ * @throws InvalidProtocolBufferException if the input is not valid JSON |
+ * format or there are unknown fields in the input. |
+ */ |
+ public void merge(String json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ // TODO(xiaofeng): Investigate the allocation overhead and optimize for |
+ // mobile. |
+ new ParserImpl(registry).merge(json, builder); |
+ } |
+ |
+ /** |
+ * Parses from JSON into a protobuf message. |
+ * |
+ * @throws InvalidProtocolBufferException if the input is not valid JSON |
+ * format or there are unknown fields in the input. |
+ * @throws IOException if reading from the input throws. |
+ */ |
+ public void merge(Reader json, Message.Builder builder) |
+ throws IOException { |
+ // TODO(xiaofeng): Investigate the allocation overhead and optimize for |
+ // mobile. |
+ new ParserImpl(registry).merge(json, builder); |
+ } |
+ } |
+ |
+ /** |
+ * A TypeRegistry is used to resolve Any messages in the JSON conversion. |
+ * You must provide a TypeRegistry containing all message types used in |
+ * Any message fields, or the JSON conversion will fail because data |
+ * in Any message fields is unrecognizable. You don't need to supply a |
+ * TypeRegistry if you don't use Any message fields. |
+ */ |
+ public static class TypeRegistry { |
+ private static class EmptyTypeRegistryHolder { |
+ private static final TypeRegistry EMPTY = new TypeRegistry( |
+ Collections.<String, Descriptor>emptyMap()); |
+ } |
+ |
+ public static TypeRegistry getEmptyTypeRegistry() { |
+ return EmptyTypeRegistryHolder.EMPTY; |
+ } |
+ |
+ public static Builder newBuilder() { |
+ return new Builder(); |
+ } |
+ |
+ /** |
+ * Find a type by its full name. Returns null if it cannot be found in |
+ * this {@link TypeRegistry}. |
+ */ |
+ public Descriptor find(String name) { |
+ return types.get(name); |
+ } |
+ |
+ private final Map<String, Descriptor> types; |
+ |
+ private TypeRegistry(Map<String, Descriptor> types) { |
+ this.types = types; |
+ } |
+ |
+ /** |
+ * A Builder is used to build {@link TypeRegistry}. |
+ */ |
+ public static class Builder { |
+ private Builder() {} |
+ |
+ /** |
+ * Adds a message type and all types defined in the same .proto file as |
+ * well as all transitively imported .proto files to this {@link Builder}. |
+ */ |
+ public Builder add(Descriptor messageType) { |
+ if (types == null) { |
+ throw new IllegalStateException( |
+ "A TypeRegistry.Builer can only be used once."); |
+ } |
+ addFile(messageType.getFile()); |
+ return this; |
+ } |
+ |
+ /** |
+ * Adds message types and all types defined in the same .proto file as |
+ * well as all transitively imported .proto files to this {@link Builder}. |
+ */ |
+ public Builder add(Iterable<Descriptor> messageTypes) { |
+ if (types == null) { |
+ throw new IllegalStateException( |
+ "A TypeRegistry.Builer can only be used once."); |
+ } |
+ for (Descriptor type : messageTypes) { |
+ addFile(type.getFile()); |
+ } |
+ return this; |
+ } |
+ |
+ /** |
+ * Builds a {@link TypeRegistry}. This method can only be called once for |
+ * one Builder. |
+ */ |
+ public TypeRegistry build() { |
+ TypeRegistry result = new TypeRegistry(types); |
+ // Make sure the built {@link TypeRegistry} is immutable. |
+ types = null; |
+ return result; |
+ } |
+ |
+ private void addFile(FileDescriptor file) { |
+ // Skip the file if it's already added. |
+ if (!files.add(file.getFullName())) { |
+ return; |
+ } |
+ for (FileDescriptor dependency : file.getDependencies()) { |
+ addFile(dependency); |
+ } |
+ for (Descriptor message : file.getMessageTypes()) { |
+ addMessage(message); |
+ } |
+ } |
+ |
+ private void addMessage(Descriptor message) { |
+ for (Descriptor nestedType : message.getNestedTypes()) { |
+ addMessage(nestedType); |
+ } |
+ |
+ if (types.containsKey(message.getFullName())) { |
+ logger.warning("Type " + message.getFullName() |
+ + " is added multiple times."); |
+ return; |
+ } |
+ |
+ types.put(message.getFullName(), message); |
+ } |
+ |
+ private final Set<String> files = new HashSet<String>(); |
+ private Map<String, Descriptor> types = |
+ new HashMap<String, Descriptor>(); |
+ } |
+ } |
+ |
+ /** |
+ * A TextGenerator adds indentation when writing formatted text. |
+ */ |
+ private static final class TextGenerator { |
+ private final Appendable output; |
+ private final StringBuilder indent = new StringBuilder(); |
+ private boolean atStartOfLine = true; |
+ |
+ private 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 < 2) { |
+ 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, i + 1)); |
+ pos = i + 1; |
+ atStartOfLine = true; |
+ } |
+ } |
+ write(text.subSequence(pos, size)); |
+ } |
+ |
+ private void write(final CharSequence data) throws IOException { |
+ if (data.length() == 0) { |
+ return; |
+ } |
+ if (atStartOfLine) { |
+ atStartOfLine = false; |
+ output.append(indent); |
+ } |
+ output.append(data); |
+ } |
+ } |
+ |
+ /** |
+ * A Printer converts protobuf messages to JSON format. |
+ */ |
+ private static final class PrinterImpl { |
+ private final TypeRegistry registry; |
+ private final boolean includingDefaultValueFields; |
+ private final boolean preservingProtoFieldNames; |
+ private final TextGenerator generator; |
+ // We use Gson to help handle string escapes. |
+ private final Gson gson; |
+ |
+ private static class GsonHolder { |
+ private static final Gson DEFAULT_GSON = new Gson(); |
+ } |
+ |
+ PrinterImpl( |
+ TypeRegistry registry, |
+ boolean includingDefaultValueFields, |
+ boolean preservingProtoFieldNames, |
+ Appendable jsonOutput) { |
+ this.registry = registry; |
+ this.includingDefaultValueFields = includingDefaultValueFields; |
+ this.preservingProtoFieldNames = preservingProtoFieldNames; |
+ this.generator = new TextGenerator(jsonOutput); |
+ this.gson = GsonHolder.DEFAULT_GSON; |
+ } |
+ |
+ void print(MessageOrBuilder message) throws IOException { |
+ WellKnownTypePrinter specialPrinter = wellKnownTypePrinters.get( |
+ message.getDescriptorForType().getFullName()); |
+ if (specialPrinter != null) { |
+ specialPrinter.print(this, message); |
+ return; |
+ } |
+ print(message, null); |
+ } |
+ |
+ private interface WellKnownTypePrinter { |
+ void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException; |
+ } |
+ |
+ private static final Map<String, WellKnownTypePrinter> |
+ wellKnownTypePrinters = buildWellKnownTypePrinters(); |
+ |
+ private static Map<String, WellKnownTypePrinter> |
+ buildWellKnownTypePrinters() { |
+ Map<String, WellKnownTypePrinter> printers = |
+ new HashMap<String, WellKnownTypePrinter>(); |
+ // Special-case Any. |
+ printers.put(Any.getDescriptor().getFullName(), |
+ new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException { |
+ printer.printAny(message); |
+ } |
+ }); |
+ // Special-case wrapper types. |
+ WellKnownTypePrinter wrappersPrinter = new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException { |
+ printer.printWrapper(message); |
+ |
+ } |
+ }; |
+ printers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); |
+ printers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); |
+ printers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); |
+ printers.put(Int64Value.getDescriptor().getFullName(), wrappersPrinter); |
+ printers.put(UInt64Value.getDescriptor().getFullName(), wrappersPrinter); |
+ printers.put(StringValue.getDescriptor().getFullName(), wrappersPrinter); |
+ printers.put(BytesValue.getDescriptor().getFullName(), wrappersPrinter); |
+ printers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); |
+ printers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); |
+ // Special-case Timestamp. |
+ printers.put(Timestamp.getDescriptor().getFullName(), |
+ new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException { |
+ printer.printTimestamp(message); |
+ } |
+ }); |
+ // Special-case Duration. |
+ printers.put(Duration.getDescriptor().getFullName(), |
+ new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException { |
+ printer.printDuration(message); |
+ } |
+ }); |
+ // Special-case FieldMask. |
+ printers.put(FieldMask.getDescriptor().getFullName(), |
+ new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException { |
+ printer.printFieldMask(message); |
+ } |
+ }); |
+ // Special-case Struct. |
+ printers.put(Struct.getDescriptor().getFullName(), |
+ new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException { |
+ printer.printStruct(message); |
+ } |
+ }); |
+ // Special-case Value. |
+ printers.put(Value.getDescriptor().getFullName(), |
+ new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException { |
+ printer.printValue(message); |
+ } |
+ }); |
+ // Special-case ListValue. |
+ printers.put(ListValue.getDescriptor().getFullName(), |
+ new WellKnownTypePrinter() { |
+ @Override |
+ public void print(PrinterImpl printer, MessageOrBuilder message) |
+ throws IOException { |
+ printer.printListValue(message); |
+ } |
+ }); |
+ return printers; |
+ } |
+ |
+ /** Prints google.protobuf.Any */ |
+ private void printAny(MessageOrBuilder message) throws IOException { |
+ Descriptor descriptor = message.getDescriptorForType(); |
+ FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); |
+ FieldDescriptor valueField = descriptor.findFieldByName("value"); |
+ // Validates type of the message. Note that we can't just cast the message |
+ // to com.google.protobuf.Any because it might be a DynamicMessage. |
+ if (typeUrlField == null || valueField == null |
+ || typeUrlField.getType() != FieldDescriptor.Type.STRING |
+ || valueField.getType() != FieldDescriptor.Type.BYTES) { |
+ throw new InvalidProtocolBufferException("Invalid Any type."); |
+ } |
+ String typeUrl = (String) message.getField(typeUrlField); |
+ String typeName = getTypeName(typeUrl); |
+ Descriptor type = registry.find(typeName); |
+ if (type == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Cannot find type for url: " + typeUrl); |
+ } |
+ ByteString content = (ByteString) message.getField(valueField); |
+ Message contentMessage = DynamicMessage.getDefaultInstance(type) |
+ .getParserForType().parseFrom(content); |
+ WellKnownTypePrinter printer = wellKnownTypePrinters.get(typeName); |
+ if (printer != null) { |
+ // If the type is one of the well-known types, we use a special |
+ // formatting. |
+ generator.print("{\n"); |
+ generator.indent(); |
+ generator.print("\"@type\": " + gson.toJson(typeUrl) + ",\n"); |
+ generator.print("\"value\": "); |
+ printer.print(this, contentMessage); |
+ generator.print("\n"); |
+ generator.outdent(); |
+ generator.print("}"); |
+ } else { |
+ // Print the content message instead (with a "@type" field added). |
+ print(contentMessage, typeUrl); |
+ } |
+ } |
+ |
+ /** Prints wrapper types (e.g., google.protobuf.Int32Value) */ |
+ private void printWrapper(MessageOrBuilder message) throws IOException { |
+ Descriptor descriptor = message.getDescriptorForType(); |
+ FieldDescriptor valueField = descriptor.findFieldByName("value"); |
+ if (valueField == null) { |
+ throw new InvalidProtocolBufferException("Invalid Wrapper type."); |
+ } |
+ // When formatting wrapper types, we just print its value field instead of |
+ // the whole message. |
+ printSingleFieldValue(valueField, message.getField(valueField)); |
+ } |
+ |
+ private ByteString toByteString(MessageOrBuilder message) { |
+ if (message instanceof Message) { |
+ return ((Message) message).toByteString(); |
+ } else { |
+ return ((Message.Builder) message).build().toByteString(); |
+ } |
+ } |
+ |
+ /** Prints google.protobuf.Timestamp */ |
+ private void printTimestamp(MessageOrBuilder message) throws IOException { |
+ Timestamp value = Timestamp.parseFrom(toByteString(message)); |
+ generator.print("\"" + TimeUtil.toString(value) + "\""); |
+ } |
+ |
+ /** Prints google.protobuf.Duration */ |
+ private void printDuration(MessageOrBuilder message) throws IOException { |
+ Duration value = Duration.parseFrom(toByteString(message)); |
+ generator.print("\"" + TimeUtil.toString(value) + "\""); |
+ |
+ } |
+ |
+ /** Prints google.protobuf.FieldMask */ |
+ private void printFieldMask(MessageOrBuilder message) throws IOException { |
+ FieldMask value = FieldMask.parseFrom(toByteString(message)); |
+ generator.print("\"" + FieldMaskUtil.toString(value) + "\""); |
+ } |
+ |
+ /** Prints google.protobuf.Struct */ |
+ private void printStruct(MessageOrBuilder message) throws IOException { |
+ Descriptor descriptor = message.getDescriptorForType(); |
+ FieldDescriptor field = descriptor.findFieldByName("fields"); |
+ if (field == null) { |
+ throw new InvalidProtocolBufferException("Invalid Struct type."); |
+ } |
+ // Struct is formatted as a map object. |
+ printMapFieldValue(field, message.getField(field)); |
+ } |
+ |
+ /** Prints google.protobuf.Value */ |
+ private void printValue(MessageOrBuilder message) throws IOException { |
+ // For a Value message, only the value of the field is formatted. |
+ Map<FieldDescriptor, Object> fields = message.getAllFields(); |
+ if (fields.isEmpty()) { |
+ // No value set. |
+ generator.print("null"); |
+ return; |
+ } |
+ // A Value message can only have at most one field set (it only contains |
+ // an oneof). |
+ if (fields.size() != 1) { |
+ throw new InvalidProtocolBufferException("Invalid Value type."); |
+ } |
+ for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { |
+ printSingleFieldValue(entry.getKey(), entry.getValue()); |
+ } |
+ } |
+ |
+ /** Prints google.protobuf.ListValue */ |
+ private void printListValue(MessageOrBuilder message) throws IOException { |
+ Descriptor descriptor = message.getDescriptorForType(); |
+ FieldDescriptor field = descriptor.findFieldByName("values"); |
+ if (field == null) { |
+ throw new InvalidProtocolBufferException("Invalid ListValue type."); |
+ } |
+ printRepeatedFieldValue(field, message.getField(field)); |
+ } |
+ |
+ /** Prints a regular message with an optional type URL. */ |
+ private void print(MessageOrBuilder message, String typeUrl) |
+ throws IOException { |
+ generator.print("{\n"); |
+ generator.indent(); |
+ |
+ boolean printedField = false; |
+ if (typeUrl != null) { |
+ generator.print("\"@type\": " + gson.toJson(typeUrl)); |
+ printedField = true; |
+ } |
+ Map<FieldDescriptor, Object> fieldsToPrint = null; |
+ if (includingDefaultValueFields) { |
+ fieldsToPrint = new TreeMap<FieldDescriptor, Object>(); |
+ for (FieldDescriptor field : message.getDescriptorForType().getFields()) { |
+ if (field.isOptional() |
+ && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE |
+ && !message.hasField(field)) { |
+ // Always skip empty optional message fields. If not we will recurse indefinitely if |
+ // a message has itself as a sub-field. |
+ continue; |
+ } |
+ fieldsToPrint.put(field, message.getField(field)); |
+ } |
+ } else { |
+ fieldsToPrint = message.getAllFields(); |
+ } |
+ for (Map.Entry<FieldDescriptor, Object> field : fieldsToPrint.entrySet()) { |
+ if (printedField) { |
+ // Add line-endings for the previous field. |
+ generator.print(",\n"); |
+ } else { |
+ printedField = true; |
+ } |
+ printField(field.getKey(), field.getValue()); |
+ } |
+ |
+ // Add line-endings for the last field. |
+ if (printedField) { |
+ generator.print("\n"); |
+ } |
+ generator.outdent(); |
+ generator.print("}"); |
+ } |
+ |
+ private void printField(FieldDescriptor field, Object value) |
+ throws IOException { |
+ if (preservingProtoFieldNames) { |
+ generator.print("\"" + field.getName() + "\": "); |
+ } else { |
+ generator.print("\"" + field.getJsonName() + "\": "); |
+ } |
+ if (field.isMapField()) { |
+ printMapFieldValue(field, value); |
+ } else if (field.isRepeated()) { |
+ printRepeatedFieldValue(field, value); |
+ } else { |
+ printSingleFieldValue(field, value); |
+ } |
+ } |
+ |
+ @SuppressWarnings("rawtypes") |
+ private void printRepeatedFieldValue(FieldDescriptor field, Object value) |
+ throws IOException { |
+ generator.print("["); |
+ boolean printedElement = false; |
+ for (Object element : (List) value) { |
+ if (printedElement) { |
+ generator.print(", "); |
+ } else { |
+ printedElement = true; |
+ } |
+ printSingleFieldValue(field, element); |
+ } |
+ generator.print("]"); |
+ } |
+ |
+ @SuppressWarnings("rawtypes") |
+ private void printMapFieldValue(FieldDescriptor field, Object value) |
+ throws IOException { |
+ Descriptor type = field.getMessageType(); |
+ FieldDescriptor keyField = type.findFieldByName("key"); |
+ FieldDescriptor valueField = type.findFieldByName("value"); |
+ if (keyField == null || valueField == null) { |
+ throw new InvalidProtocolBufferException("Invalid map field."); |
+ } |
+ generator.print("{\n"); |
+ generator.indent(); |
+ boolean printedElement = false; |
+ for (Object element : (List) value) { |
+ Message entry = (Message) element; |
+ Object entryKey = entry.getField(keyField); |
+ Object entryValue = entry.getField(valueField); |
+ if (printedElement) { |
+ generator.print(",\n"); |
+ } else { |
+ printedElement = true; |
+ } |
+ // Key fields are always double-quoted. |
+ printSingleFieldValue(keyField, entryKey, true); |
+ generator.print(": "); |
+ printSingleFieldValue(valueField, entryValue); |
+ } |
+ if (printedElement) { |
+ generator.print("\n"); |
+ } |
+ generator.outdent(); |
+ generator.print("}"); |
+ } |
+ |
+ private void printSingleFieldValue(FieldDescriptor field, Object value) |
+ throws IOException { |
+ printSingleFieldValue(field, value, false); |
+ } |
+ |
+ /** |
+ * Prints a field's value in JSON format. |
+ * |
+ * @param alwaysWithQuotes whether to always add double-quotes to primitive |
+ * types. |
+ */ |
+ private void printSingleFieldValue( |
+ final FieldDescriptor field, final Object value, |
+ boolean alwaysWithQuotes) throws IOException { |
+ switch (field.getType()) { |
+ case INT32: |
+ case SINT32: |
+ case SFIXED32: |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ generator.print(((Integer) value).toString()); |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ break; |
+ |
+ case INT64: |
+ case SINT64: |
+ case SFIXED64: |
+ generator.print("\"" + ((Long) value).toString() + "\""); |
+ break; |
+ |
+ case BOOL: |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ if (((Boolean) value).booleanValue()) { |
+ generator.print("true"); |
+ } else { |
+ generator.print("false"); |
+ } |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ break; |
+ |
+ case FLOAT: |
+ Float floatValue = (Float) value; |
+ if (floatValue.isNaN()) { |
+ generator.print("\"NaN\""); |
+ } else if (floatValue.isInfinite()) { |
+ if (floatValue < 0) { |
+ generator.print("\"-Infinity\""); |
+ } else { |
+ generator.print("\"Infinity\""); |
+ } |
+ } else { |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ generator.print(floatValue.toString()); |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ } |
+ break; |
+ |
+ case DOUBLE: |
+ Double doubleValue = (Double) value; |
+ if (doubleValue.isNaN()) { |
+ generator.print("\"NaN\""); |
+ } else if (doubleValue.isInfinite()) { |
+ if (doubleValue < 0) { |
+ generator.print("\"-Infinity\""); |
+ } else { |
+ generator.print("\"Infinity\""); |
+ } |
+ } else { |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ generator.print(doubleValue.toString()); |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ } |
+ break; |
+ |
+ case UINT32: |
+ case FIXED32: |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ generator.print(unsignedToString((Integer) value)); |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ break; |
+ |
+ case UINT64: |
+ case FIXED64: |
+ generator.print("\"" + unsignedToString((Long) value) + "\""); |
+ break; |
+ |
+ case STRING: |
+ generator.print(gson.toJson(value)); |
+ break; |
+ |
+ case BYTES: |
+ generator.print("\""); |
+ generator.print( |
+ BaseEncoding.base64().encode(((ByteString) value).toByteArray())); |
+ generator.print("\""); |
+ break; |
+ |
+ case ENUM: |
+ // Special-case google.protobuf.NullValue (it's an Enum). |
+ if (field.getEnumType().getFullName().equals( |
+ "google.protobuf.NullValue")) { |
+ // No matter what value it contains, we always print it as "null". |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ generator.print("null"); |
+ if (alwaysWithQuotes) { |
+ generator.print("\""); |
+ } |
+ } else { |
+ if (((EnumValueDescriptor) value).getIndex() == -1) { |
+ generator.print( |
+ String.valueOf(((EnumValueDescriptor) value).getNumber())); |
+ } else { |
+ generator.print( |
+ "\"" + ((EnumValueDescriptor) value).getName() + "\""); |
+ } |
+ } |
+ break; |
+ |
+ case MESSAGE: |
+ case GROUP: |
+ print((Message) value); |
+ break; |
+ } |
+ } |
+ } |
+ |
+ /** 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(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 & Long.MAX_VALUE) |
+ .setBit(Long.SIZE - 1).toString(); |
+ } |
+ } |
+ |
+ private static final String TYPE_URL_PREFIX = "type.googleapis.com"; |
+ |
+ private static String getTypeName(String typeUrl) |
+ throws InvalidProtocolBufferException { |
+ String[] parts = typeUrl.split("/"); |
+ if (parts.length != 2 || !parts[0].equals(TYPE_URL_PREFIX)) { |
+ throw new InvalidProtocolBufferException( |
+ "Invalid type url found: " + typeUrl); |
+ } |
+ return parts[1]; |
+ } |
+ |
+ private static class ParserImpl { |
+ private final TypeRegistry registry; |
+ private final JsonParser jsonParser; |
+ |
+ ParserImpl(TypeRegistry registry) { |
+ this.registry = registry; |
+ this.jsonParser = new JsonParser(); |
+ } |
+ |
+ void merge(Reader json, Message.Builder builder) |
+ throws IOException { |
+ JsonReader reader = new JsonReader(json); |
+ reader.setLenient(false); |
+ merge(jsonParser.parse(reader), builder); |
+ } |
+ |
+ void merge(String json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ try { |
+ JsonReader reader = new JsonReader(new StringReader(json)); |
+ reader.setLenient(false); |
+ merge(jsonParser.parse(reader), builder); |
+ } catch (InvalidProtocolBufferException e) { |
+ throw e; |
+ } catch (Exception e) { |
+ // We convert all exceptions from JSON parsing to our own exceptions. |
+ throw new InvalidProtocolBufferException(e.getMessage()); |
+ } |
+ } |
+ |
+ private interface WellKnownTypeParser { |
+ void merge(ParserImpl parser, JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException; |
+ } |
+ |
+ private static final Map<String, WellKnownTypeParser> wellKnownTypeParsers = |
+ buildWellKnownTypeParsers(); |
+ |
+ private static Map<String, WellKnownTypeParser> |
+ buildWellKnownTypeParsers() { |
+ Map<String, WellKnownTypeParser> parsers = |
+ new HashMap<String, WellKnownTypeParser>(); |
+ // Special-case Any. |
+ parsers.put(Any.getDescriptor().getFullName(), new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ parser.mergeAny(json, builder); |
+ } |
+ }); |
+ // Special-case wrapper types. |
+ WellKnownTypeParser wrappersPrinter = new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ parser.mergeWrapper(json, builder); |
+ } |
+ }; |
+ parsers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); |
+ parsers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); |
+ parsers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); |
+ parsers.put(Int64Value.getDescriptor().getFullName(), wrappersPrinter); |
+ parsers.put(UInt64Value.getDescriptor().getFullName(), wrappersPrinter); |
+ parsers.put(StringValue.getDescriptor().getFullName(), wrappersPrinter); |
+ parsers.put(BytesValue.getDescriptor().getFullName(), wrappersPrinter); |
+ parsers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); |
+ parsers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); |
+ // Special-case Timestamp. |
+ parsers.put(Timestamp.getDescriptor().getFullName(), |
+ new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ parser.mergeTimestamp(json, builder); |
+ } |
+ }); |
+ // Special-case Duration. |
+ parsers.put(Duration.getDescriptor().getFullName(), |
+ new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ parser.mergeDuration(json, builder); |
+ } |
+ }); |
+ // Special-case FieldMask. |
+ parsers.put(FieldMask.getDescriptor().getFullName(), |
+ new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ parser.mergeFieldMask(json, builder); |
+ } |
+ }); |
+ // Special-case Struct. |
+ parsers.put(Struct.getDescriptor().getFullName(), |
+ new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ parser.mergeStruct(json, builder); |
+ } |
+ }); |
+ // Special-case Value. |
+ parsers.put(Value.getDescriptor().getFullName(), |
+ new WellKnownTypeParser() { |
+ @Override |
+ public void merge(ParserImpl parser, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ parser.mergeValue(json, builder); |
+ } |
+ }); |
+ return parsers; |
+ } |
+ |
+ private void merge(JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ WellKnownTypeParser specialParser = wellKnownTypeParsers.get( |
+ builder.getDescriptorForType().getFullName()); |
+ if (specialParser != null) { |
+ specialParser.merge(this, json, builder); |
+ return; |
+ } |
+ mergeMessage(json, builder, false); |
+ } |
+ |
+ // Maps from camel-case field names to FieldDescriptor. |
+ private final Map<Descriptor, Map<String, FieldDescriptor>> fieldNameMaps = |
+ new HashMap<Descriptor, Map<String, FieldDescriptor>>(); |
+ |
+ private Map<String, FieldDescriptor> getFieldNameMap( |
+ Descriptor descriptor) { |
+ if (!fieldNameMaps.containsKey(descriptor)) { |
+ Map<String, FieldDescriptor> fieldNameMap = |
+ new HashMap<String, FieldDescriptor>(); |
+ for (FieldDescriptor field : descriptor.getFields()) { |
+ fieldNameMap.put(field.getName(), field); |
+ fieldNameMap.put(field.getJsonName(), field); |
+ } |
+ fieldNameMaps.put(descriptor, fieldNameMap); |
+ return fieldNameMap; |
+ } |
+ return fieldNameMaps.get(descriptor); |
+ } |
+ |
+ private void mergeMessage(JsonElement json, Message.Builder builder, |
+ boolean skipTypeUrl) throws InvalidProtocolBufferException { |
+ if (!(json instanceof JsonObject)) { |
+ throw new InvalidProtocolBufferException( |
+ "Expect message object but got: " + json); |
+ } |
+ JsonObject object = (JsonObject) json; |
+ Map<String, FieldDescriptor> fieldNameMap = |
+ getFieldNameMap(builder.getDescriptorForType()); |
+ for (Map.Entry<String, JsonElement> entry : object.entrySet()) { |
+ if (skipTypeUrl && entry.getKey().equals("@type")) { |
+ continue; |
+ } |
+ FieldDescriptor field = fieldNameMap.get(entry.getKey()); |
+ if (field == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Cannot find field: " + entry.getKey() + " in message " |
+ + builder.getDescriptorForType().getFullName()); |
+ } |
+ mergeField(field, entry.getValue(), builder); |
+ } |
+ } |
+ |
+ private void mergeAny(JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ Descriptor descriptor = builder.getDescriptorForType(); |
+ FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); |
+ FieldDescriptor valueField = descriptor.findFieldByName("value"); |
+ // Validates type of the message. Note that we can't just cast the message |
+ // to com.google.protobuf.Any because it might be a DynamicMessage. |
+ if (typeUrlField == null || valueField == null |
+ || typeUrlField.getType() != FieldDescriptor.Type.STRING |
+ || valueField.getType() != FieldDescriptor.Type.BYTES) { |
+ throw new InvalidProtocolBufferException("Invalid Any type."); |
+ } |
+ |
+ if (!(json instanceof JsonObject)) { |
+ throw new InvalidProtocolBufferException( |
+ "Expect message object but got: " + json); |
+ } |
+ JsonObject object = (JsonObject) json; |
+ JsonElement typeUrlElement = object.get("@type"); |
+ if (typeUrlElement == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Missing type url when parsing: " + json); |
+ } |
+ String typeUrl = typeUrlElement.getAsString(); |
+ Descriptor contentType = registry.find(getTypeName(typeUrl)); |
+ if (contentType == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Cannot resolve type: " + typeUrl); |
+ } |
+ builder.setField(typeUrlField, typeUrl); |
+ Message.Builder contentBuilder = |
+ DynamicMessage.getDefaultInstance(contentType).newBuilderForType(); |
+ WellKnownTypeParser specialParser = |
+ wellKnownTypeParsers.get(contentType.getFullName()); |
+ if (specialParser != null) { |
+ JsonElement value = object.get("value"); |
+ if (value != null) { |
+ specialParser.merge(this, value, contentBuilder); |
+ } |
+ } else { |
+ mergeMessage(json, contentBuilder, true); |
+ } |
+ builder.setField(valueField, contentBuilder.build().toByteString()); |
+ } |
+ |
+ private void mergeFieldMask(JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ FieldMask value = FieldMaskUtil.fromString(json.getAsString()); |
+ builder.mergeFrom(value.toByteString()); |
+ } |
+ |
+ private void mergeTimestamp(JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ try { |
+ Timestamp value = TimeUtil.parseTimestamp(json.getAsString()); |
+ builder.mergeFrom(value.toByteString()); |
+ } catch (ParseException e) { |
+ throw new InvalidProtocolBufferException( |
+ "Failed to parse timestamp: " + json); |
+ } |
+ } |
+ |
+ private void mergeDuration(JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ try { |
+ Duration value = TimeUtil.parseDuration(json.getAsString()); |
+ builder.mergeFrom(value.toByteString()); |
+ } catch (ParseException e) { |
+ throw new InvalidProtocolBufferException( |
+ "Failed to parse duration: " + json); |
+ } |
+ } |
+ |
+ private void mergeStruct(JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ Descriptor descriptor = builder.getDescriptorForType(); |
+ FieldDescriptor field = descriptor.findFieldByName("fields"); |
+ if (field == null) { |
+ throw new InvalidProtocolBufferException("Invalid Struct type."); |
+ } |
+ mergeMapField(field, json, builder); |
+ } |
+ |
+ private void mergeValue(JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ Descriptor type = builder.getDescriptorForType(); |
+ if (json instanceof JsonPrimitive) { |
+ JsonPrimitive primitive = (JsonPrimitive) json; |
+ if (primitive.isBoolean()) { |
+ builder.setField(type.findFieldByName("bool_value"), |
+ primitive.getAsBoolean()); |
+ } else if (primitive.isNumber()) { |
+ builder.setField(type.findFieldByName("number_value"), |
+ primitive.getAsDouble()); |
+ } else { |
+ builder.setField(type.findFieldByName("string_value"), |
+ primitive.getAsString()); |
+ } |
+ } else if (json instanceof JsonObject) { |
+ FieldDescriptor field = type.findFieldByName("struct_value"); |
+ Message.Builder structBuilder = builder.newBuilderForField(field); |
+ merge(json, structBuilder); |
+ builder.setField(field, structBuilder.build()); |
+ } else if (json instanceof JsonArray) { |
+ FieldDescriptor field = type.findFieldByName("list_value"); |
+ Message.Builder listBuilder = builder.newBuilderForField(field); |
+ FieldDescriptor listField = |
+ listBuilder.getDescriptorForType().findFieldByName("values"); |
+ mergeRepeatedField(listField, json, listBuilder); |
+ builder.setField(field, listBuilder.build()); |
+ } else { |
+ throw new IllegalStateException("Unexpected json data: " + json); |
+ } |
+ } |
+ |
+ private void mergeWrapper(JsonElement json, Message.Builder builder) |
+ throws InvalidProtocolBufferException { |
+ Descriptor type = builder.getDescriptorForType(); |
+ FieldDescriptor field = type.findFieldByName("value"); |
+ if (field == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Invalid wrapper type: " + type.getFullName()); |
+ } |
+ builder.setField(field, parseFieldValue(field, json, builder)); |
+ } |
+ |
+ private void mergeField(FieldDescriptor field, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ if (field.isRepeated()) { |
+ if (builder.getRepeatedFieldCount(field) > 0) { |
+ throw new InvalidProtocolBufferException( |
+ "Field " + field.getFullName() + " has already been set."); |
+ } |
+ } else { |
+ if (builder.hasField(field)) { |
+ throw new InvalidProtocolBufferException( |
+ "Field " + field.getFullName() + " has already been set."); |
+ } |
+ if (field.getContainingOneof() != null |
+ && builder.getOneofFieldDescriptor(field.getContainingOneof()) != null) { |
+ FieldDescriptor other = builder.getOneofFieldDescriptor(field.getContainingOneof()); |
+ throw new InvalidProtocolBufferException( |
+ "Cannot set field " + field.getFullName() + " because another field " |
+ + other.getFullName() + " belonging to the same oneof has already been set "); |
+ } |
+ } |
+ if (field.isRepeated() && json instanceof JsonNull) { |
+ // We allow "null" as value for all field types and treat it as if the |
+ // field is not present. |
+ return; |
+ } |
+ if (field.isMapField()) { |
+ mergeMapField(field, json, builder); |
+ } else if (field.isRepeated()) { |
+ mergeRepeatedField(field, json, builder); |
+ } else { |
+ Object value = parseFieldValue(field, json, builder); |
+ if (value != null) { |
+ builder.setField(field, value); |
+ } |
+ } |
+ } |
+ |
+ private void mergeMapField(FieldDescriptor field, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ if (!(json instanceof JsonObject)) { |
+ throw new InvalidProtocolBufferException( |
+ "Expect a map object but found: " + json); |
+ } |
+ Descriptor type = field.getMessageType(); |
+ FieldDescriptor keyField = type.findFieldByName("key"); |
+ FieldDescriptor valueField = type.findFieldByName("value"); |
+ if (keyField == null || valueField == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Invalid map field: " + field.getFullName()); |
+ } |
+ JsonObject object = (JsonObject) json; |
+ for (Map.Entry<String, JsonElement> entry : object.entrySet()) { |
+ Message.Builder entryBuilder = builder.newBuilderForField(field); |
+ Object key = parseFieldValue( |
+ keyField, new JsonPrimitive(entry.getKey()), entryBuilder); |
+ Object value = parseFieldValue( |
+ valueField, entry.getValue(), entryBuilder); |
+ if (value == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Map value cannot be null."); |
+ } |
+ entryBuilder.setField(keyField, key); |
+ entryBuilder.setField(valueField, value); |
+ builder.addRepeatedField(field, entryBuilder.build()); |
+ } |
+ } |
+ |
+ /** |
+ * Gets the default value for a field type. Note that we use proto3 |
+ * language defaults and ignore any default values set through the |
+ * proto "default" option. |
+ */ |
+ private Object getDefaultValue(FieldDescriptor field, |
+ Message.Builder builder) { |
+ switch (field.getType()) { |
+ case INT32: |
+ case SINT32: |
+ case SFIXED32: |
+ case UINT32: |
+ case FIXED32: |
+ return 0; |
+ case INT64: |
+ case SINT64: |
+ case SFIXED64: |
+ case UINT64: |
+ case FIXED64: |
+ return 0L; |
+ case FLOAT: |
+ return 0.0f; |
+ case DOUBLE: |
+ return 0.0; |
+ case BOOL: |
+ return false; |
+ case STRING: |
+ return ""; |
+ case BYTES: |
+ return ByteString.EMPTY; |
+ case ENUM: |
+ return field.getEnumType().getValues().get(0); |
+ case MESSAGE: |
+ case GROUP: |
+ return builder.newBuilderForField(field).getDefaultInstanceForType(); |
+ default: |
+ throw new IllegalStateException( |
+ "Invalid field type: " + field.getType()); |
+ } |
+ } |
+ |
+ private void mergeRepeatedField(FieldDescriptor field, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ if (!(json instanceof JsonArray)) { |
+ throw new InvalidProtocolBufferException( |
+ "Expect an array but found: " + json); |
+ } |
+ JsonArray array = (JsonArray) json; |
+ for (int i = 0; i < array.size(); ++i) { |
+ Object value = parseFieldValue(field, array.get(i), builder); |
+ if (value == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Repeated field elements cannot be null"); |
+ } |
+ builder.addRepeatedField(field, value); |
+ } |
+ } |
+ |
+ private int parseInt32(JsonElement json) |
+ throws InvalidProtocolBufferException { |
+ try { |
+ return Integer.parseInt(json.getAsString()); |
+ } catch (Exception e) { |
+ // Fall through. |
+ } |
+ // JSON doesn't distinguish between integer values and floating point values so "1" and |
+ // "1.000" are treated as equal in JSON. For this reason we accept floating point values for |
+ // integer fields as well as long as it actually is an integer (i.e., round(value) == value). |
+ try { |
+ BigDecimal value = new BigDecimal(json.getAsString()); |
+ return value.intValueExact(); |
+ } catch (Exception e) { |
+ throw new InvalidProtocolBufferException("Not an int32 value: " + json); |
+ } |
+ } |
+ |
+ private long parseInt64(JsonElement json) |
+ throws InvalidProtocolBufferException { |
+ try { |
+ return Long.parseLong(json.getAsString()); |
+ } catch (Exception e) { |
+ // Fall through. |
+ } |
+ // JSON doesn't distinguish between integer values and floating point values so "1" and |
+ // "1.000" are treated as equal in JSON. For this reason we accept floating point values for |
+ // integer fields as well as long as it actually is an integer (i.e., round(value) == value). |
+ try { |
+ BigDecimal value = new BigDecimal(json.getAsString()); |
+ return value.longValueExact(); |
+ } catch (Exception e) { |
+ throw new InvalidProtocolBufferException("Not an int32 value: " + json); |
+ } |
+ } |
+ |
+ private int parseUint32(JsonElement json) |
+ throws InvalidProtocolBufferException { |
+ try { |
+ long result = Long.parseLong(json.getAsString()); |
+ if (result < 0 || result > 0xFFFFFFFFL) { |
+ throw new InvalidProtocolBufferException( |
+ "Out of range uint32 value: " + json); |
+ } |
+ return (int) result; |
+ } catch (InvalidProtocolBufferException e) { |
+ throw e; |
+ } catch (Exception e) { |
+ // Fall through. |
+ } |
+ // JSON doesn't distinguish between integer values and floating point values so "1" and |
+ // "1.000" are treated as equal in JSON. For this reason we accept floating point values for |
+ // integer fields as well as long as it actually is an integer (i.e., round(value) == value). |
+ try { |
+ BigDecimal decimalValue = new BigDecimal(json.getAsString()); |
+ BigInteger value = decimalValue.toBigIntegerExact(); |
+ if (value.signum() < 0 || value.compareTo(new BigInteger("FFFFFFFF", 16)) > 0) { |
+ throw new InvalidProtocolBufferException("Out of range uint32 value: " + json); |
+ } |
+ return value.intValue(); |
+ } catch (InvalidProtocolBufferException e) { |
+ throw e; |
+ } catch (Exception e) { |
+ throw new InvalidProtocolBufferException( |
+ "Not an uint32 value: " + json); |
+ } |
+ } |
+ |
+ private static final BigInteger MAX_UINT64 = |
+ new BigInteger("FFFFFFFFFFFFFFFF", 16); |
+ |
+ private long parseUint64(JsonElement json) |
+ throws InvalidProtocolBufferException { |
+ try { |
+ BigDecimal decimalValue = new BigDecimal(json.getAsString()); |
+ BigInteger value = decimalValue.toBigIntegerExact(); |
+ if (value.compareTo(BigInteger.ZERO) < 0 |
+ || value.compareTo(MAX_UINT64) > 0) { |
+ throw new InvalidProtocolBufferException( |
+ "Out of range uint64 value: " + json); |
+ } |
+ return value.longValue(); |
+ } catch (InvalidProtocolBufferException e) { |
+ throw e; |
+ } catch (Exception e) { |
+ throw new InvalidProtocolBufferException( |
+ "Not an uint64 value: " + json); |
+ } |
+ } |
+ |
+ private boolean parseBool(JsonElement json) |
+ throws InvalidProtocolBufferException { |
+ if (json.getAsString().equals("true")) { |
+ return true; |
+ } |
+ if (json.getAsString().equals("false")) { |
+ return false; |
+ } |
+ throw new InvalidProtocolBufferException("Invalid bool value: " + json); |
+ } |
+ |
+ private static final double EPSILON = 1e-6; |
+ |
+ private float parseFloat(JsonElement json) |
+ throws InvalidProtocolBufferException { |
+ if (json.getAsString().equals("NaN")) { |
+ return Float.NaN; |
+ } else if (json.getAsString().equals("Infinity")) { |
+ return Float.POSITIVE_INFINITY; |
+ } else if (json.getAsString().equals("-Infinity")) { |
+ return Float.NEGATIVE_INFINITY; |
+ } |
+ try { |
+ // We don't use Float.parseFloat() here because that function simply |
+ // accepts all double values. Here we parse the value into a Double |
+ // and do explicit range check on it. |
+ double value = Double.parseDouble(json.getAsString()); |
+ // When a float value is printed, the printed value might be a little |
+ // larger or smaller due to precision loss. Here we need to add a bit |
+ // of tolerance when checking whether the float value is in range. |
+ if (value > Float.MAX_VALUE * (1.0 + EPSILON) |
+ || value < -Float.MAX_VALUE * (1.0 + EPSILON)) { |
+ throw new InvalidProtocolBufferException( |
+ "Out of range float value: " + json); |
+ } |
+ return (float) value; |
+ } catch (InvalidProtocolBufferException e) { |
+ throw e; |
+ } catch (Exception e) { |
+ throw new InvalidProtocolBufferException("Not a float value: " + json); |
+ } |
+ } |
+ |
+ private static final BigDecimal MORE_THAN_ONE = new BigDecimal( |
+ String.valueOf(1.0 + EPSILON)); |
+ // When a float value is printed, the printed value might be a little |
+ // larger or smaller due to precision loss. Here we need to add a bit |
+ // of tolerance when checking whether the float value is in range. |
+ private static final BigDecimal MAX_DOUBLE = new BigDecimal( |
+ String.valueOf(Double.MAX_VALUE)).multiply(MORE_THAN_ONE); |
+ private static final BigDecimal MIN_DOUBLE = new BigDecimal( |
+ String.valueOf(-Double.MAX_VALUE)).multiply(MORE_THAN_ONE); |
+ |
+ private double parseDouble(JsonElement json) |
+ throws InvalidProtocolBufferException { |
+ if (json.getAsString().equals("NaN")) { |
+ return Double.NaN; |
+ } else if (json.getAsString().equals("Infinity")) { |
+ return Double.POSITIVE_INFINITY; |
+ } else if (json.getAsString().equals("-Infinity")) { |
+ return Double.NEGATIVE_INFINITY; |
+ } |
+ try { |
+ // We don't use Double.parseDouble() here because that function simply |
+ // accepts all values. Here we parse the value into a BigDecimal and do |
+ // explicit range check on it. |
+ BigDecimal value = new BigDecimal(json.getAsString()); |
+ if (value.compareTo(MAX_DOUBLE) > 0 |
+ || value.compareTo(MIN_DOUBLE) < 0) { |
+ throw new InvalidProtocolBufferException( |
+ "Out of range double value: " + json); |
+ } |
+ return value.doubleValue(); |
+ } catch (InvalidProtocolBufferException e) { |
+ throw e; |
+ } catch (Exception e) { |
+ throw new InvalidProtocolBufferException( |
+ "Not an double value: " + json); |
+ } |
+ } |
+ |
+ private String parseString(JsonElement json) { |
+ return json.getAsString(); |
+ } |
+ |
+ private ByteString parseBytes(JsonElement json) throws InvalidProtocolBufferException { |
+ String encoded = json.getAsString(); |
+ if (encoded.length() % 4 != 0) { |
+ throw new InvalidProtocolBufferException( |
+ "Bytes field is not encoded in standard BASE64 with paddings: " + encoded); |
+ } |
+ return ByteString.copyFrom( |
+ BaseEncoding.base64().decode(json.getAsString())); |
+ } |
+ |
+ private EnumValueDescriptor parseEnum(EnumDescriptor enumDescriptor, |
+ JsonElement json) throws InvalidProtocolBufferException { |
+ String value = json.getAsString(); |
+ EnumValueDescriptor result = enumDescriptor.findValueByName(value); |
+ if (result == null) { |
+ // Try to interpret the value as a number. |
+ try { |
+ int numericValue = parseInt32(json); |
+ if (enumDescriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) { |
+ result = enumDescriptor.findValueByNumberCreatingIfUnknown(numericValue); |
+ } else { |
+ result = enumDescriptor.findValueByNumber(numericValue); |
+ } |
+ } catch (InvalidProtocolBufferException e) { |
+ // Fall through. This exception is about invalid int32 value we get from parseInt32() but |
+ // that's not the exception we want the user to see. Since result == null, we will throw |
+ // an exception later. |
+ } |
+ |
+ if (result == null) { |
+ throw new InvalidProtocolBufferException( |
+ "Invalid enum value: " + value + " for enum type: " |
+ + enumDescriptor.getFullName()); |
+ } |
+ } |
+ return result; |
+ } |
+ |
+ private Object parseFieldValue(FieldDescriptor field, JsonElement json, |
+ Message.Builder builder) throws InvalidProtocolBufferException { |
+ if (json instanceof JsonNull) { |
+ if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE |
+ && field.getMessageType().getFullName().equals( |
+ Value.getDescriptor().getFullName())) { |
+ // For every other type, "null" means absence, but for the special |
+ // Value message, it means the "null_value" field has been set. |
+ Value value = Value.newBuilder().setNullValueValue(0).build(); |
+ return builder.newBuilderForField(field).mergeFrom( |
+ value.toByteString()).build(); |
+ } |
+ return null; |
+ } |
+ switch (field.getType()) { |
+ case INT32: |
+ case SINT32: |
+ case SFIXED32: |
+ return parseInt32(json); |
+ |
+ case INT64: |
+ case SINT64: |
+ case SFIXED64: |
+ return parseInt64(json); |
+ |
+ case BOOL: |
+ return parseBool(json); |
+ |
+ case FLOAT: |
+ return parseFloat(json); |
+ |
+ case DOUBLE: |
+ return parseDouble(json); |
+ |
+ case UINT32: |
+ case FIXED32: |
+ return parseUint32(json); |
+ |
+ case UINT64: |
+ case FIXED64: |
+ return parseUint64(json); |
+ |
+ case STRING: |
+ return parseString(json); |
+ |
+ case BYTES: |
+ return parseBytes(json); |
+ |
+ case ENUM: |
+ return parseEnum(field.getEnumType(), json); |
+ |
+ case MESSAGE: |
+ case GROUP: |
+ Message.Builder subBuilder = builder.newBuilderForField(field); |
+ merge(json, subBuilder); |
+ return subBuilder.build(); |
+ |
+ default: |
+ throw new InvalidProtocolBufferException( |
+ "Invalid field type: " + field.getType()); |
+ } |
+ } |
+ } |
+} |