| Index: third_party/protobuf/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
|
| diff --git a/third_party/protobuf/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/third_party/protobuf/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..547ab22cb2d6c3dbf819691e32707c54b368849b
|
| --- /dev/null
|
| +++ b/third_party/protobuf/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
|
| @@ -0,0 +1,753 @@
|
| +/*
|
| + * 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.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.");
|
| +
|
| + final RubyMap map = newMapForField(context, fieldDescriptor);
|
| + map.mergeIntoSelf(context, value);
|
| + maps.put(fieldDescriptor, map);
|
| + } else if (fieldDescriptor.isRepeated()) {
|
| + if (!(value instanceof RubyArray))
|
| + throw runtime.newTypeError("Expected array as initializer var for repeated field.");
|
| + 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) {
|
| + int hashCode = System.identityHashCode(this);
|
| + return context.runtime.newFixnum(hashCode);
|
| + }
|
| +
|
| + /*
|
| + * 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()) {
|
| + 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);
|
| + }
|
| + 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 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.runtime, 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.containsKey(oneofDescriptor)) {
|
| + if (oneofCases.get(oneofDescriptor) != fieldDescriptor)
|
| + return context.runtime.getNil();
|
| + return fields.get(fieldDescriptor);
|
| + } else {
|
| + Descriptors.FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
|
| + if (oneofCase != fieldDescriptor) return context.runtime.getNil();
|
| + 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) {
|
| + 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;
|
| +}
|
|
|