| /* |
| * 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 (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; |
| } |