/* * 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)) throw runtime.newTypeError("Expected symbols as hash keys in initialization map."); final Descriptors.FieldDescriptor fieldDescriptor = findField(context, key); 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); } 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 (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: case STRING: Utils.validateStringEncoding(context, fieldDescriptor.getType(), value); RubyString str = (RubyString) value; switch (fieldDescriptor.getType()) { case BYTES: val = ByteString.copyFrom(str.getBytes()); break; case STRING: val = str.asJavaString(); break; default: break; } 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) { 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); for (int i = 0; i < arr.size(); i++) { repeatedField.push(context, arr.eltInternal(i)); } 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; }