|
@@ -32,24 +32,45 @@
|
|
|
|
|
|
package com.google.protobuf.jruby;
|
|
package com.google.protobuf.jruby;
|
|
|
|
|
|
-import com.google.protobuf.*;
|
|
|
|
|
|
+import com.google.protobuf.Descriptors.Descriptor;
|
|
|
|
+import com.google.protobuf.Descriptors.EnumDescriptor;
|
|
|
|
+import com.google.protobuf.Descriptors.EnumValueDescriptor;
|
|
|
|
+import com.google.protobuf.Descriptors.FieldDescriptor;
|
|
|
|
+import com.google.protobuf.Descriptors.FileDescriptor;
|
|
|
|
+import com.google.protobuf.Descriptors.OneofDescriptor;
|
|
|
|
+import com.google.protobuf.ByteString;
|
|
|
|
+import com.google.protobuf.DynamicMessage;
|
|
|
|
+import com.google.protobuf.InvalidProtocolBufferException;
|
|
|
|
+import com.google.protobuf.Message;
|
|
|
|
+import com.google.protobuf.UnknownFieldSet;
|
|
|
|
+import com.google.protobuf.util.JsonFormat;
|
|
import org.jruby.*;
|
|
import org.jruby.*;
|
|
import org.jruby.anno.JRubyMethod;
|
|
import org.jruby.anno.JRubyMethod;
|
|
|
|
+import org.jruby.exceptions.RaiseException;
|
|
import org.jruby.runtime.Block;
|
|
import org.jruby.runtime.Block;
|
|
import org.jruby.runtime.Helpers;
|
|
import org.jruby.runtime.Helpers;
|
|
import org.jruby.runtime.ThreadContext;
|
|
import org.jruby.runtime.ThreadContext;
|
|
import org.jruby.runtime.builtin.IRubyObject;
|
|
import org.jruby.runtime.builtin.IRubyObject;
|
|
import org.jruby.util.ByteList;
|
|
import org.jruby.util.ByteList;
|
|
|
|
|
|
|
|
+import java.nio.ByteBuffer;
|
|
import java.security.MessageDigest;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.HashMap;
|
|
import java.util.HashMap;
|
|
|
|
+import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
|
|
public class RubyMessage extends RubyObject {
|
|
public class RubyMessage extends RubyObject {
|
|
- public RubyMessage(Ruby ruby, RubyClass klazz, Descriptors.Descriptor descriptor) {
|
|
|
|
- super(ruby, klazz);
|
|
|
|
|
|
+ public RubyMessage(Ruby runtime, RubyClass klazz, Descriptor descriptor) {
|
|
|
|
+ super(runtime, klazz);
|
|
|
|
+
|
|
this.descriptor = descriptor;
|
|
this.descriptor = descriptor;
|
|
|
|
+ this.cRepeatedField = (RubyClass) runtime.getClassFromPath("Google::Protobuf::RepeatedField");
|
|
|
|
+ this.cMap = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Map");
|
|
|
|
+ this.builder = DynamicMessage.newBuilder(descriptor);
|
|
|
|
+ this.fields = new HashMap<FieldDescriptor, IRubyObject>();
|
|
|
|
+ this.oneofCases = new HashMap<OneofDescriptor, FieldDescriptor>();
|
|
|
|
+ this.proto3 = descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -67,13 +88,6 @@ public class RubyMessage extends RubyObject {
|
|
@JRubyMethod(optional = 1)
|
|
@JRubyMethod(optional = 1)
|
|
public IRubyObject initialize(final ThreadContext context, IRubyObject[] args) {
|
|
public IRubyObject initialize(final ThreadContext context, IRubyObject[] args) {
|
|
final Ruby runtime = context.runtime;
|
|
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.length == 1) {
|
|
if (!(args[0] instanceof RubyHash)) {
|
|
if (!(args[0] instanceof RubyHash)) {
|
|
throw runtime.newArgumentError("expected Hash arguments.");
|
|
throw runtime.newArgumentError("expected Hash arguments.");
|
|
@@ -84,35 +98,36 @@ public class RubyMessage extends RubyObject {
|
|
public void visit(IRubyObject key, IRubyObject value) {
|
|
public void visit(IRubyObject key, IRubyObject value) {
|
|
if (!(key instanceof RubySymbol) && !(key instanceof RubyString))
|
|
if (!(key instanceof RubySymbol) && !(key instanceof RubyString))
|
|
throw runtime.newTypeError("Expected string or symbols as hash keys in initialization map.");
|
|
throw runtime.newTypeError("Expected string or symbols as hash keys in initialization map.");
|
|
- final Descriptors.FieldDescriptor fieldDescriptor = findField(context, key);
|
|
|
|
|
|
+ final FieldDescriptor fieldDescriptor = findField(context, key, ignoreUnknownFieldsOnInit);
|
|
|
|
|
|
- if (value.isNil()) return;
|
|
|
|
|
|
+ if (value == null || value.isNil()) return;
|
|
|
|
|
|
if (Utils.isMapEntry(fieldDescriptor)) {
|
|
if (Utils.isMapEntry(fieldDescriptor)) {
|
|
if (!(value instanceof RubyHash))
|
|
if (!(value instanceof RubyHash))
|
|
- throw runtime.newArgumentError("Expected Hash object as initializer value for map field '" + key.asJavaString() + "'.");
|
|
|
|
|
|
+ throw runtime.newArgumentError("Expected Hash object as initializer value for map field '" + key.asJavaString() + "' (given " + value.getMetaClass() + ").");
|
|
|
|
|
|
final RubyMap map = newMapForField(context, fieldDescriptor);
|
|
final RubyMap map = newMapForField(context, fieldDescriptor);
|
|
map.mergeIntoSelf(context, value);
|
|
map.mergeIntoSelf(context, value);
|
|
- maps.put(fieldDescriptor, map);
|
|
|
|
|
|
+ fields.put(fieldDescriptor, map);
|
|
} else if (fieldDescriptor.isRepeated()) {
|
|
} else if (fieldDescriptor.isRepeated()) {
|
|
if (!(value instanceof RubyArray))
|
|
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);
|
|
|
|
|
|
+ throw runtime.newArgumentError("Expected array as initializer value for repeated field '" + key.asJavaString() + "' (given " + value.getMetaClass() + ").");
|
|
|
|
+ fields.put(fieldDescriptor, rubyToRepeatedField(context, fieldDescriptor, value));
|
|
} else {
|
|
} else {
|
|
- Descriptors.OneofDescriptor oneof = fieldDescriptor.getContainingOneof();
|
|
|
|
|
|
+ OneofDescriptor oneof = fieldDescriptor.getContainingOneof();
|
|
if (oneof != null) {
|
|
if (oneof != null) {
|
|
oneofCases.put(oneof, fieldDescriptor);
|
|
oneofCases.put(oneof, fieldDescriptor);
|
|
}
|
|
}
|
|
|
|
|
|
- if (value instanceof RubyHash && fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
|
|
|
|
|
|
+ if (value instanceof RubyHash && fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE) {
|
|
RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
RubyClass typeClass = (RubyClass) descriptor.msgclass(context);
|
|
RubyClass typeClass = (RubyClass) descriptor.msgclass(context);
|
|
value = (IRubyObject) typeClass.newInstance(context, value, Block.NULL_BLOCK);
|
|
value = (IRubyObject) typeClass.newInstance(context, value, Block.NULL_BLOCK);
|
|
|
|
+ fields.put(fieldDescriptor, value);
|
|
|
|
+ } else {
|
|
|
|
+ indexSet(context, key, value);
|
|
}
|
|
}
|
|
|
|
|
|
- fields.put(fieldDescriptor, value);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
@@ -129,8 +144,8 @@ public class RubyMessage extends RubyObject {
|
|
*/
|
|
*/
|
|
@JRubyMethod(name = "[]=")
|
|
@JRubyMethod(name = "[]=")
|
|
public IRubyObject indexSet(ThreadContext context, IRubyObject fieldName, IRubyObject value) {
|
|
public IRubyObject indexSet(ThreadContext context, IRubyObject fieldName, IRubyObject value) {
|
|
- Descriptors.FieldDescriptor fieldDescriptor = findField(context, fieldName);
|
|
|
|
- return setField(context, fieldDescriptor, value);
|
|
|
|
|
|
+ FieldDescriptor fieldDescriptor = findField(context, fieldName);
|
|
|
|
+ return setFieldInternal(context, fieldDescriptor, value);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -142,8 +157,8 @@ public class RubyMessage extends RubyObject {
|
|
*/
|
|
*/
|
|
@JRubyMethod(name = "[]")
|
|
@JRubyMethod(name = "[]")
|
|
public IRubyObject index(ThreadContext context, IRubyObject fieldName) {
|
|
public IRubyObject index(ThreadContext context, IRubyObject fieldName) {
|
|
- Descriptors.FieldDescriptor fieldDescriptor = findField(context, fieldName);
|
|
|
|
- return getField(context, fieldDescriptor);
|
|
|
|
|
|
+ FieldDescriptor fieldDescriptor = findField(context, fieldName);
|
|
|
|
+ return getFieldInternal(context, fieldDescriptor);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -154,16 +169,37 @@ public class RubyMessage extends RubyObject {
|
|
* formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
|
|
* formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
|
|
* field's value is represented according to its own #inspect method.
|
|
* field's value is represented according to its own #inspect method.
|
|
*/
|
|
*/
|
|
- @JRubyMethod
|
|
|
|
|
|
+ @JRubyMethod(name = {"inspect", "to_s"})
|
|
public IRubyObject inspect() {
|
|
public IRubyObject inspect() {
|
|
|
|
+ ThreadContext context = getRuntime().getCurrentContext();
|
|
String cname = metaClass.getName();
|
|
String cname = metaClass.getName();
|
|
|
|
+ String colon = ": ";
|
|
|
|
+ String comma = ", ";
|
|
StringBuilder sb = new StringBuilder("<");
|
|
StringBuilder sb = new StringBuilder("<");
|
|
- sb.append(cname);
|
|
|
|
- sb.append(": ");
|
|
|
|
- sb.append(this.layoutInspect());
|
|
|
|
|
|
+ boolean addComma = false;
|
|
|
|
+
|
|
|
|
+ sb.append(cname).append(colon);
|
|
|
|
+
|
|
|
|
+ for (FieldDescriptor fd : descriptor.getFields()) {
|
|
|
|
+ if (addComma) {
|
|
|
|
+ sb.append(comma);
|
|
|
|
+ } else {
|
|
|
|
+ addComma = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ sb.append(fd.getName()).append(colon);
|
|
|
|
+
|
|
|
|
+ IRubyObject value = getFieldInternal(context, fd);
|
|
|
|
+ if (value instanceof RubyBoolean) {
|
|
|
|
+ // Booleans don't implement internal "inspect" methods so have to call handle them manually
|
|
|
|
+ sb.append(value.isTrue() ? "true" : "false");
|
|
|
|
+ } else {
|
|
|
|
+ sb.append(value.inspect());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
sb.append(">");
|
|
sb.append(">");
|
|
|
|
|
|
- return getRuntime().newString(sb.toString());
|
|
|
|
|
|
+ return context.runtime.newString(sb.toString());
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -176,16 +212,10 @@ public class RubyMessage extends RubyObject {
|
|
public IRubyObject hash(ThreadContext context) {
|
|
public IRubyObject hash(ThreadContext context) {
|
|
try {
|
|
try {
|
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
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());
|
|
|
|
|
|
+ for (FieldDescriptor fd : descriptor.getFields()) {
|
|
|
|
+ digest.update((byte) getFieldInternal(context, fd).hashCode());
|
|
}
|
|
}
|
|
- return context.runtime.newString(new ByteList(digest.digest()));
|
|
|
|
|
|
+ return context.runtime.newFixnum(ByteBuffer.wrap(digest.digest()).getLong());
|
|
} catch (NoSuchAlgorithmException ignore) {
|
|
} catch (NoSuchAlgorithmException ignore) {
|
|
return context.runtime.newFixnum(System.identityHashCode(this));
|
|
return context.runtime.newFixnum(System.identityHashCode(this));
|
|
}
|
|
}
|
|
@@ -200,7 +230,7 @@ public class RubyMessage extends RubyObject {
|
|
* method's semantics (a more efficient comparison may actually be done if the
|
|
* method's semantics (a more efficient comparison may actually be done if the
|
|
* field is of a primitive type).
|
|
* field is of a primitive type).
|
|
*/
|
|
*/
|
|
- @JRubyMethod(name = "==")
|
|
|
|
|
|
+ @JRubyMethod(name = {"==", "eql?"})
|
|
public IRubyObject eq(ThreadContext context, IRubyObject other) {
|
|
public IRubyObject eq(ThreadContext context, IRubyObject other) {
|
|
Ruby runtime = context.runtime;
|
|
Ruby runtime = context.runtime;
|
|
if (!(other instanceof RubyMessage))
|
|
if (!(other instanceof RubyMessage))
|
|
@@ -210,9 +240,9 @@ public class RubyMessage extends RubyObject {
|
|
return runtime.getFalse();
|
|
return runtime.getFalse();
|
|
}
|
|
}
|
|
|
|
|
|
- for (Descriptors.FieldDescriptor fdef : descriptor.getFields()) {
|
|
|
|
- IRubyObject thisVal = getField(context, fdef);
|
|
|
|
- IRubyObject thatVal = message.getField(context, fdef);
|
|
|
|
|
|
+ for (FieldDescriptor fdef : descriptor.getFields()) {
|
|
|
|
+ IRubyObject thisVal = getFieldInternal(context, fdef);
|
|
|
|
+ IRubyObject thatVal = message.getFieldInternal(context, fdef);
|
|
IRubyObject ret = thisVal.callMethod(context, "==", thatVal);
|
|
IRubyObject ret = thisVal.callMethod(context, "==", thatVal);
|
|
if (!ret.isTrue()) {
|
|
if (!ret.isTrue()) {
|
|
return runtime.getFalse();
|
|
return runtime.getFalse();
|
|
@@ -225,46 +255,153 @@ public class RubyMessage extends RubyObject {
|
|
* call-seq:
|
|
* call-seq:
|
|
* Message.method_missing(*args)
|
|
* 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
|
|
|
|
|
|
+ * Provides accessors and setters and methods to clear and check for presence of
|
|
|
|
+ * 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
|
|
* 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
|
|
* provided with the name of the field plus the '=' suffix. Thus, given a
|
|
* message instance 'msg' with field 'foo', the following code is valid:
|
|
* message instance 'msg' with field 'foo', the following code is valid:
|
|
*
|
|
*
|
|
* msg.foo = 42
|
|
* msg.foo = 42
|
|
* puts msg.foo
|
|
* puts msg.foo
|
|
|
|
+ *
|
|
|
|
+ * This method also provides read-only accessors for oneofs. If a oneof exists
|
|
|
|
+ * with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to
|
|
|
|
+ * the name of the field in that oneof that is currently set, or nil if none.
|
|
|
|
+ *
|
|
|
|
+ * It also provides methods of the form 'clear_fieldname' to clear the value
|
|
|
|
+ * of the field 'fieldname'. For basic data types, this will set the default
|
|
|
|
+ * value of the field.
|
|
|
|
+ *
|
|
|
|
+ * Additionally, it provides methods of the form 'has_fieldname?', which returns
|
|
|
|
+ * true if the field 'fieldname' is set in the message object, else false. For
|
|
|
|
+ * 'proto3' syntax, calling this for a basic type field will result in an error.
|
|
*/
|
|
*/
|
|
@JRubyMethod(name = "method_missing", rest = true)
|
|
@JRubyMethod(name = "method_missing", rest = true)
|
|
public IRubyObject methodMissing(ThreadContext context, IRubyObject[] args) {
|
|
public IRubyObject methodMissing(ThreadContext context, IRubyObject[] args) {
|
|
|
|
+ Ruby runtime = context.runtime;
|
|
|
|
+ String methodName = args[0].asJavaString();
|
|
|
|
+
|
|
if (args.length == 1) {
|
|
if (args.length == 1) {
|
|
RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
|
|
RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
|
|
|
|
+
|
|
|
|
+ // If we find a Oneof return it's name (use lookupOneof because it has an index)
|
|
IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]);
|
|
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);
|
|
|
|
|
|
+
|
|
|
|
+ if (!oneofDescriptor.isNil()) {
|
|
|
|
+ RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor;
|
|
|
|
+ FieldDescriptor fieldDescriptor = oneofCases.get(rubyOneofDescriptor.getDescriptor());
|
|
|
|
+
|
|
|
|
+ return fieldDescriptor == null ? context.nil : runtime.newSymbol(fieldDescriptor.getName());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If we find a field return its value
|
|
|
|
+ FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName);
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor != null) {
|
|
|
|
+ return getFieldInternal(context, fieldDescriptor);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (methodName.startsWith(CLEAR_PREFIX)) {
|
|
|
|
+ methodName = methodName.substring(6);
|
|
|
|
+ oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName));
|
|
|
|
+
|
|
|
|
+ if (!oneofDescriptor.isNil()) {
|
|
|
|
+ fieldDescriptor = oneofCases.get(((RubyOneofDescriptor) oneofDescriptor).getDescriptor());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor == null) {
|
|
|
|
+ fieldDescriptor = descriptor.findFieldByName(methodName);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor != null) {
|
|
|
|
+ return clearFieldInternal(context, fieldDescriptor);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) {
|
|
|
|
+ methodName = methodName.substring(4, methodName.length() - 1); // Trim "has_" and "?" off the field name
|
|
|
|
+ oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName));
|
|
|
|
+ if (!oneofDescriptor.isNil()) {
|
|
|
|
+ RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor;
|
|
|
|
+ return oneofCases.containsKey(rubyOneofDescriptor.getDescriptor()) ? runtime.getTrue() : runtime.getFalse();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fieldDescriptor = descriptor.findFieldByName(methodName);
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor != null &&
|
|
|
|
+ (!proto3 || fieldDescriptor.getContainingOneof() == null) && // This seems like a bug but its needed to pass the tests...
|
|
|
|
+ fieldHasPresence(fieldDescriptor)) {
|
|
|
|
+ return fields.containsKey(fieldDescriptor) ? runtime.getTrue() : runtime.getFalse();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else if (methodName.endsWith(AS_VALUE_SUFFIX)) {
|
|
|
|
+ methodName = methodName.substring(0, methodName.length() - 9);
|
|
|
|
+ fieldDescriptor = descriptor.findFieldByName(methodName);
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor != null && isWrappable(fieldDescriptor)) {
|
|
|
|
+ IRubyObject value = getFieldInternal(context, fieldDescriptor);
|
|
|
|
+
|
|
|
|
+ if (!value.isNil() && value instanceof RubyMessage) {
|
|
|
|
+ return ((RubyMessage) value).index(context, runtime.newString("value"));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return value;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else if (methodName.endsWith(CONST_SUFFIX)) {
|
|
|
|
+ methodName = methodName.substring(0, methodName.length() - 6);
|
|
|
|
+ fieldDescriptor = descriptor.findFieldByName(methodName);
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) {
|
|
|
|
+ IRubyObject enumValue = getFieldInternal(context, fieldDescriptor);
|
|
|
|
+
|
|
|
|
+ if (!enumValue.isNil()) {
|
|
|
|
+ EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
|
|
|
|
+ if (enumValue instanceof RubyRepeatedField) {
|
|
|
|
+ RubyArray values = (RubyArray) ((RubyRepeatedField) enumValue).toArray(context);
|
|
|
|
+ RubyArray retValues = runtime.newArray(values.getLength());
|
|
|
|
+ for (int i = 0; i < values.getLength(); i++) {
|
|
|
|
+ String val = values.eltInternal(i).toString();
|
|
|
|
+ retValues.store((long) i, runtime.newFixnum(enumDescriptor.findValueByName(val).getNumber()));
|
|
|
|
+ }
|
|
|
|
+ return retValues;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return runtime.newFixnum(enumDescriptor.findValueByName(enumValue.asJavaString()).getNumber());
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- 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);
|
|
|
|
|
|
+ } else if (args.length == 2 && methodName.endsWith(Utils.EQUAL_SIGN)) {
|
|
|
|
+
|
|
|
|
+ methodName = methodName.substring(0, methodName.length() - 1); // Trim equals sign
|
|
|
|
+ FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName);
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor != null) {
|
|
|
|
+ return setFieldInternal(context, fieldDescriptor, args[1]);
|
|
}
|
|
}
|
|
|
|
|
|
- if (!hasField(field)) {
|
|
|
|
- return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK);
|
|
|
|
|
|
+ if (methodName.endsWith(AS_VALUE_SUFFIX)) {
|
|
|
|
+ methodName = methodName.substring(0, methodName.length() - 9);
|
|
|
|
+
|
|
|
|
+ fieldDescriptor = descriptor.findFieldByName(methodName);
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor != null) {
|
|
|
|
+ if (args[1].isNil()) {
|
|
|
|
+ return setFieldInternal(context, fieldDescriptor, args[1]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
|
|
|
|
+ RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
|
|
|
|
+ msg.indexSet(context, runtime.newString("value"), args[1]);
|
|
|
|
+ return setFieldInternal(context, fieldDescriptor, msg);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- return indexSet(context, field, args[1]);
|
|
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -276,18 +413,15 @@ public class RubyMessage extends RubyObject {
|
|
public IRubyObject dup(ThreadContext context) {
|
|
public IRubyObject dup(ThreadContext context) {
|
|
RubyMessage dup = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
|
|
RubyMessage dup = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
|
|
IRubyObject value;
|
|
IRubyObject value;
|
|
- for (Descriptors.FieldDescriptor fieldDescriptor : this.descriptor.getFields()) {
|
|
|
|
|
|
+ for (FieldDescriptor fieldDescriptor : this.descriptor.getFields()) {
|
|
if (fieldDescriptor.isRepeated()) {
|
|
if (fieldDescriptor.isRepeated()) {
|
|
- dup.addRepeatedField(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor));
|
|
|
|
|
|
+ dup.fields.put(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor));
|
|
} else if (fields.containsKey(fieldDescriptor)) {
|
|
} else if (fields.containsKey(fieldDescriptor)) {
|
|
dup.fields.put(fieldDescriptor, fields.get(fieldDescriptor));
|
|
dup.fields.put(fieldDescriptor, fields.get(fieldDescriptor));
|
|
} else if (this.builder.hasField(fieldDescriptor)) {
|
|
} else if (this.builder.hasField(fieldDescriptor)) {
|
|
dup.fields.put(fieldDescriptor, wrapField(context, fieldDescriptor, this.builder.getField(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;
|
|
return dup;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -312,6 +446,9 @@ public class RubyMessage extends RubyObject {
|
|
*/
|
|
*/
|
|
@JRubyMethod(meta = true)
|
|
@JRubyMethod(meta = true)
|
|
public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject value) {
|
|
public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject value) {
|
|
|
|
+ if (recv != value.getMetaClass()) {
|
|
|
|
+ throw context.runtime.newArgumentError("Tried to encode a " + value.getMetaClass() + " message with " + recv);
|
|
|
|
+ }
|
|
RubyMessage message = (RubyMessage) value;
|
|
RubyMessage message = (RubyMessage) value;
|
|
return context.runtime.newString(new ByteList(message.build(context).toByteArray()));
|
|
return context.runtime.newString(new ByteList(message.build(context).toByteArray()));
|
|
}
|
|
}
|
|
@@ -333,38 +470,108 @@ public class RubyMessage extends RubyObject {
|
|
} catch (InvalidProtocolBufferException e) {
|
|
} catch (InvalidProtocolBufferException e) {
|
|
throw context.runtime.newRuntimeError(e.getMessage());
|
|
throw context.runtime.newRuntimeError(e.getMessage());
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (!ret.proto3) {
|
|
|
|
+ // Need to reset unknown values in repeated enum fields
|
|
|
|
+ ret.builder.getUnknownFields().asMap().forEach((i, values) -> {
|
|
|
|
+ FieldDescriptor fd = ret.builder.getDescriptorForType().findFieldByNumber(i);
|
|
|
|
+ if (fd != null && fd.isRepeated() && fd.getType() == FieldDescriptor.Type.ENUM) {
|
|
|
|
+ EnumDescriptor ed = fd.getEnumType();
|
|
|
|
+ values.getVarintList().forEach(value -> {
|
|
|
|
+ ret.builder.addRepeatedField(fd, ed.findValueByNumberCreatingIfUnknown(value.intValue()));
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
* call-seq:
|
|
* call-seq:
|
|
- * MessageClass.encode_json(msg) => json_string
|
|
|
|
|
|
+ * MessageClass.encode_json(msg, options = {}) => json_string
|
|
*
|
|
*
|
|
* Encodes the given message object into its serialized JSON representation.
|
|
* Encodes the given message object into its serialized JSON representation.
|
|
|
|
+ * @param options [Hash] options for the decoder
|
|
|
|
+ * preserve_proto_fieldnames: set true to use original fieldnames (default is to camelCase)
|
|
|
|
+ * emit_defaults: set true to emit 0/false values (default is to omit them)
|
|
*/
|
|
*/
|
|
- @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");
|
|
|
|
|
|
+ @JRubyMethod(name = "encode_json", required = 1, optional = 1, meta = true)
|
|
|
|
+ public static IRubyObject encodeJson(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
|
|
|
|
+ Ruby runtime = context.runtime;
|
|
|
|
+ RubyMessage message = (RubyMessage) args[0];
|
|
|
|
+ JsonFormat.Printer printer = JsonFormat.printer().omittingInsignificantWhitespace();
|
|
|
|
+ String result;
|
|
|
|
+
|
|
|
|
+ if (args.length > 1) {
|
|
|
|
+ RubyHash options = (RubyHash) args[1];
|
|
|
|
+ IRubyObject emitDefaults = options.fastARef(runtime.newSymbol("emit_defaults"));
|
|
|
|
+ IRubyObject preserveNames = options.fastARef(runtime.newSymbol("preserve_proto_fieldnames"));
|
|
|
|
+
|
|
|
|
+ if (emitDefaults != null && emitDefaults.isTrue()) {
|
|
|
|
+ printer = printer.includingDefaultValueFields();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (preserveNames != null && preserveNames.isTrue()) {
|
|
|
|
+ printer = printer.preservingProtoFieldNames();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ result = printer.print(message.build(context));
|
|
|
|
+ } catch(InvalidProtocolBufferException e) {
|
|
|
|
+ throw runtime.newRuntimeError(e.getMessage());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return runtime.newString(result);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
* call-seq:
|
|
* call-seq:
|
|
- * MessageClass.decode_json(data) => message
|
|
|
|
|
|
+ * MessageClass.decode_json(data, options = {}) => message
|
|
*
|
|
*
|
|
* Decodes the given data (as a string containing bytes in protocol buffers wire
|
|
* Decodes the given data (as a string containing bytes in protocol buffers wire
|
|
* format) under the interpretration given by this message class's definition
|
|
* format) under the interpretration given by this message class's definition
|
|
* and returns a message object with the corresponding field values.
|
|
* and returns a message object with the corresponding field values.
|
|
|
|
+ *
|
|
|
|
+ * @param options [Hash] options for the decoder
|
|
|
|
+ * ignore_unknown_fields: set true to ignore unknown fields (default is to
|
|
|
|
+ * raise an error)
|
|
*/
|
|
*/
|
|
- @JRubyMethod(name = "decode_json", meta = true)
|
|
|
|
- public static IRubyObject decodeJson(ThreadContext context, IRubyObject recv, IRubyObject json) {
|
|
|
|
|
|
+ @JRubyMethod(name = "decode_json", required = 1, optional = 1, meta = true)
|
|
|
|
+ public static IRubyObject decodeJson(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
|
|
Ruby runtime = context.runtime;
|
|
Ruby runtime = context.runtime;
|
|
|
|
+ boolean ignoreUnknownFields = false;
|
|
|
|
+ IRubyObject data = args[0];
|
|
|
|
+ JsonFormat.Parser parser = JsonFormat.parser();
|
|
|
|
+
|
|
|
|
+ if (args.length == 2) {
|
|
|
|
+ if (!(args[1] instanceof RubyHash)) {
|
|
|
|
+ throw runtime.newArgumentError("Expected hash arguments.");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ IRubyObject ignoreSetting = ((RubyHash) args[1]).fastARef(runtime.newSymbol("ignore_unknown_fields"));
|
|
|
|
+ if (ignoreSetting != null && ignoreSetting.isTrue()) {
|
|
|
|
+ parser = parser.ignoringUnknownFields();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!(data instanceof RubyString)) {
|
|
|
|
+ throw runtime.newArgumentError("Expected string for JSON data.");
|
|
|
|
+ }
|
|
|
|
+
|
|
RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
|
|
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);
|
|
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ parser.merge(data.asJavaString(), ret.builder);
|
|
|
|
+ } catch(InvalidProtocolBufferException e) {
|
|
|
|
+ throw createParseError(context, e.getMessage().replace("Cannot find", "No such"));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (isWrapper(ret.descriptor)) {
|
|
|
|
+ throw runtime.newRuntimeError("Parsing a wrapper type from JSON at the top level does not work.");
|
|
|
|
+ }
|
|
|
|
+
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -372,11 +579,13 @@ public class RubyMessage extends RubyObject {
|
|
public IRubyObject toHash(ThreadContext context) {
|
|
public IRubyObject toHash(ThreadContext context) {
|
|
Ruby runtime = context.runtime;
|
|
Ruby runtime = context.runtime;
|
|
RubyHash ret = RubyHash.newHash(runtime);
|
|
RubyHash ret = RubyHash.newHash(runtime);
|
|
- for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
|
|
|
|
- IRubyObject value = getField(context, fdef);
|
|
|
|
|
|
+ for (FieldDescriptor fdef : this.descriptor.getFields()) {
|
|
|
|
+ IRubyObject value = getFieldInternal(context, fdef, proto3);
|
|
|
|
+
|
|
if (!value.isNil()) {
|
|
if (!value.isNil()) {
|
|
if (fdef.isRepeated() && !fdef.isMapField()) {
|
|
if (fdef.isRepeated() && !fdef.isMapField()) {
|
|
- if (fdef.getType() != Descriptors.FieldDescriptor.Type.MESSAGE) {
|
|
|
|
|
|
+ if (!proto3 && ((RubyRepeatedField) value).size() == 0) continue; // Don't output empty repeated fields for proto2
|
|
|
|
+ if (fdef.getType() != FieldDescriptor.Type.MESSAGE) {
|
|
value = Helpers.invoke(context, value, "to_a");
|
|
value = Helpers.invoke(context, value, "to_a");
|
|
} else {
|
|
} else {
|
|
RubyArray ary = value.convertToArray();
|
|
RubyArray ary = value.convertToArray();
|
|
@@ -393,7 +602,9 @@ public class RubyMessage extends RubyObject {
|
|
value = Helpers.invoke(context, value, "to_a");
|
|
value = Helpers.invoke(context, value, "to_a");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- ret.fastASet(runtime.newSymbol(fdef.getName()), value);
|
|
|
|
|
|
+ if (proto3 || !value.isNil()) {
|
|
|
|
+ ret.fastASet(runtime.newSymbol(fdef.getName()), value);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
@@ -406,176 +617,197 @@ public class RubyMessage extends RubyObject {
|
|
if (depth > SINK_MAXIMUM_NESTING) {
|
|
if (depth > SINK_MAXIMUM_NESTING) {
|
|
throw context.runtime.newRuntimeError("Maximum recursion depth exceeded during encoding.");
|
|
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()) {
|
|
|
|
|
|
+ for (FieldDescriptor fieldDescriptor : fields.keySet()) {
|
|
IRubyObject value = fields.get(fieldDescriptor);
|
|
IRubyObject value = fields.get(fieldDescriptor);
|
|
- this.builder.setField(fieldDescriptor, convert(context, fieldDescriptor, value, depth));
|
|
|
|
|
|
+
|
|
|
|
+ if (value instanceof RubyMap) {
|
|
|
|
+ builder.clearField(fieldDescriptor);
|
|
|
|
+ RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
|
|
+ for (DynamicMessage kv : ((RubyMap) value).build(context, mapDescriptor, depth)) {
|
|
|
|
+ builder.addRepeatedField(fieldDescriptor, kv);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else if (value instanceof RubyRepeatedField) {
|
|
|
|
+ RubyRepeatedField repeatedField = (RubyRepeatedField) value;
|
|
|
|
+
|
|
|
|
+ builder.clearField(fieldDescriptor);
|
|
|
|
+ for (int i = 0; i < repeatedField.size(); i++) {
|
|
|
|
+ Object item = convert(context, fieldDescriptor, repeatedField.get(i), depth);
|
|
|
|
+ builder.addRepeatedField(fieldDescriptor, item);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ builder.setField(fieldDescriptor, convert(context, fieldDescriptor, value, depth));
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- return this.builder.build();
|
|
|
|
- }
|
|
|
|
|
|
|
|
- protected Descriptors.Descriptor getDescriptor() {
|
|
|
|
- return this.descriptor;
|
|
|
|
|
|
+ return builder.build();
|
|
}
|
|
}
|
|
|
|
|
|
// Internal use only, called by Google::Protobuf.deep_copy
|
|
// Internal use only, called by Google::Protobuf.deep_copy
|
|
protected IRubyObject deepCopy(ThreadContext context) {
|
|
protected IRubyObject deepCopy(ThreadContext context) {
|
|
RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
|
|
RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
|
|
- for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
|
|
|
|
|
|
+ for (FieldDescriptor fdef : descriptor.getFields()) {
|
|
if (fdef.isRepeated()) {
|
|
if (fdef.isRepeated()) {
|
|
- copy.addRepeatedField(fdef, this.getRepeatedField(context, fdef).deepCopy(context));
|
|
|
|
|
|
+ copy.fields.put(fdef, this.getRepeatedField(context, fdef).deepCopy(context));
|
|
} else if (fields.containsKey(fdef)) {
|
|
} else if (fields.containsKey(fdef)) {
|
|
copy.fields.put(fdef, fields.get(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)));
|
|
|
|
|
|
+ } else if (builder.hasField(fdef)) {
|
|
|
|
+ copy.fields.put(fdef, wrapField(context, fdef, builder.getField(fdef)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return copy;
|
|
return copy;
|
|
}
|
|
}
|
|
|
|
|
|
- private RubyRepeatedField getRepeatedField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
|
|
|
|
- if (this.repeatedFields.containsKey(fieldDescriptor)) {
|
|
|
|
- return this.repeatedFields.get(fieldDescriptor);
|
|
|
|
|
|
+ protected IRubyObject clearField(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
|
|
+ validateMessageType(context, fieldDescriptor, "clear");
|
|
|
|
+ return clearFieldInternal(context, fieldDescriptor);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected void discardUnknownFields(ThreadContext context) {
|
|
|
|
+ discardUnknownFields(context, builder);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected IRubyObject getField(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
|
|
+ validateMessageType(context, fieldDescriptor, "get");
|
|
|
|
+ return getFieldInternal(context, fieldDescriptor);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected IRubyObject hasField(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
|
|
+ validateMessageType(context, fieldDescriptor, "has?");
|
|
|
|
+ if (!fieldHasPresence(fieldDescriptor)) throw context.runtime.newArgumentError("does not track presence");
|
|
|
|
+ return fields.containsKey(fieldDescriptor) ? context.runtime.getTrue() : context.runtime.getFalse();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected IRubyObject setField(ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) {
|
|
|
|
+ validateMessageType(context, fieldDescriptor, "set");
|
|
|
|
+ return setFieldInternal(context, fieldDescriptor, value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private RubyRepeatedField getRepeatedField(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
|
|
+ if (fields.containsKey(fieldDescriptor)) {
|
|
|
|
+ return (RubyRepeatedField) fields.get(fieldDescriptor);
|
|
}
|
|
}
|
|
int count = this.builder.getRepeatedFieldCount(fieldDescriptor);
|
|
int count = this.builder.getRepeatedFieldCount(fieldDescriptor);
|
|
RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
|
|
RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
|
|
for (int i = 0; i < count; i++) {
|
|
for (int i = 0; i < count; i++) {
|
|
- ret.push(context, wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i)));
|
|
|
|
|
|
+ ret.push(context, new IRubyObject[] {wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i))});
|
|
}
|
|
}
|
|
- addRepeatedField(fieldDescriptor, ret);
|
|
|
|
|
|
+ fields.put(fieldDescriptor, ret);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
- private void addRepeatedField(Descriptors.FieldDescriptor fieldDescriptor, RubyRepeatedField repeatedField) {
|
|
|
|
- this.repeatedFields.put(fieldDescriptor, repeatedField);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) {
|
|
private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) {
|
|
this.builder.mergeFrom(dynamicMessage);
|
|
this.builder.mergeFrom(dynamicMessage);
|
|
return this;
|
|
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 IRubyObject clearFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
|
|
+ OneofDescriptor ood = fieldDescriptor.getContainingOneof();
|
|
|
|
+ if (ood != null) oneofCases.remove(ood);
|
|
|
|
+ fields.remove(fieldDescriptor);
|
|
|
|
+ builder.clearField(fieldDescriptor);
|
|
|
|
+ return context.nil;
|
|
}
|
|
}
|
|
|
|
|
|
- private boolean hasField(IRubyObject fieldName) {
|
|
|
|
- String nameStr = fieldName.asJavaString();
|
|
|
|
- return this.descriptor.findFieldByName(Utils.escapeIdentifier(nameStr)) != null;
|
|
|
|
|
|
+ private void discardUnknownFields(ThreadContext context, Message.Builder messageBuilder) {
|
|
|
|
+ messageBuilder.setUnknownFields(UnknownFieldSet.getDefaultInstance());
|
|
|
|
+ messageBuilder.getAllFields().forEach((fd, value) -> {
|
|
|
|
+ if (fd.getType() == FieldDescriptor.Type.MESSAGE) {
|
|
|
|
+ if (fd.isRepeated()) {
|
|
|
|
+ messageBuilder.clearField(fd);
|
|
|
|
+ ((List) value).forEach((val) -> {
|
|
|
|
+ Message.Builder submessageBuilder = ((DynamicMessage) val).toBuilder();
|
|
|
|
+ discardUnknownFields(context, submessageBuilder);
|
|
|
|
+ messageBuilder.addRepeatedField(fd, submessageBuilder.build());
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ Message.Builder submessageBuilder = ((DynamicMessage) value).toBuilder();
|
|
|
|
+ discardUnknownFields(context, submessageBuilder);
|
|
|
|
+ messageBuilder.setField(fd, submessageBuilder.build());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
|
|
- 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");
|
|
|
|
|
|
+ private FieldDescriptor findField(ThreadContext context, IRubyObject fieldName) {
|
|
|
|
+ return findField(context, fieldName, false);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private FieldDescriptor findField(ThreadContext context, IRubyObject fieldName, boolean ignoreUnknownField) {
|
|
|
|
+ String nameStr = fieldName.asJavaString();
|
|
|
|
+ FieldDescriptor ret = this.descriptor.findFieldByName(nameStr);
|
|
|
|
+ if (ret == null && !ignoreUnknownField) {
|
|
|
|
+ throw context.runtime.newArgumentError("field " + fieldName.asJavaString() + " is not found");
|
|
}
|
|
}
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
- // convert a ruby object to protobuf type, with type check
|
|
|
|
|
|
+ // convert a ruby object to protobuf type, skip type check since it is checked on the way in
|
|
private Object convert(ThreadContext context,
|
|
private Object convert(ThreadContext context,
|
|
- Descriptors.FieldDescriptor fieldDescriptor,
|
|
|
|
|
|
+ FieldDescriptor fieldDescriptor,
|
|
IRubyObject value, int depth) {
|
|
IRubyObject value, int depth) {
|
|
Ruby runtime = context.runtime;
|
|
Ruby runtime = context.runtime;
|
|
Object val = null;
|
|
Object val = null;
|
|
switch (fieldDescriptor.getType()) {
|
|
switch (fieldDescriptor.getType()) {
|
|
case INT32:
|
|
case INT32:
|
|
|
|
+ val = RubyNumeric.num2int(value);
|
|
|
|
+ break;
|
|
case INT64:
|
|
case INT64:
|
|
|
|
+ val = RubyNumeric.num2long(value);
|
|
|
|
+ break;
|
|
case UINT32:
|
|
case UINT32:
|
|
|
|
+ val = Utils.num2uint(value);
|
|
|
|
+ break;
|
|
case UINT64:
|
|
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;
|
|
|
|
- }
|
|
|
|
|
|
+ val = Utils.num2ulong(context.runtime, value);
|
|
break;
|
|
break;
|
|
case FLOAT:
|
|
case FLOAT:
|
|
- if (!Utils.isRubyNum(value))
|
|
|
|
- throw runtime.newTypeError("Expected number type for float field.");
|
|
|
|
val = (float) RubyNumeric.num2dbl(value);
|
|
val = (float) RubyNumeric.num2dbl(value);
|
|
break;
|
|
break;
|
|
case DOUBLE:
|
|
case DOUBLE:
|
|
- if (!Utils.isRubyNum(value))
|
|
|
|
- throw runtime.newTypeError("Expected number type for double field.");
|
|
|
|
- val = RubyNumeric.num2dbl(value);
|
|
|
|
|
|
+ val = (double) RubyNumeric.num2dbl(value);
|
|
break;
|
|
break;
|
|
case BOOL:
|
|
case BOOL:
|
|
- if (!(value instanceof RubyBoolean))
|
|
|
|
- throw runtime.newTypeError("Invalid argument for boolean field.");
|
|
|
|
val = value.isTrue();
|
|
val = value.isTrue();
|
|
break;
|
|
break;
|
|
case BYTES:
|
|
case BYTES:
|
|
- Utils.validateStringEncoding(context, fieldDescriptor.getType(), value);
|
|
|
|
val = ByteString.copyFrom(((RubyString) value).getBytes());
|
|
val = ByteString.copyFrom(((RubyString) value).getBytes());
|
|
break;
|
|
break;
|
|
case STRING:
|
|
case STRING:
|
|
- Utils.validateStringEncoding(context, fieldDescriptor.getType(), value);
|
|
|
|
val = ((RubyString) value).asJavaString();
|
|
val = ((RubyString) value).asJavaString();
|
|
break;
|
|
break;
|
|
case MESSAGE:
|
|
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);
|
|
val = ((RubyMessage) value).build(context, depth + 1);
|
|
break;
|
|
break;
|
|
case ENUM:
|
|
case ENUM:
|
|
- Descriptors.EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
|
|
|
|
-
|
|
|
|
|
|
+ EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
|
|
if (Utils.isRubyNum(value)) {
|
|
if (Utils.isRubyNum(value)) {
|
|
val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
|
|
val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
|
|
- } else if (value instanceof RubySymbol || value instanceof RubyString) {
|
|
|
|
- val = enumDescriptor.findValueByName(value.asJavaString());
|
|
|
|
} else {
|
|
} else {
|
|
- throw runtime.newTypeError("Expected number or symbol type for enum field.");
|
|
|
|
- }
|
|
|
|
- if (val == null) {
|
|
|
|
- throw runtime.newRangeError("Enum value " + value + " is not found.");
|
|
|
|
|
|
+ val = enumDescriptor.findValueByName(value.asJavaString());
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
default:
|
|
default:
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
+
|
|
return val;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
|
|
- private IRubyObject wrapField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, Object value) {
|
|
|
|
|
|
+ private static RaiseException createParseError(ThreadContext context, String message) {
|
|
|
|
+ if (parseErrorClass == null) {
|
|
|
|
+ parseErrorClass = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError");
|
|
|
|
+ }
|
|
|
|
+ return RaiseException.from(context.runtime, parseErrorClass, message);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private IRubyObject wrapField(ThreadContext context, FieldDescriptor fieldDescriptor, Object value) {
|
|
if (value == null) {
|
|
if (value == null) {
|
|
return context.runtime.getNil();
|
|
return context.runtime.getNil();
|
|
}
|
|
}
|
|
Ruby runtime = context.runtime;
|
|
Ruby runtime = context.runtime;
|
|
|
|
+
|
|
switch (fieldDescriptor.getType()) {
|
|
switch (fieldDescriptor.getType()) {
|
|
case INT32:
|
|
case INT32:
|
|
case INT64:
|
|
case INT64:
|
|
@@ -592,7 +824,7 @@ public class RubyMessage extends RubyObject {
|
|
RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
|
|
RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
|
|
return msg.buildFrom(context, (DynamicMessage) value);
|
|
return msg.buildFrom(context, (DynamicMessage) value);
|
|
case ENUM:
|
|
case ENUM:
|
|
- Descriptors.EnumValueDescriptor enumValueDescriptor = (Descriptors.EnumValueDescriptor) value;
|
|
|
|
|
|
+ EnumValueDescriptor enumValueDescriptor = (EnumValueDescriptor) value;
|
|
if (enumValueDescriptor.getIndex() == -1) { // UNKNOWN ENUM VALUE
|
|
if (enumValueDescriptor.getIndex() == -1) { // UNKNOWN ENUM VALUE
|
|
return runtime.newFixnum(enumValueDescriptor.getNumber());
|
|
return runtime.newFixnum(enumValueDescriptor.getNumber());
|
|
}
|
|
}
|
|
@@ -602,48 +834,59 @@ public class RubyMessage extends RubyObject {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private RubyRepeatedField repeatedFieldForFieldDescriptor(ThreadContext context,
|
|
|
|
- Descriptors.FieldDescriptor fieldDescriptor) {
|
|
|
|
|
|
+ private RubyRepeatedField repeatedFieldForFieldDescriptor(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
IRubyObject typeClass = context.runtime.getNilClass();
|
|
IRubyObject typeClass = context.runtime.getNilClass();
|
|
-
|
|
|
|
IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor);
|
|
IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor);
|
|
- Descriptors.FieldDescriptor.Type type = fieldDescriptor.getType();
|
|
|
|
- if (type == Descriptors.FieldDescriptor.Type.MESSAGE) {
|
|
|
|
|
|
+ FieldDescriptor.Type type = fieldDescriptor.getType();
|
|
|
|
+
|
|
|
|
+ if (type == FieldDescriptor.Type.MESSAGE) {
|
|
typeClass = ((RubyDescriptor) descriptor).msgclass(context);
|
|
typeClass = ((RubyDescriptor) descriptor).msgclass(context);
|
|
|
|
|
|
- } else if (type == Descriptors.FieldDescriptor.Type.ENUM) {
|
|
|
|
|
|
+ } else if (type == FieldDescriptor.Type.ENUM) {
|
|
typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context);
|
|
typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context);
|
|
}
|
|
}
|
|
- return new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass);
|
|
|
|
|
|
+
|
|
|
|
+ RubyRepeatedField field = new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass);
|
|
|
|
+ field.setName(fieldDescriptor.getName());
|
|
|
|
+
|
|
|
|
+ return field;
|
|
}
|
|
}
|
|
|
|
|
|
- protected IRubyObject getField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
|
|
|
|
- Descriptors.OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
|
|
|
|
|
|
+ private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
|
|
+ return getFieldInternal(context, fieldDescriptor, true);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor, boolean returnDefaults) {
|
|
|
|
+ OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
|
|
if (oneofDescriptor != null) {
|
|
if (oneofDescriptor != null) {
|
|
if (oneofCases.get(oneofDescriptor) == fieldDescriptor) {
|
|
if (oneofCases.get(oneofDescriptor) == fieldDescriptor) {
|
|
return fields.get(fieldDescriptor);
|
|
return fields.get(fieldDescriptor);
|
|
} else {
|
|
} else {
|
|
- Descriptors.FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
|
|
|
|
|
|
+ FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
|
|
if (oneofCase != fieldDescriptor) {
|
|
if (oneofCase != fieldDescriptor) {
|
|
- if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
|
|
|
|
- return context.runtime.getNil();
|
|
|
|
|
|
+ if (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE || !returnDefaults) {
|
|
|
|
+ return context.nil;
|
|
} else {
|
|
} else {
|
|
return wrapField(context, fieldDescriptor, fieldDescriptor.getDefaultValue());
|
|
return wrapField(context, fieldDescriptor, fieldDescriptor.getDefaultValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- IRubyObject value = wrapField(context, oneofCase, builder.getField(oneofCase));
|
|
|
|
- fields.put(fieldDescriptor, value);
|
|
|
|
- return value;
|
|
|
|
|
|
+ if (returnDefaults || builder.hasField(fieldDescriptor)) {
|
|
|
|
+ IRubyObject value = wrapField(context, oneofCase, builder.getField(oneofCase));
|
|
|
|
+ fields.put(fieldDescriptor, value);
|
|
|
|
+ return value;
|
|
|
|
+ } else {
|
|
|
|
+ return context.nil;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (Utils.isMapEntry(fieldDescriptor)) {
|
|
if (Utils.isMapEntry(fieldDescriptor)) {
|
|
- RubyMap map = maps.get(fieldDescriptor);
|
|
|
|
|
|
+ RubyMap map = (RubyMap) fields.get(fieldDescriptor);
|
|
if (map == null) {
|
|
if (map == null) {
|
|
map = newMapForField(context, fieldDescriptor);
|
|
map = newMapForField(context, fieldDescriptor);
|
|
int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor);
|
|
int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor);
|
|
- Descriptors.FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
|
|
|
|
- Descriptors.FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
|
|
|
|
|
|
+ FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
|
|
|
|
+ FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
|
|
RubyDescriptor kvDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
RubyDescriptor kvDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
RubyClass kvClass = (RubyClass) kvDescriptor.msgclass(context);
|
|
RubyClass kvClass = (RubyClass) kvDescriptor.msgclass(context);
|
|
for (int i = 0; i < mapSize; i++) {
|
|
for (int i = 0; i < mapSize; i++) {
|
|
@@ -652,158 +895,234 @@ public class RubyMessage extends RubyObject {
|
|
kvMessage.buildFrom(context, message);
|
|
kvMessage.buildFrom(context, message);
|
|
map.indexSet(context, kvMessage.getField(context, keyField), kvMessage.getField(context, valueField));
|
|
map.indexSet(context, kvMessage.getField(context, keyField), kvMessage.getField(context, valueField));
|
|
}
|
|
}
|
|
- maps.put(fieldDescriptor, map);
|
|
|
|
|
|
+ fields.put(fieldDescriptor, map);
|
|
}
|
|
}
|
|
return map;
|
|
return map;
|
|
}
|
|
}
|
|
|
|
+
|
|
if (fieldDescriptor.isRepeated()) {
|
|
if (fieldDescriptor.isRepeated()) {
|
|
return getRepeatedField(context, fieldDescriptor);
|
|
return getRepeatedField(context, fieldDescriptor);
|
|
}
|
|
}
|
|
- if (fieldDescriptor.getType() != Descriptors.FieldDescriptor.Type.MESSAGE ||
|
|
|
|
- this.builder.hasField(fieldDescriptor) || fields.containsKey(fieldDescriptor)) {
|
|
|
|
|
|
+
|
|
|
|
+ if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE ||
|
|
|
|
+ builder.hasField(fieldDescriptor) || fields.containsKey(fieldDescriptor)) {
|
|
if (fields.containsKey(fieldDescriptor)) {
|
|
if (fields.containsKey(fieldDescriptor)) {
|
|
return fields.get(fieldDescriptor);
|
|
return fields.get(fieldDescriptor);
|
|
- } else {
|
|
|
|
- IRubyObject value = wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor));
|
|
|
|
- if (this.builder.hasField(fieldDescriptor)) {
|
|
|
|
|
|
+ } else if (returnDefaults || builder.hasField(fieldDescriptor)) {
|
|
|
|
+ IRubyObject value = wrapField(context, fieldDescriptor, builder.getField(fieldDescriptor));
|
|
|
|
+ if (builder.hasField(fieldDescriptor)) {
|
|
fields.put(fieldDescriptor, value);
|
|
fields.put(fieldDescriptor, value);
|
|
}
|
|
}
|
|
return value;
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return context.runtime.getNil();
|
|
|
|
|
|
+ return context.nil;
|
|
}
|
|
}
|
|
|
|
|
|
- protected IRubyObject setField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
|
|
|
|
|
|
+ private IRubyObject setFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) {
|
|
|
|
+ testFrozen("can't modify frozen " + getMetaClass());
|
|
|
|
+
|
|
if (Utils.isMapEntry(fieldDescriptor)) {
|
|
if (Utils.isMapEntry(fieldDescriptor)) {
|
|
if (!(value instanceof RubyMap)) {
|
|
if (!(value instanceof RubyMap)) {
|
|
- throw context.runtime.newTypeError("Expected Map instance");
|
|
|
|
|
|
+ throw Utils.createTypeError(context, "Expected Map instance");
|
|
}
|
|
}
|
|
- RubyMap thisMap = (RubyMap) getField(context, fieldDescriptor);
|
|
|
|
|
|
+ RubyMap thisMap = (RubyMap) getFieldInternal(context, fieldDescriptor);
|
|
thisMap.mergeIntoSelf(context, value);
|
|
thisMap.mergeIntoSelf(context, value);
|
|
|
|
+
|
|
} else if (fieldDescriptor.isRepeated()) {
|
|
} else if (fieldDescriptor.isRepeated()) {
|
|
- checkRepeatedFieldType(context, value, fieldDescriptor);
|
|
|
|
if (value instanceof RubyRepeatedField) {
|
|
if (value instanceof RubyRepeatedField) {
|
|
- addRepeatedField(fieldDescriptor, (RubyRepeatedField) value);
|
|
|
|
|
|
+ fields.put(fieldDescriptor, value);
|
|
} else {
|
|
} else {
|
|
- RubyArray ary = value.convertToArray();
|
|
|
|
- RubyRepeatedField repeatedField = rubyToRepeatedField(context, fieldDescriptor, ary);
|
|
|
|
- addRepeatedField(fieldDescriptor, repeatedField);
|
|
|
|
|
|
+ throw Utils.createTypeError(context, "Expected repeated field array");
|
|
}
|
|
}
|
|
|
|
+
|
|
} else {
|
|
} else {
|
|
- Descriptors.OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
|
|
|
|
|
|
+ boolean addValue = true;
|
|
|
|
+ FieldDescriptor.Type fieldType = fieldDescriptor.getType();
|
|
|
|
+ OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
|
|
|
|
+
|
|
|
|
+ // Determine the typeclass, if any
|
|
|
|
+ IRubyObject typeClass = context.runtime.getObject();
|
|
|
|
+ if (fieldType == FieldDescriptor.Type.MESSAGE) {
|
|
|
|
+ typeClass = ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
|
|
|
|
+ if (value.isNil()){
|
|
|
|
+ addValue = false;
|
|
|
|
+ }
|
|
|
|
+ } else if (fieldType == FieldDescriptor.Type.ENUM) {
|
|
|
|
+ typeClass = ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor)).enummodule(context);
|
|
|
|
+ value = enumToSymbol(context, fieldDescriptor.getEnumType(), value);
|
|
|
|
+ }
|
|
|
|
+
|
|
if (oneofDescriptor != null) {
|
|
if (oneofDescriptor != null) {
|
|
- Descriptors.FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor);
|
|
|
|
|
|
+ FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor);
|
|
|
|
+
|
|
|
|
+ // Remove the existing field if we are setting a different field in the Oneof
|
|
if (oneofCase != null && oneofCase != fieldDescriptor) {
|
|
if (oneofCase != null && oneofCase != fieldDescriptor) {
|
|
fields.remove(oneofCase);
|
|
fields.remove(oneofCase);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // Keep track of what Oneofs are set
|
|
if (value.isNil()) {
|
|
if (value.isNil()) {
|
|
oneofCases.remove(oneofDescriptor);
|
|
oneofCases.remove(oneofDescriptor);
|
|
- fields.remove(fieldDescriptor);
|
|
|
|
|
|
+ addValue = false;
|
|
} else {
|
|
} else {
|
|
oneofCases.put(oneofDescriptor, fieldDescriptor);
|
|
oneofCases.put(oneofDescriptor, fieldDescriptor);
|
|
- fields.put(fieldDescriptor, value);
|
|
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (addValue) {
|
|
|
|
+ value = Utils.checkType(context, fieldType, fieldDescriptor.getName(), value, (RubyModule) typeClass);
|
|
|
|
+ fields.put(fieldDescriptor, value);
|
|
} else {
|
|
} 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);
|
|
|
|
- }
|
|
|
|
|
|
+ fields.remove(fieldDescriptor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return context.runtime.getNil();
|
|
|
|
|
|
+ return context.nil;
|
|
}
|
|
}
|
|
|
|
|
|
- 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(", ");
|
|
|
|
|
|
+ private IRubyObject getDescriptorForField(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
|
|
+ RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
|
|
|
|
+ RubyFieldDescriptor fd = (RubyFieldDescriptor) thisRbDescriptor.lookup(context, context.runtime.newString(fieldDescriptor.getName()));
|
|
|
|
+ return fd.getSubtype(context);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private IRubyObject enumToSymbol(ThreadContext context, EnumDescriptor enumDescriptor, IRubyObject value) {
|
|
|
|
+ if (value instanceof RubySymbol) {
|
|
|
|
+ return (RubySymbol) value;
|
|
|
|
+ } else if (Utils.isRubyNum(value)) {
|
|
|
|
+ EnumValueDescriptor enumValue = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
|
|
|
|
+ if (enumValue.getIndex() != -1) {
|
|
|
|
+ return context.runtime.newSymbol(enumValue.getName());
|
|
|
|
+ } else {
|
|
|
|
+ return value;
|
|
|
|
+ }
|
|
|
|
+ } else if (value instanceof RubyString) {
|
|
|
|
+ return ((RubyString) value).intern();
|
|
}
|
|
}
|
|
- return sb.substring(0, sb.length() - 2);
|
|
|
|
|
|
+
|
|
|
|
+ return context.runtime.newSymbol("UNKNOWN");
|
|
}
|
|
}
|
|
|
|
|
|
- private IRubyObject getDescriptorForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
|
|
|
|
- RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
|
|
|
|
- return thisRbDescriptor.lookup(fieldDescriptor.getName()).getSubType(context);
|
|
|
|
|
|
+ private boolean fieldHasPresence(FieldDescriptor fieldDescriptor) {
|
|
|
|
+ return !fieldDescriptor.isRepeated() &&
|
|
|
|
+ (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE ||
|
|
|
|
+ fieldDescriptor.getContainingOneof() != null ||
|
|
|
|
+ !proto3);
|
|
}
|
|
}
|
|
|
|
|
|
private RubyRepeatedField rubyToRepeatedField(ThreadContext context,
|
|
private RubyRepeatedField rubyToRepeatedField(ThreadContext context,
|
|
- Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
|
|
|
|
|
|
+ FieldDescriptor fieldDescriptor, IRubyObject value) {
|
|
RubyArray arr = value.convertToArray();
|
|
RubyArray arr = value.convertToArray();
|
|
RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
|
|
RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
|
|
|
|
+ IRubyObject[] values = new IRubyObject[arr.size()];
|
|
|
|
+ FieldDescriptor.Type fieldType = fieldDescriptor.getType();
|
|
|
|
+ String fieldName = fieldDescriptor.getName();
|
|
|
|
|
|
- RubyClass typeClass = null;
|
|
|
|
- if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
|
|
|
|
|
|
+ RubyModule typeClass = null;
|
|
|
|
+ if (fieldType == FieldDescriptor.Type.MESSAGE) {
|
|
RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
- typeClass = (RubyClass) descriptor.msgclass(context);
|
|
|
|
|
|
+ typeClass = (RubyModule) descriptor.msgclass(context);
|
|
|
|
+ } else if (fieldType == FieldDescriptor.Type.ENUM) {
|
|
|
|
+ RubyEnumDescriptor enumDescriptor = (RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
|
|
+ typeClass = (RubyModule) enumDescriptor.enummodule(context);
|
|
}
|
|
}
|
|
|
|
|
|
for (int i = 0; i < arr.size(); i++) {
|
|
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);
|
|
|
|
- }
|
|
|
|
|
|
+ IRubyObject item = arr.eltInternal(i);
|
|
|
|
+ if (item instanceof RubyHash && typeClass != null) {
|
|
|
|
+ values[i] = (IRubyObject) ((RubyClass) typeClass).newInstance(context, item, Block.NULL_BLOCK);
|
|
|
|
+ } else {
|
|
|
|
+ if (fieldType == FieldDescriptor.Type.ENUM) {
|
|
|
|
+ item = enumToSymbol(context, fieldDescriptor.getEnumType(), item);
|
|
|
|
+ }
|
|
|
|
|
|
- repeatedField.push(context, row);
|
|
|
|
|
|
+ values[i] = item;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ repeatedField.push(context, values);
|
|
|
|
+
|
|
return repeatedField;
|
|
return repeatedField;
|
|
}
|
|
}
|
|
|
|
|
|
- private RubyMap newMapForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
|
|
|
|
|
|
+ private RubyMap newMapForField(ThreadContext context, FieldDescriptor fieldDescriptor) {
|
|
RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
|
|
- Descriptors.FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
|
|
|
|
- Descriptors.FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
|
|
|
|
|
|
+ FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
|
|
|
|
+ FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
|
|
IRubyObject keyType = RubySymbol.newSymbol(context.runtime, keyField.getType().name());
|
|
IRubyObject keyType = RubySymbol.newSymbol(context.runtime, keyField.getType().name());
|
|
IRubyObject valueType = RubySymbol.newSymbol(context.runtime, valueField.getType().name());
|
|
IRubyObject valueType = RubySymbol.newSymbol(context.runtime, valueField.getType().name());
|
|
- if (valueField.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
|
|
|
|
|
|
+
|
|
|
|
+ if (valueField.getType() == FieldDescriptor.Type.MESSAGE) {
|
|
RubyFieldDescriptor rubyFieldDescriptor = (RubyFieldDescriptor) mapDescriptor.lookup(context,
|
|
RubyFieldDescriptor rubyFieldDescriptor = (RubyFieldDescriptor) mapDescriptor.lookup(context,
|
|
context.runtime.newString("value"));
|
|
context.runtime.newString("value"));
|
|
- RubyDescriptor rubyDescriptor = (RubyDescriptor) rubyFieldDescriptor.getSubType(context);
|
|
|
|
|
|
+ RubyDescriptor rubyDescriptor = (RubyDescriptor) rubyFieldDescriptor.getSubtype(context);
|
|
return (RubyMap) cMap.newInstance(context, keyType, valueType,
|
|
return (RubyMap) cMap.newInstance(context, keyType, valueType,
|
|
rubyDescriptor.msgclass(context), Block.NULL_BLOCK);
|
|
rubyDescriptor.msgclass(context), Block.NULL_BLOCK);
|
|
|
|
+
|
|
|
|
+ } else if (valueField.getType() == FieldDescriptor.Type.ENUM) {
|
|
|
|
+ RubyFieldDescriptor rubyFieldDescriptor = (RubyFieldDescriptor) mapDescriptor.lookup(context,
|
|
|
|
+ context.runtime.newString("value"));
|
|
|
|
+ RubyEnumDescriptor rubyEnumDescriptor = (RubyEnumDescriptor) rubyFieldDescriptor.getSubtype(context);
|
|
|
|
+ return (RubyMap) cMap.newInstance(context, keyType, valueType,
|
|
|
|
+ rubyEnumDescriptor.enummodule(context), Block.NULL_BLOCK);
|
|
|
|
+
|
|
} else {
|
|
} else {
|
|
return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK);
|
|
return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private Descriptors.FieldDescriptor getOneofCase(Descriptors.OneofDescriptor oneof) {
|
|
|
|
|
|
+ private FieldDescriptor getOneofCase(OneofDescriptor oneof) {
|
|
if (oneofCases.containsKey(oneof)) {
|
|
if (oneofCases.containsKey(oneof)) {
|
|
return oneofCases.get(oneof);
|
|
return oneofCases.get(oneof);
|
|
}
|
|
}
|
|
return builder.getOneofFieldDescriptor(oneof);
|
|
return builder.getOneofFieldDescriptor(oneof);
|
|
}
|
|
}
|
|
|
|
|
|
- private Descriptors.Descriptor descriptor;
|
|
|
|
|
|
+ private boolean isWrappable(FieldDescriptor fieldDescriptor) {
|
|
|
|
+ if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE) return false;
|
|
|
|
+
|
|
|
|
+ return isWrapper(fieldDescriptor.getMessageType());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static boolean isWrapper(Descriptor messageDescriptor) {
|
|
|
|
+ switch(messageDescriptor.getFullName()) {
|
|
|
|
+ case "google.protobuf.DoubleValue":
|
|
|
|
+ case "google.protobuf.FloatValue":
|
|
|
|
+ case "google.protobuf.Int64Value":
|
|
|
|
+ case "google.protobuf.UInt64Value":
|
|
|
|
+ case "google.protobuf.Int32Value":
|
|
|
|
+ case "google.protobuf.UInt32Value":
|
|
|
|
+ case "google.protobuf.BoolValue":
|
|
|
|
+ case "google.protobuf.StringValue":
|
|
|
|
+ case "google.protobuf.BytesValue":
|
|
|
|
+ return true;
|
|
|
|
+ default:
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void validateMessageType(ThreadContext context, FieldDescriptor fieldDescriptor, String methodName) {
|
|
|
|
+ if (descriptor != fieldDescriptor.getContainingType()) {
|
|
|
|
+ throw context.runtime.newTypeError(methodName + " method called on wrong message type");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static RubyClass parseErrorClass;
|
|
|
|
+
|
|
|
|
+ private static final String AS_VALUE_SUFFIX = "_as_value";
|
|
|
|
+ private static final String CLEAR_PREFIX = "clear_";
|
|
|
|
+ private static final String CONST_SUFFIX = "_const";
|
|
|
|
+ private static final String HAS_PREFIX = "has_";
|
|
|
|
+ private static final String QUESTION_MARK = "?";
|
|
|
|
+ private static final int SINK_MAXIMUM_NESTING = 63;
|
|
|
|
+
|
|
|
|
+ private Descriptor descriptor;
|
|
private DynamicMessage.Builder builder;
|
|
private DynamicMessage.Builder builder;
|
|
|
|
+ private Map<FieldDescriptor, IRubyObject> fields;
|
|
|
|
+ private Map<OneofDescriptor, FieldDescriptor> oneofCases;
|
|
private RubyClass cRepeatedField;
|
|
private RubyClass cRepeatedField;
|
|
private RubyClass cMap;
|
|
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 boolean ignoreUnknownFieldsOnInit = false;
|
|
|
|
+ private boolean proto3;
|
|
|
|
+
|
|
|
|
|
|
- private static final int SINK_MAXIMUM_NESTING = 64;
|
|
|
|
}
|
|
}
|