content: /*
   * Protocol Buffers - Google's data interchange format
   * Copyright 2014 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.jruby;
  
  import com.google.protobuf.*;
  import org.jruby.*;
  import org.jruby.anno.JRubyMethod;
  import org.jruby.runtime.Block;
  import org.jruby.runtime.Helpers;
  import org.jruby.runtime.ThreadContext;
  import org.jruby.runtime.builtin.IRubyObject;
  import org.jruby.util.ByteList;
  
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.HashMap;
  import java.util.Map;
  
  public class RubyMessage extends RubyObject {
      public RubyMessage(Ruby ruby, RubyClass klazz, Descriptors.Descriptor descriptor) {
          super(ruby, klazz);
          this.descriptor = descriptor;
      }
  
      /*
       * call-seq:
       *     Message.new(kwargs) => new_message
       *
       * Creates a new instance of the given message class. Keyword arguments may be
       * provided with keywords corresponding to field names.
       *
       * Note that no literal Message class exists. Only concrete classes per message
       * type exist, as provided by the #msgclass method on Descriptors after they
       * have been added to a pool. The method definitions described here on the
       * Message class are provided on each concrete message class.
       */
      @JRubyMethod(optional = 1)
      public IRubyObject initialize(final ThreadContext context, IRubyObject[] args) {
          final Ruby runtime = context.runtime;
          this.cRepeatedField = (RubyClass) runtime.getClassFromPath("Google::Protobuf::RepeatedField");
          this.cMap = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Map");
          this.builder = DynamicMessage.newBuilder(this.descriptor);
          this.repeatedFields = new HashMap<Descriptors.FieldDescriptor, RubyRepeatedField>();
          this.maps = new HashMap<Descriptors.FieldDescriptor, RubyMap>();
          this.fields = new HashMap<Descriptors.FieldDescriptor, IRubyObject>();
          this.oneofCases = new HashMap<Descriptors.OneofDescriptor, Descriptors.FieldDescriptor>();
          if (args.length == 1) {
              if (!(args[0] instanceof RubyHash)) {
                  throw runtime.newArgumentError("expected Hash arguments.");
              }
              RubyHash hash = args[0].convertToHash();
              hash.visitAll(new RubyHash.Visitor() {
                  @Override
                  public void visit(IRubyObject key, IRubyObject value) {
                      if (!(key instanceof RubySymbol) && !(key instanceof RubyString))
                          throw runtime.newTypeError("Expected string or symbols as hash keys in initialization map.");
                      final Descriptors.FieldDescriptor fieldDescriptor = findField(context, key);
  
                      if (value.isNil()) return;
  
                      if (Utils.isMapEntry(fieldDescriptor)) {
                          if (!(value instanceof RubyHash))
                              throw runtime.newArgumentError("Expected Hash object as initializer value for map field '" +  key.asJavaString() + "'.");
  
                          final RubyMap map = newMapForField(context, fieldDescriptor);
                          map.mergeIntoSelf(context, value);
                          maps.put(fieldDescriptor, map);
                      } else if (fieldDescriptor.isRepeated()) {
                          if (!(value instanceof RubyArray))
                              throw runtime.newArgumentError("Expected array as initializer value for repeated field '" +  key.asJavaString() + "'.");
                          RubyRepeatedField repeatedField = rubyToRepeatedField(context, fieldDescriptor, value);
                          addRepeatedField(fieldDescriptor, repeatedField);
                      } else {
                          Descriptors.OneofDescriptor oneof = fieldDescriptor.getContainingOneof();
                          if (oneof != null) {
                              oneofCases.put(oneof, fieldDescriptor);
                          }
  
                          if (value instanceof RubyHash && fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
                              RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
                              RubyClass typeClass = (RubyClass) descriptor.msgclass(context);
                              value = (IRubyObject) typeClass.newInstance(context, value, Block.NULL_BLOCK);
                          }
  
                          fields.put(fieldDescriptor, value);
                      }
                  }
              });
          }
          return this;
      }
  
      /*
       * call-seq:
       *     Message.[]=(index, value)
       *
       * Sets a field's value by field name. The provided field name should be a
       * string.
       */
      @JRubyMethod(name = "[]=")
      public IRubyObject indexSet(ThreadContext context, IRubyObject fieldName, IRubyObject value) {
          Descriptors.FieldDescriptor fieldDescriptor = findField(context, fieldName);
          return setField(context, fieldDescriptor, value);
      }
  
      /*
       * call-seq:
       *     Message.[](index) => value
       *
       * Accesses a field's value by field name. The provided field name should be a
       * string.
       */
      @JRubyMethod(name = "[]")
      public IRubyObject index(ThreadContext context, IRubyObject fieldName) {
          Descriptors.FieldDescriptor fieldDescriptor = findField(context, fieldName);
          return getField(context, fieldDescriptor);
      }
  
      /*
       * call-seq:
       *     Message.inspect => string
       *
       * Returns a human-readable string representing this message. It will be
       * formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
       * field's value is represented according to its own #inspect method.
       */
      @JRubyMethod
      public IRubyObject inspect() {
          String cname = metaClass.getName();
          StringBuilder sb = new StringBuilder("<");
          sb.append(cname);
          sb.append(": ");
          sb.append(this.layoutInspect());
          sb.append(">");
  
          return getRuntime().newString(sb.toString());
      }
  
      /*
       * call-seq:
       *     Message.hash => hash_value
       *
       * Returns a hash value that represents this message's field values.
       */
      @JRubyMethod
      public IRubyObject hash(ThreadContext context) {
          try {
              MessageDigest digest = MessageDigest.getInstance("SHA-256");
              for (RubyMap map : maps.values()) {
                  digest.update((byte) map.hashCode());
              }
              for (RubyRepeatedField repeatedField : repeatedFields.values()) {
                  digest.update((byte) repeatedFields.hashCode());
              }
              for (IRubyObject field : fields.values()) {
                  digest.update((byte) field.hashCode());
              }
              return context.runtime.newString(new ByteList(digest.digest()));
          } catch (NoSuchAlgorithmException ignore) {
              return context.runtime.newFixnum(System.identityHashCode(this));
          }
      }
  
      /*
       * call-seq:
       *     Message.==(other) => boolean
       *
       * Performs a deep comparison of this message with another. Messages are equal
       * if they have the same type and if each field is equal according to the :==
       * method's semantics (a more efficient comparison may actually be done if the
       * field is of a primitive type).
       */
      @JRubyMethod(name = "==")
      public IRubyObject eq(ThreadContext context, IRubyObject other) {
          Ruby runtime = context.runtime;
          if (!(other instanceof RubyMessage))
              return runtime.getFalse();
          RubyMessage message = (RubyMessage) other;
          if (descriptor != message.descriptor) {
              return runtime.getFalse();
          }
  
          for (Descriptors.FieldDescriptor fdef : descriptor.getFields()) {
              IRubyObject thisVal = getField(context, fdef);
              IRubyObject thatVal = message.getField(context, fdef);
              IRubyObject ret = thisVal.callMethod(context, "==", thatVal);
              if (!ret.isTrue()) {
                  return runtime.getFalse();
              }
          }
          return runtime.getTrue();
      }
  
      /*
       * call-seq:
       *     Message.method_missing(*args)
       *
       * Provides accessors and setters for message fields according to their field
       * names. For any field whose name does not conflict with a built-in method, an
       * accessor is provided with the same name as the field, and a setter is
       * provided with the name of the field plus the '=' suffix. Thus, given a
       * message instance 'msg' with field 'foo', the following code is valid:
       *
       *     msg.foo = 42
       *     puts msg.foo
       */
      @JRubyMethod(name = "method_missing", rest = true)
      public IRubyObject methodMissing(ThreadContext context, IRubyObject[] args) {
          if (args.length == 1) {
              RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
              IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]);
              if (oneofDescriptor.isNil()) {
                  if (!hasField(args[0])) {
                      return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK);
                  }
                  return index(context, args[0]);
              }
              RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor;
              Descriptors.FieldDescriptor fieldDescriptor =
                      oneofCases.get(rubyOneofDescriptor.getOneofDescriptor());
              if (fieldDescriptor == null)
                  return context.runtime.getNil();
  
              return context.runtime.newSymbol(fieldDescriptor.getName());
          } else {
              // fieldName is RubySymbol
              RubyString field = args[0].asString();
              RubyString equalSign = context.runtime.newString(Utils.EQUAL_SIGN);
              if (field.end_with_p(context, equalSign).isTrue()) {
                  field.chomp_bang(context, equalSign);
              }
  
              if (!hasField(field)) {
                  return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK);
              }
              return indexSet(context, field, args[1]);
          }
      }
  
      /**
       * call-seq:
       * Message.dup => new_message
       * Performs a shallow copy of this message and returns the new copy.
       */
      @JRubyMethod
      public IRubyObject dup(ThreadContext context) {
          RubyMessage dup = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
          IRubyObject value;
          for (Descriptors.FieldDescriptor fieldDescriptor : this.descriptor.getFields()) {
              if (fieldDescriptor.isRepeated()) {
                  dup.addRepeatedField(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor));
              } else if (fields.containsKey(fieldDescriptor)) {
                  dup.fields.put(fieldDescriptor, fields.get(fieldDescriptor));
              } else if (this.builder.hasField(fieldDescriptor)) {
                  dup.fields.put(fieldDescriptor, wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor)));
              }
          }
          for (Descriptors.FieldDescriptor fieldDescriptor : maps.keySet()) {
              dup.maps.put(fieldDescriptor, maps.get(fieldDescriptor));
          }
          return dup;
      }
  
      /*
       * call-seq:
       *     Message.descriptor => descriptor
       *
       * Class method that returns the Descriptor instance corresponding to this
       * message class's type.
       */
      @JRubyMethod(name = "descriptor", meta = true)
      public static IRubyObject getDescriptor(ThreadContext context, IRubyObject recv) {
          return ((RubyClass) recv).getInstanceVariable(Utils.DESCRIPTOR_INSTANCE_VAR);
      }
  
      /*
       * call-seq:
       *     MessageClass.encode(msg) => bytes
       *
       * Encodes the given message object to its serialized form in protocol buffers
       * wire format.
       */
      @JRubyMethod(meta = true)
      public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject value) {
          RubyMessage message = (RubyMessage) value;
          return context.runtime.newString(new ByteList(message.build(context).toByteArray()));
      }
  
      /*
       * call-seq:
       *     MessageClass.decode(data) => message
       *
       * Decodes the given data (as a string containing bytes in protocol buffers wire
       * format) under the interpretration given by this message class's definition
       * and returns a message object with the corresponding field values.
       */
      @JRubyMethod(meta = true)
      public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyObject data) {
          byte[] bin = data.convertToString().getBytes();
          RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
          try {
              ret.builder.mergeFrom(bin);
          } catch (InvalidProtocolBufferException e) {
              throw context.runtime.newRuntimeError(e.getMessage());
          }
          return ret;
      }
  
      /*
       * call-seq:
       *     MessageClass.encode_json(msg) => json_string
       *
       * Encodes the given message object into its serialized JSON representation.
       */
      @JRubyMethod(name = "encode_json", meta = true)
      public static IRubyObject encodeJson(ThreadContext context, IRubyObject recv, IRubyObject msgRb) {
          RubyMessage message = (RubyMessage) msgRb;
          return Helpers.invoke(context, message.toHash(context), "to_json");
      }
  
      /*
       * call-seq:
       *     MessageClass.decode_json(data) => message
       *
       * Decodes the given data (as a string containing bytes in protocol buffers wire
       * format) under the interpretration given by this message class's definition
       * and returns a message object with the corresponding field values.
       */
      @JRubyMethod(name = "decode_json", meta = true)
      public static IRubyObject decodeJson(ThreadContext context, IRubyObject recv, IRubyObject json) {
          Ruby runtime = context.runtime;
          RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
          RubyModule jsonModule = runtime.getClassFromPath("JSON");
          RubyHash opts = RubyHash.newHash(runtime);
          opts.fastASet(runtime.newSymbol("symbolize_names"), runtime.getTrue());
          IRubyObject[] args = new IRubyObject[] { Helpers.invoke(context, jsonModule, "parse", json, opts) };
          ret.initialize(context, args);
          return ret;
      }
  
      @JRubyMethod(name = {"to_h", "to_hash"})
      public IRubyObject toHash(ThreadContext context) {
          Ruby runtime = context.runtime;
          RubyHash ret = RubyHash.newHash(runtime);
          for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
              IRubyObject value = getField(context, fdef);
              if (!value.isNil()) {
                  if (fdef.isRepeated() && !fdef.isMapField()) {
                      if (fdef.getType() != Descriptors.FieldDescriptor.Type.MESSAGE) {
                          value = Helpers.invoke(context, value, "to_a");
                      } else {
                          RubyArray ary = value.convertToArray();
                          for (int i = 0; i < ary.size(); i++) {
                              IRubyObject submsg = Helpers.invoke(context, ary.eltInternal(i), "to_h");
                              ary.eltInternalSet(i, submsg);
                          }
  
                          value = ary.to_ary();
                      }
                  } else if (value.respondsTo("to_h")) {
                      value = Helpers.invoke(context, value, "to_h");
                  } else if (value.respondsTo("to_a")) {
                      value = Helpers.invoke(context, value, "to_a");
                  }
              }
              ret.fastASet(runtime.newSymbol(fdef.getName()), value);
          }
          return ret;
      }
  
      protected DynamicMessage build(ThreadContext context) {
          return build(context, 0);
      }
  
      protected DynamicMessage build(ThreadContext context, int depth) {
          if (depth > SINK_MAXIMUM_NESTING) {
              throw context.runtime.newRuntimeError("Maximum recursion depth exceeded during encoding.");
          }
          for (Descriptors.FieldDescriptor fieldDescriptor : maps.keySet()) {
              this.builder.clearField(fieldDescriptor);
              RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
              for (DynamicMessage kv : maps.get(fieldDescriptor).build(context, mapDescriptor)) {
                  this.builder.addRepeatedField(fieldDescriptor, kv);
              }
          }
          for (Descriptors.FieldDescriptor fieldDescriptor : repeatedFields.keySet()) {
              RubyRepeatedField repeatedField = repeatedFields.get(fieldDescriptor);
              this.builder.clearField(fieldDescriptor);
              for (int i = 0; i < repeatedField.size(); i++) {
                  Object item = convert(context, fieldDescriptor, repeatedField.get(i), depth);
                  this.builder.addRepeatedField(fieldDescriptor, item);
              }
          }
          for (Descriptors.FieldDescriptor fieldDescriptor : fields.keySet()) {
              IRubyObject value = fields.get(fieldDescriptor);
              this.builder.setField(fieldDescriptor, convert(context, fieldDescriptor, value, depth));
          }
          return this.builder.build();
      }
  
      protected Descriptors.Descriptor getDescriptor() {
          return this.descriptor;
      }
  
      // Internal use only, called by Google::Protobuf.deep_copy
      protected IRubyObject deepCopy(ThreadContext context) {
          RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
          for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
              if (fdef.isRepeated()) {
                  copy.addRepeatedField(fdef, this.getRepeatedField(context, fdef).deepCopy(context));
              } else if (fields.containsKey(fdef)) {
                  copy.fields.put(fdef, fields.get(fdef));
              } else if (this.builder.hasField(fdef)) {
                  copy.fields.put(fdef, wrapField(context, fdef, this.builder.getField(fdef)));
              }
          }
          return copy;
      }
  
      private RubyRepeatedField getRepeatedField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
          if (this.repeatedFields.containsKey(fieldDescriptor)) {
              return this.repeatedFields.get(fieldDescriptor);
          }
          int count = this.builder.getRepeatedFieldCount(fieldDescriptor);
          RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
          for (int i = 0; i < count; i++) {
              ret.push(context, wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i)));
          }
          addRepeatedField(fieldDescriptor, ret);
          return ret;
      }
  
      private void addRepeatedField(Descriptors.FieldDescriptor fieldDescriptor, RubyRepeatedField repeatedField) {
          this.repeatedFields.put(fieldDescriptor, repeatedField);
      }
  
      private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) {
          this.builder.mergeFrom(dynamicMessage);
          return this;
      }
  
      private Descriptors.FieldDescriptor findField(ThreadContext context, IRubyObject fieldName) {
          String nameStr = fieldName.asJavaString();
          Descriptors.FieldDescriptor ret = this.descriptor.findFieldByName(Utils.escapeIdentifier(nameStr));
          if (ret == null)
              throw context.runtime.newArgumentError("field " + fieldName.asJavaString() + " is not found");
          return ret;
      }
  
      private boolean hasField(IRubyObject fieldName) {
          String nameStr = fieldName.asJavaString();
          return this.descriptor.findFieldByName(Utils.escapeIdentifier(nameStr)) != null;
      }
  
      private void checkRepeatedFieldType(ThreadContext context, IRubyObject value,
                                          Descriptors.FieldDescriptor fieldDescriptor) {
          Ruby runtime = context.runtime;
          if (!(value instanceof RubyRepeatedField)) {
              throw runtime.newTypeError("Expected repeated field array");
          }
      }
  
      // convert a ruby object to protobuf type, with type check
      private Object convert(ThreadContext context,
                             Descriptors.FieldDescriptor fieldDescriptor,
                             IRubyObject value, int depth) {
          Ruby runtime = context.runtime;
          Object val = null;
          switch (fieldDescriptor.getType()) {
              case INT32:
              case INT64:
              case UINT32:
              case UINT64:
                  if (!Utils.isRubyNum(value)) {
                      throw runtime.newTypeError("Expected number type for integral field.");
                  }
                  Utils.checkIntTypePrecision(context, fieldDescriptor.getType(), value);
                  switch (fieldDescriptor.getType()) {
                      case INT32:
                          val = RubyNumeric.num2int(value);
                          break;
                      case INT64:
                          val = RubyNumeric.num2long(value);
                          break;
                      case UINT32:
                          val = Utils.num2uint(value);
                          break;
                      case UINT64:
                          val = Utils.num2ulong(context.runtime, value);
                          break;
                      default:
                          break;
                  }
                  break;
              case FLOAT:
                  if (!Utils.isRubyNum(value))
                      throw runtime.newTypeError("Expected number type for float field.");
                  val = (float) RubyNumeric.num2dbl(value);
                  break;
              case DOUBLE:
                  if (!Utils.isRubyNum(value))
                      throw runtime.newTypeError("Expected number type for double field.");
                  val = RubyNumeric.num2dbl(value);
                  break;
              case BOOL:
                  if (!(value instanceof RubyBoolean))
                      throw runtime.newTypeError("Invalid argument for boolean field.");
                  val = value.isTrue();
                  break;
              case BYTES:
                  Utils.validateStringEncoding(context, fieldDescriptor.getType(), value);
                  val = ByteString.copyFrom(((RubyString) value).getBytes());
                  break;
              case STRING:
                  Utils.validateStringEncoding(context, fieldDescriptor.getType(), value);
                  val = ((RubyString) value).asJavaString();
                  break;
              case MESSAGE:
                  RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
                  if (!value.getMetaClass().equals(typeClass))
                      throw runtime.newTypeError(value, "Invalid type to assign to submessage field.");
                  val = ((RubyMessage) value).build(context, depth + 1);
                  break;
              case ENUM:
                  Descriptors.EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
  
                  if (Utils.isRubyNum(value)) {
                      val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
                  } else if (value instanceof RubySymbol || value instanceof RubyString) {
                      val = enumDescriptor.findValueByName(value.asJavaString());
                  } else {
                      throw runtime.newTypeError("Expected number or symbol type for enum field.");
                  }
                  if (val == null) {
                      throw runtime.newRangeError("Enum value " + value + " is not found.");
                  }
                  break;
              default:
                  break;
          }
          return val;
      }
  
      private IRubyObject wrapField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, Object value) {
          if (value == null) {
              return context.runtime.getNil();
          }
          Ruby runtime = context.runtime;
          switch (fieldDescriptor.getType()) {
              case INT32:
              case INT64:
              case UINT32:
              case UINT64:
              case FLOAT:
              case DOUBLE:
              case BOOL:
              case BYTES:
              case STRING:
                  return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value);
              case MESSAGE:
                  RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
                  RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
                  return msg.buildFrom(context, (DynamicMessage) value);
              case ENUM:
                  Descriptors.EnumValueDescriptor enumValueDescriptor = (Descriptors.EnumValueDescriptor) value;
                  if (enumValueDescriptor.getIndex() == -1) { // UNKNOWN ENUM VALUE
                      return runtime.newFixnum(enumValueDescriptor.getNumber());
                  }
                  return runtime.newSymbol(enumValueDescriptor.getName());
              default:
                  return runtime.newString(value.toString());
          }
      }
  
      private RubyRepeatedField repeatedFieldForFieldDescriptor(ThreadContext context,
                                                                Descriptors.FieldDescriptor fieldDescriptor) {
          IRubyObject typeClass = context.runtime.getNilClass();
  
          IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor);
          Descriptors.FieldDescriptor.Type type = fieldDescriptor.getType();
          if (type == Descriptors.FieldDescriptor.Type.MESSAGE) {
              typeClass = ((RubyDescriptor) descriptor).msgclass(context);
  
          } else if (type == Descriptors.FieldDescriptor.Type.ENUM) {
              typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context);
          }
          return new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass);
      }
  
      protected IRubyObject getField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
          Descriptors.OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
          if (oneofDescriptor != null) {
              if (oneofCases.get(oneofDescriptor) == fieldDescriptor) {
                  return fields.get(fieldDescriptor);
              } else {
                  Descriptors.FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
                  if (oneofCase != fieldDescriptor) {
                    if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
                      return context.runtime.getNil();
                    } else {
                      return wrapField(context, fieldDescriptor, fieldDescriptor.getDefaultValue());
                    }
                  }
                  IRubyObject value = wrapField(context, oneofCase, builder.getField(oneofCase));
                  fields.put(fieldDescriptor, value);
                  return value;
              }
          }
  
          if (Utils.isMapEntry(fieldDescriptor)) {
              RubyMap map = maps.get(fieldDescriptor);
              if (map == null) {
                  map = newMapForField(context, fieldDescriptor);
                  int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor);
                  Descriptors.FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
                  Descriptors.FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
                  RubyDescriptor kvDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
                  RubyClass kvClass = (RubyClass) kvDescriptor.msgclass(context);
                  for (int i = 0; i < mapSize; i++) {
                      RubyMessage kvMessage = (RubyMessage) kvClass.newInstance(context, Block.NULL_BLOCK);
                      DynamicMessage message = (DynamicMessage) this.builder.getRepeatedField(fieldDescriptor, i);
                      kvMessage.buildFrom(context, message);
                      map.indexSet(context, kvMessage.getField(context, keyField), kvMessage.getField(context, valueField));
                  }
                  maps.put(fieldDescriptor, map);
              }
              return map;
          }
          if (fieldDescriptor.isRepeated()) {
              return getRepeatedField(context, fieldDescriptor);
          }
          if (fieldDescriptor.getType() != Descriptors.FieldDescriptor.Type.MESSAGE ||
                  this.builder.hasField(fieldDescriptor) || fields.containsKey(fieldDescriptor)) {
              if (fields.containsKey(fieldDescriptor)) {
                  return fields.get(fieldDescriptor);
              } else {
                  IRubyObject value = wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor));
                  if (this.builder.hasField(fieldDescriptor)) {
                      fields.put(fieldDescriptor, value);
                  }
                  return value;
              }
          }
          return context.runtime.getNil();
      }
  
      protected IRubyObject setField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
          if (Utils.isMapEntry(fieldDescriptor)) {
              if (!(value instanceof RubyMap)) {
                  throw context.runtime.newTypeError("Expected Map instance");
              }
              RubyMap thisMap = (RubyMap) getField(context, fieldDescriptor);
              thisMap.mergeIntoSelf(context, value);
          } else if (fieldDescriptor.isRepeated()) {
              checkRepeatedFieldType(context, value, fieldDescriptor);
              if (value instanceof RubyRepeatedField) {
                  addRepeatedField(fieldDescriptor, (RubyRepeatedField) value);
              } else {
                  RubyArray ary = value.convertToArray();
                  RubyRepeatedField repeatedField = rubyToRepeatedField(context, fieldDescriptor, ary);
                  addRepeatedField(fieldDescriptor, repeatedField);
              }
          } else {
              Descriptors.OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
              if (oneofDescriptor != null) {
                  Descriptors.FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor);
                  if (oneofCase != null && oneofCase != fieldDescriptor) {
                      fields.remove(oneofCase);
                  }
                  if (value.isNil()) {
                      oneofCases.remove(oneofDescriptor);
                      fields.remove(fieldDescriptor);
                  } else {
                      oneofCases.put(oneofDescriptor, fieldDescriptor);
                      fields.put(fieldDescriptor, value);
                  }
              } else {
                  Descriptors.FieldDescriptor.Type fieldType = fieldDescriptor.getType();
                  IRubyObject typeClass = context.runtime.getObject();
                  boolean addValue = true;
                  if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE) {
                      typeClass = ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
                      if (value.isNil()){
                          addValue = false;
                      }
                  } else if (fieldType == Descriptors.FieldDescriptor.Type.ENUM) {
                      typeClass = ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor)).enummodule(context);
                      Descriptors.EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
                      if (Utils.isRubyNum(value)) {
                          Descriptors.EnumValueDescriptor val =
                                  enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
                          if (val.getIndex() != -1) value = context.runtime.newSymbol(val.getName());
                      }
                  }
                  if (addValue) {
                      value = Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
                      this.fields.put(fieldDescriptor, value);
                  } else {
                      this.fields.remove(fieldDescriptor);
                  }
              }
          }
          return context.runtime.getNil();
      }
  
      private String layoutInspect() {
          ThreadContext context = getRuntime().getCurrentContext();
          StringBuilder sb = new StringBuilder();
          for (Descriptors.FieldDescriptor fdef : descriptor.getFields()) {
              sb.append(Utils.unescapeIdentifier(fdef.getName()));
              sb.append(": ");
              sb.append(getField(context, fdef).inspect());
              sb.append(", ");
          }
          return sb.substring(0, sb.length() - 2);
      }
  
      private IRubyObject getDescriptorForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
          RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
          return thisRbDescriptor.lookup(fieldDescriptor.getName()).getSubType(context);
      }
  
      private RubyRepeatedField rubyToRepeatedField(ThreadContext context,
                                                    Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
          RubyArray arr = value.convertToArray();
          RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
  
          RubyClass typeClass = null;
          if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
              RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
              typeClass = (RubyClass) descriptor.msgclass(context);
          }
  
          for (int i = 0; i < arr.size(); i++) {
              IRubyObject row = arr.eltInternal(i);
              if (row instanceof RubyHash && typeClass != null) {
                  row = (IRubyObject) typeClass.newInstance(context, row, Block.NULL_BLOCK);
              }
  
              repeatedField.push(context, row);
          }
          return repeatedField;
      }
  
      private RubyMap newMapForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
          RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
          Descriptors.FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
          Descriptors.FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
          IRubyObject keyType = RubySymbol.newSymbol(context.runtime, keyField.getType().name());
          IRubyObject valueType = RubySymbol.newSymbol(context.runtime, valueField.getType().name());
          if (valueField.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
              RubyFieldDescriptor rubyFieldDescriptor = (RubyFieldDescriptor) mapDescriptor.lookup(context,
                      context.runtime.newString("value"));
              RubyDescriptor rubyDescriptor = (RubyDescriptor) rubyFieldDescriptor.getSubType(context);
              return (RubyMap) cMap.newInstance(context, keyType, valueType,
                      rubyDescriptor.msgclass(context), Block.NULL_BLOCK);
          } else {
              return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK);
          }
      }
  
      private Descriptors.FieldDescriptor getOneofCase(Descriptors.OneofDescriptor oneof) {
          if (oneofCases.containsKey(oneof)) {
              return oneofCases.get(oneof);
          }
          return builder.getOneofFieldDescriptor(oneof);
      }
  
      private Descriptors.Descriptor descriptor;
      private DynamicMessage.Builder builder;
      private RubyClass cRepeatedField;
      private RubyClass cMap;
      private Map<Descriptors.FieldDescriptor, RubyRepeatedField> repeatedFields;
      private Map<Descriptors.FieldDescriptor, RubyMap> maps;
      private Map<Descriptors.FieldDescriptor, IRubyObject> fields;
      private Map<Descriptors.OneofDescriptor, Descriptors.FieldDescriptor> oneofCases;
  
      private static final int SINK_MAXIMUM_NESTING = 64;
  }
  