|  | @@ -0,0 +1,1571 @@
 | 
	
		
			
				|  |  | +// 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.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());
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  /**
 | 
	
		
			
				|  |  | +   * A Printer converts protobuf message to JSON format.
 | 
	
		
			
				|  |  | +   */
 | 
	
		
			
				|  |  | +  public static class Printer {
 | 
	
		
			
				|  |  | +    private final TypeRegistry registry;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    private Printer(TypeRegistry registry) {
 | 
	
		
			
				|  |  | +      this.registry = registry;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 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);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 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, 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.contains(file.getName())) {
 | 
	
		
			
				|  |  | +          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 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, Appendable jsonOutput) {
 | 
	
		
			
				|  |  | +      this.registry = registry;
 | 
	
		
			
				|  |  | +      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;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      for (Map.Entry<FieldDescriptor, Object> field
 | 
	
		
			
				|  |  | +          : message.getAllFields().entrySet()) {
 | 
	
		
			
				|  |  | +        // Skip unknown enum fields.
 | 
	
		
			
				|  |  | +        if (field.getValue() instanceof EnumValueDescriptor
 | 
	
		
			
				|  |  | +            && ((EnumValueDescriptor) field.getValue()).getIndex() == -1) {
 | 
	
		
			
				|  |  | +          continue;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        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 {
 | 
	
		
			
				|  |  | +      generator.print("\"" + fieldNameToCamelName(field.getName()) + "\": ");
 | 
	
		
			
				|  |  | +      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) {
 | 
	
		
			
				|  |  | +        // Skip unknown enum entries.
 | 
	
		
			
				|  |  | +        if (element instanceof EnumValueDescriptor
 | 
	
		
			
				|  |  | +            && ((EnumValueDescriptor) element).getIndex() == -1) {
 | 
	
		
			
				|  |  | +          continue;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        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);
 | 
	
		
			
				|  |  | +        // Skip unknown enum entries.
 | 
	
		
			
				|  |  | +        if (entryValue instanceof EnumValueDescriptor
 | 
	
		
			
				|  |  | +            && ((EnumValueDescriptor) entryValue).getIndex() == -1) {
 | 
	
		
			
				|  |  | +          continue;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        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 {
 | 
	
		
			
				|  |  | +            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 String fieldNameToCamelName(String name) {
 | 
	
		
			
				|  |  | +    StringBuilder result = new StringBuilder(name.length());
 | 
	
		
			
				|  |  | +    boolean isNextUpperCase = false;
 | 
	
		
			
				|  |  | +    for (int i = 0; i < name.length(); i++) {
 | 
	
		
			
				|  |  | +      Character ch = name.charAt(i);
 | 
	
		
			
				|  |  | +      if (Character.isLowerCase(ch)) {
 | 
	
		
			
				|  |  | +        if (isNextUpperCase) {
 | 
	
		
			
				|  |  | +          result.append(Character.toUpperCase(ch));
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          result.append(ch);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        isNextUpperCase = false;
 | 
	
		
			
				|  |  | +      } else if (Character.isUpperCase(ch)) {
 | 
	
		
			
				|  |  | +        if (i == 0 && !isNextUpperCase) {
 | 
	
		
			
				|  |  | +          // Force first letter to lower-case unless explicitly told to
 | 
	
		
			
				|  |  | +          // capitalize it.
 | 
	
		
			
				|  |  | +          result.append(Character.toLowerCase(ch));
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          // Capital letters after the first are left as-is.
 | 
	
		
			
				|  |  | +          result.append(ch);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        isNextUpperCase = false;
 | 
	
		
			
				|  |  | +      } else if (Character.isDigit(ch)) {
 | 
	
		
			
				|  |  | +        result.append(ch);
 | 
	
		
			
				|  |  | +        isNextUpperCase = true;
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        isNextUpperCase = true;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return result.toString();
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  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(fieldNameToCamelName(field.getName()), 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 (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) {
 | 
	
		
			
				|  |  | +          value = getDefaultValue(valueField, entryBuilder);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        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) {
 | 
	
		
			
				|  |  | +          value = getDefaultValue(field, builder);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        builder.addRepeatedField(field, value);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    private int parseInt32(JsonElement json)
 | 
	
		
			
				|  |  | +        throws InvalidProtocolBufferException {
 | 
	
		
			
				|  |  | +      try {
 | 
	
		
			
				|  |  | +        return Integer.parseInt(json.getAsString());
 | 
	
		
			
				|  |  | +      } 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) {
 | 
	
		
			
				|  |  | +        throw new InvalidProtocolBufferException("Not an int64 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) {
 | 
	
		
			
				|  |  | +        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 {
 | 
	
		
			
				|  |  | +        BigInteger value = new BigInteger(json.getAsString());
 | 
	
		
			
				|  |  | +        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) {
 | 
	
		
			
				|  |  | +      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) {
 | 
	
		
			
				|  |  | +        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());
 | 
	
		
			
				|  |  | +      } 
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 |