aboutsummaryrefslogtreecommitdiffhomepage
path: root/ruby/src
diff options
context:
space:
mode:
authorGravatar Isaiah Peng <issaria@gmail.com>2014-12-24 15:48:41 +0100
committerGravatar Isaiah Peng <issaria@gmail.com>2015-03-10 23:14:08 +0100
commit27e2b57830c328b83286e055752bf92790587953 (patch)
tree6cf4de2cbcb24d53b2475ed15d6a61a98712685c /ruby/src
parenta5f7bb8ebb60d636c21c18ad2ffeda80e8f80a48 (diff)
add jruby support by protobuf-java reflection API
Diffstat (limited to 'ruby/src')
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java167
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java267
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java169
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java86
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyEnumBuilderContext.java82
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java185
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java248
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java434
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java744
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyMessageBuilderContext.java217
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyOneofBuilderContext.java84
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java124
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java118
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java391
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java776
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/Utils.java300
-rw-r--r--ruby/src/main/java/google/ProtobufJavaService.java60
-rw-r--r--ruby/src/main/sentinel.proto15
18 files changed, 4467 insertions, 0 deletions
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java
new file mode 100644
index 00000000..5addae58
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java
@@ -0,0 +1,167 @@
+/*
+ * 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 org.jruby.*;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.*;
+import org.jruby.runtime.builtin.IRubyObject;
+
+@JRubyClass(name = "Builder")
+public class RubyBuilder extends RubyObject {
+ public static void createRubyBuilder(Ruby runtime) {
+ RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cBuilder = protobuf.defineClassUnder("Builder", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyBuilder(runtime, klazz);
+ }
+ });
+ cBuilder.defineAnnotatedMethods(RubyBuilder.class);
+ }
+
+ public RubyBuilder(Ruby runtime, RubyClass metaClass) {
+ super(runtime, metaClass);
+ this.cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor");
+ this.cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor");
+ this.cMessageBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::MessageBuilderContext");
+ this.cEnumBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumBuilderContext");
+ }
+
+ /*
+ * call-seq:
+ * Builder.new => builder
+ *
+ * Creates a new Builder. A Builder can accumulate a set of new message and enum
+ * descriptors and atomically register them into a pool in a way that allows for
+ * (co)recursive type references.
+ */
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context) {
+ Ruby runtime = context.runtime;
+ this.pendingList = runtime.newArray();
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * Builder.add_message(name, &block)
+ *
+ * Creates a new, empty descriptor with the given name, and invokes the block in
+ * the context of a MessageBuilderContext on that descriptor. The block can then
+ * call, e.g., MessageBuilderContext#optional and MessageBuilderContext#repeated
+ * methods to define the message fields.
+ *
+ * This is the recommended, idiomatic way to build message definitions.
+ */
+ @JRubyMethod(name = "add_message")
+ public IRubyObject addMessage(ThreadContext context, IRubyObject name, Block block) {
+ RubyDescriptor msgdef = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK);
+ IRubyObject ctx = cMessageBuilderContext.newInstance(context, msgdef, this, Block.NULL_BLOCK);
+ msgdef.setName(context, name);
+ if (block.isGiven()) {
+ if (block.arity() == Arity.ONE_ARGUMENT) {
+ block.yield(context, ctx);
+ } else {
+ Binding binding = block.getBinding();
+ binding.setSelf(ctx);
+ block.yieldSpecific(context);
+ }
+ }
+ this.pendingList.add(msgdef);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Builder.add_enum(name, &block)
+ *
+ * Creates a new, empty enum descriptor with the given name, and invokes the block in
+ * the context of an EnumBuilderContext on that descriptor. The block can then
+ * call EnumBuilderContext#add_value to define the enum values.
+ *
+ * This is the recommended, idiomatic way to build enum definitions.
+ */
+ @JRubyMethod(name = "add_enum")
+ public IRubyObject addEnum(ThreadContext context, IRubyObject name, Block block) {
+ RubyEnumDescriptor enumDef = (RubyEnumDescriptor) cEnumDescriptor.newInstance(context, Block.NULL_BLOCK);
+ IRubyObject ctx = cEnumBuilderContext.newInstance(context, enumDef, Block.NULL_BLOCK);
+ enumDef.setName(context, name);
+
+ if (block.isGiven()) {
+ if (block.arity() == Arity.ONE_ARGUMENT) {
+ block.yield(context, ctx);
+ } else {
+ Binding binding = block.getBinding();
+ binding.setSelf(ctx);
+ block.yieldSpecific(context);
+ }
+ }
+
+ this.pendingList.add(enumDef);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Builder.finalize_to_pool(pool)
+ *
+ * Adds all accumulated message and enum descriptors created in this builder
+ * context to the given pool. The operation occurs atomically, and all
+ * descriptors can refer to each other (including in cycles). This is the only
+ * way to build (co)recursive message definitions.
+ *
+ * This method is usually called automatically by DescriptorPool#build after it
+ * invokes the given user block in the context of the builder. The user should
+ * not normally need to call this manually because a Builder is not normally
+ * created manually.
+ */
+ @JRubyMethod(name = "finalize_to_pool")
+ public IRubyObject finalizeToPool(ThreadContext context, IRubyObject rbPool) {
+ RubyDescriptorPool pool = (RubyDescriptorPool) rbPool;
+ for (int i = 0; i < this.pendingList.size(); i++) {
+ IRubyObject defRb = this.pendingList.entry(i);
+ if (defRb instanceof RubyDescriptor) {
+ pool.addToSymtab(context, (RubyDescriptor) defRb);
+ } else {
+ pool.addToSymtab(context, (RubyEnumDescriptor) defRb);
+ }
+ }
+ this.pendingList = context.runtime.newArray();
+ return context.runtime.getNil();
+ }
+
+ protected RubyArray pendingList;
+ private RubyClass cDescriptor, cEnumDescriptor, cMessageBuilderContext, cEnumBuilderContext;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java
new file mode 100644
index 00000000..51c50be8
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java
@@ -0,0 +1,267 @@
+/*
+ * 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.DescriptorProtos;
+import com.google.protobuf.Descriptors;
+import org.jruby.*;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+@JRubyClass(name = "Descriptor", include = "Enumerable")
+public class RubyDescriptor extends RubyObject {
+ public static void createRubyDescriptor(Ruby runtime) {
+ RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cDescriptor = protobuf.defineClassUnder("Descriptor", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyDescriptor(runtime, klazz);
+ }
+ });
+ cDescriptor.includeModule(runtime.getEnumerable());
+ cDescriptor.defineAnnotatedMethods(RubyDescriptor.class);
+ }
+
+ public RubyDescriptor(Ruby runtime, RubyClass klazz) {
+ super(runtime, klazz);
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.new => descriptor
+ *
+ * Creates a new, empty, message type descriptor. At a minimum, its name must be
+ * set before it is added to a pool. It cannot be used to create messages until
+ * it is added to a pool, after which it becomes immutable (as part of a
+ * finalization process).
+ */
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context) {
+ this.builder = DescriptorProtos.DescriptorProto.newBuilder();
+ this.fieldDefMap = new HashMap<String, RubyFieldDescriptor>();
+ this.oneofDefs = new HashMap<IRubyObject, RubyOneofDescriptor>();
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.name => name
+ *
+ * Returns the name of this message type as a fully-qualfied string (e.g.,
+ * My.Package.MessageType).
+ */
+ @JRubyMethod(name = "name")
+ public IRubyObject getName(ThreadContext context) {
+ return this.name;
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.name = name
+ *
+ * Assigns a name to this message type. The descriptor must not have been added
+ * to a pool yet.
+ */
+ @JRubyMethod(name = "name=")
+ public IRubyObject setName(ThreadContext context, IRubyObject name) {
+ this.name = name;
+ this.builder.setName(Utils.escapeIdentifier(this.name.asJavaString()));
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.add_field(field) => nil
+ *
+ * Adds the given FieldDescriptor to this message type. The descriptor must not
+ * have been added to a pool yet. Raises an exception if a field with the same
+ * name or number already exists. Sub-type references (e.g. for fields of type
+ * message) are not resolved at this point.
+ */
+ @JRubyMethod(name = "add_field")
+ public IRubyObject addField(ThreadContext context, IRubyObject obj) {
+ RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) obj;
+ this.fieldDefMap.put(fieldDef.getName(context).asJavaString(), fieldDef);
+ this.builder.addField(fieldDef.build());
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.lookup(name) => FieldDescriptor
+ *
+ * Returns the field descriptor for the field with the given name, if present,
+ * or nil if none.
+ */
+ @JRubyMethod
+ public IRubyObject lookup(ThreadContext context, IRubyObject fieldName) {
+ return this.fieldDefMap.get(fieldName.asJavaString());
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.msgclass => message_klass
+ *
+ * Returns the Ruby class created for this message type. Valid only once the
+ * message type has been added to a pool.
+ */
+ @JRubyMethod
+ public IRubyObject msgclass(ThreadContext context) {
+ if (this.klazz == null) {
+ this.klazz = buildClassFromDescriptor(context);
+ }
+ return this.klazz;
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.each(&block)
+ *
+ * Iterates over fields in this message type, yielding to the block on each one.
+ */
+ @JRubyMethod
+ public IRubyObject each(ThreadContext context, Block block) {
+ for (Map.Entry<String, RubyFieldDescriptor> entry : fieldDefMap.entrySet()) {
+ block.yield(context, entry.getValue());
+ }
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.add_oneof(oneof) => nil
+ *
+ * Adds the given OneofDescriptor to this message type. This descriptor must not
+ * have been added to a pool yet. Raises an exception if a oneof with the same
+ * name already exists, or if any of the oneof's fields' names or numbers
+ * conflict with an existing field in this message type. All fields in the oneof
+ * are added to the message descriptor. Sub-type references (e.g. for fields of
+ * type message) are not resolved at this point.
+ */
+ @JRubyMethod(name = "add_oneof")
+ public IRubyObject addOneof(ThreadContext context, IRubyObject obj) {
+ RubyOneofDescriptor def = (RubyOneofDescriptor) obj;
+ builder.addOneofDecl(def.build(builder.getOneofDeclCount()));
+ for (RubyFieldDescriptor fieldDescriptor : def.getFields()) {
+ addField(context, fieldDescriptor);
+ }
+ oneofDefs.put(def.getName(context), def);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.each_oneof(&block) => nil
+ *
+ * Invokes the given block for each oneof in this message type, passing the
+ * corresponding OneofDescriptor.
+ */
+ @JRubyMethod(name = "each_oneof")
+ public IRubyObject eachOneof(ThreadContext context, Block block) {
+ for (RubyOneofDescriptor oneofDescriptor : oneofDefs.values()) {
+ block.yieldSpecific(context, oneofDescriptor);
+ }
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Descriptor.lookup_oneof(name) => OneofDescriptor
+ *
+ * Returns the oneof descriptor for the oneof with the given name, if present,
+ * or nil if none.
+ */
+ @JRubyMethod(name = "lookup_oneof")
+ public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) {
+ if (name instanceof RubySymbol) {
+ name = ((RubySymbol) name).id2name();
+ }
+ return oneofDefs.containsKey(name) ? oneofDefs.get(name) : context.runtime.getNil();
+ }
+
+ public void setDescriptor(Descriptors.Descriptor descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ public Descriptors.Descriptor getDescriptor() {
+ return this.descriptor;
+ }
+
+ public DescriptorProtos.DescriptorProto.Builder getBuilder() {
+ return builder;
+ }
+
+ public void setMapEntry(boolean isMapEntry) {
+ this.builder.setOptions(DescriptorProtos.MessageOptions.newBuilder().setMapEntry(isMapEntry));
+ }
+
+ private RubyModule buildClassFromDescriptor(ThreadContext context) {
+ Ruby runtime = context.runtime;
+
+ ObjectAllocator allocator = new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyMessage(runtime, klazz, descriptor);
+ }
+ };
+
+ // rb_define_class_id
+ RubyClass klass = RubyClass.newClass(runtime, runtime.getObject());
+ klass.setAllocator(allocator);
+ klass.makeMetaClass(runtime.getObject().getMetaClass());
+ klass.inherit(runtime.getObject());
+ klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this);
+ klass.defineAnnotatedMethods(RubyMessage.class);
+ return klass;
+ }
+
+ protected RubyFieldDescriptor lookup(String fieldName) {
+ return fieldDefMap.get(Utils.unescapeIdentifier(fieldName));
+ }
+
+ private IRubyObject name;
+ private RubyModule klazz;
+
+ private DescriptorProtos.DescriptorProto.Builder builder;
+ private Descriptors.Descriptor descriptor;
+ private Map<String, RubyFieldDescriptor> fieldDefMap;
+ private Map<IRubyObject, RubyOneofDescriptor> oneofDefs;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java
new file mode 100644
index 00000000..0345cb99
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java
@@ -0,0 +1,169 @@
+/*
+ * 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.DescriptorProtos;
+import com.google.protobuf.Descriptors;
+import org.jruby.*;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.*;
+import org.jruby.runtime.builtin.IRubyObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@JRubyClass(name = "DescriptorPool")
+public class RubyDescriptorPool extends RubyObject {
+ public static void createRubyDescriptorPool(Ruby runtime) {
+ RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cDescriptorPool = protobuf.defineClassUnder("DescriptorPool", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyDescriptorPool(runtime, klazz);
+ }
+ });
+
+ cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class);
+ descriptorPool = (RubyDescriptorPool) cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK);
+ }
+
+ public RubyDescriptorPool(Ruby ruby, RubyClass klazz) {
+ super(ruby, klazz);
+ }
+
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context) {
+ this.symtab = new HashMap<IRubyObject, IRubyObject>();
+ this.cBuilder = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Builder");
+ this.builder = DescriptorProtos.FileDescriptorProto.newBuilder();
+ return this;
+ }
+
+ @JRubyMethod
+ public IRubyObject build(ThreadContext context, Block block) {
+ RubyBuilder ctx = (RubyBuilder) cBuilder.newInstance(context, Block.NULL_BLOCK);
+ if (block.arity() == Arity.ONE_ARGUMENT) {
+ block.yield(context, ctx);
+ } else {
+ Binding binding = block.getBinding();
+ binding.setSelf(ctx);
+ block.yieldSpecific(context);
+ }
+ ctx.finalizeToPool(context, this);
+ buildFileDescriptor(context);
+ return context.runtime.getNil();
+ }
+
+ @JRubyMethod
+ public IRubyObject lookup(ThreadContext context, IRubyObject name) {
+ IRubyObject descriptor = this.symtab.get(name);
+ if (descriptor == null) {
+ return context.runtime.getNil();
+ }
+ return descriptor;
+ }
+
+ /*
+ * call-seq:
+ * DescriptorPool.generated_pool => descriptor_pool
+ *
+ * Class method that returns the global DescriptorPool. This is a singleton into
+ * which generated-code message and enum types are registered. The user may also
+ * register types in this pool for convenience so that they do not have to hold
+ * a reference to a private pool instance.
+ */
+ @JRubyMethod(meta = true, name = "generated_pool")
+ public static IRubyObject generatedPool(ThreadContext context, IRubyObject recv) {
+ return descriptorPool;
+ }
+
+ protected void addToSymtab(ThreadContext context, RubyDescriptor def) {
+ symtab.put(def.getName(context), def);
+ this.builder.addMessageType(def.getBuilder());
+ }
+
+ protected void addToSymtab(ThreadContext context, RubyEnumDescriptor def) {
+ symtab.put(def.getName(context), def);
+ this.builder.addEnumType(def.getBuilder());
+ }
+
+ private void buildFileDescriptor(ThreadContext context) {
+ Ruby runtime = context.runtime;
+ try {
+ this.builder.setSyntax("proto3");
+ final Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(
+ this.builder.build(), new Descriptors.FileDescriptor[]{});
+
+ for (Descriptors.EnumDescriptor enumDescriptor : fileDescriptor.getEnumTypes()) {
+ String enumName = Utils.unescapeIdentifier(enumDescriptor.getName());
+ if (enumDescriptor.findValueByNumber(0) == null) {
+ throw runtime.newTypeError("Enum definition " + enumName
+ + " does not contain a value for '0'");
+ }
+ ((RubyEnumDescriptor) symtab.get(runtime.newString(enumName)))
+ .setDescriptor(enumDescriptor);
+ }
+ for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
+ RubyDescriptor rubyDescriptor = ((RubyDescriptor)
+ symtab.get(runtime.newString(Utils.unescapeIdentifier(descriptor.getName()))));
+ for (Descriptors.FieldDescriptor fieldDescriptor : descriptor.getFields()) {
+ if (fieldDescriptor.isRequired()) {
+ throw runtime.newTypeError("Required fields are unsupported in proto3");
+ }
+ RubyFieldDescriptor rubyFieldDescriptor = rubyDescriptor.lookup(fieldDescriptor.getName());
+ rubyFieldDescriptor.setFieldDef(fieldDescriptor);
+ if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
+ RubyDescriptor subType = (RubyDescriptor) lookup(context,
+ runtime.newString(Utils.unescapeIdentifier(fieldDescriptor.getMessageType().getName())));
+ rubyFieldDescriptor.setSubType(subType);
+ }
+ if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.ENUM) {
+ RubyEnumDescriptor subType = (RubyEnumDescriptor) lookup(context,
+ runtime.newString(Utils.unescapeIdentifier(fieldDescriptor.getEnumType().getName())));
+ rubyFieldDescriptor.setSubType(subType);
+ }
+ }
+ rubyDescriptor.setDescriptor(descriptor);
+ }
+ } catch (Descriptors.DescriptorValidationException e) {
+ throw runtime.newRuntimeError(e.getMessage());
+ }
+ }
+
+ private static RubyDescriptorPool descriptorPool;
+
+ private RubyClass cBuilder;
+ private Map<IRubyObject, IRubyObject> symtab;
+ private DescriptorProtos.FileDescriptorProto.Builder builder;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java
new file mode 100644
index 00000000..929d8699
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java
@@ -0,0 +1,86 @@
+/*
+ * 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.Descriptors;
+import org.jruby.RubyModule;
+import org.jruby.RubyNumeric;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+public class RubyEnum {
+ /*
+ * call-seq:
+ * Enum.lookup(number) => name
+ *
+ * This module method, provided on each generated enum module, looks up an enum
+ * value by number and returns its name as a Ruby symbol, or nil if not found.
+ */
+ @JRubyMethod(meta = true)
+ public static IRubyObject lookup(ThreadContext context, IRubyObject recv, IRubyObject number) {
+ RubyEnumDescriptor rubyEnumDescriptorescriptor = (RubyEnumDescriptor) getDescriptor(context, recv);
+ Descriptors.EnumDescriptor descriptor = rubyEnumDescriptorescriptor.getDescriptor();
+ Descriptors.EnumValueDescriptor value = descriptor.findValueByNumber(RubyNumeric.num2int(number));
+ if (value == null) return context.runtime.getNil();
+ return context.runtime.newSymbol(value.getName());
+ }
+
+ /*
+ * call-seq:
+ * Enum.resolve(name) => number
+ *
+ * This module method, provided on each generated enum module, looks up an enum
+ * value by name (as a Ruby symbol) and returns its name, or nil if not found.
+ */
+ @JRubyMethod(meta = true)
+ public static IRubyObject resolve(ThreadContext context, IRubyObject recv, IRubyObject name) {
+ RubyEnumDescriptor rubyEnumDescriptorescriptor = (RubyEnumDescriptor) getDescriptor(context, recv);
+ Descriptors.EnumDescriptor descriptor = rubyEnumDescriptorescriptor.getDescriptor();
+ Descriptors.EnumValueDescriptor value = descriptor.findValueByName(name.asJavaString());
+ if (value == null) return context.runtime.getNil();
+ return context.runtime.newFixnum(value.getNumber());
+ }
+
+ /*
+ * call-seq:
+ * Enum.descriptor
+ *
+ * This module method, provided on each generated enum module, returns the
+ * EnumDescriptor corresponding to this enum type.
+ */
+ @JRubyMethod(meta = true, name = "descriptor")
+ public static IRubyObject getDescriptor(ThreadContext context, IRubyObject recv) {
+ return ((RubyModule) recv).getInstanceVariable(Utils.DESCRIPTOR_INSTANCE_VAR);
+ }
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumBuilderContext.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumBuilderContext.java
new file mode 100644
index 00000000..e4cac345
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumBuilderContext.java
@@ -0,0 +1,82 @@
+/*
+ * 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 org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+@JRubyClass(name = "EnumBuilderContext")
+public class RubyEnumBuilderContext extends RubyObject {
+ public static void createRubyEnumBuilderContext(Ruby runtime) {
+ RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cMessageBuilderContext = protobuf.defineClassUnder("EnumBuilderContext", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyEnumBuilderContext(runtime, klazz);
+ }
+ });
+ cMessageBuilderContext.defineAnnotatedMethods(RubyEnumBuilderContext.class);
+ }
+
+ public RubyEnumBuilderContext(Ruby ruby, RubyClass klazz) {
+ super(ruby, klazz);
+ }
+
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context, IRubyObject enumDescriptor) {
+ this.enumDescriptor = (RubyEnumDescriptor) enumDescriptor;
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * EnumBuilder.add_value(name, number)
+ *
+ * Adds the given name => number mapping to the enum type. Name must be a Ruby
+ * symbol.
+ */
+ @JRubyMethod
+ public IRubyObject value(ThreadContext context, IRubyObject name, IRubyObject number) {
+ this.enumDescriptor.addValue(context, name, number);
+ return context.runtime.getNil();
+ }
+
+ private RubyEnumDescriptor enumDescriptor;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java
new file mode 100644
index 00000000..4df832d0
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java
@@ -0,0 +1,185 @@
+/*
+ * 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.DescriptorProtos;
+import com.google.protobuf.Descriptors;
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.RubyNumeric;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+@JRubyClass(name = "EnumDescriptor", include = "Enumerable")
+public class RubyEnumDescriptor extends RubyObject {
+ public static void createRubyEnumDescriptor(Ruby runtime) {
+ RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cEnumDescriptor = mProtobuf.defineClassUnder("EnumDescriptor", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyEnumDescriptor(runtime, klazz);
+ }
+ });
+ cEnumDescriptor.includeModule(runtime.getEnumerable());
+ cEnumDescriptor.defineAnnotatedMethods(RubyEnumDescriptor.class);
+ }
+
+ public RubyEnumDescriptor(Ruby runtime, RubyClass klazz) {
+ super(runtime, klazz);
+ }
+
+ /*
+ * call-seq:
+ * EnumDescriptor.new => enum_descriptor
+ *
+ * Creates a new, empty, enum descriptor. Must be added to a pool before the
+ * enum type can be used. The enum type may only be modified prior to adding to
+ * a pool.
+ */
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context) {
+ this.builder = DescriptorProtos.EnumDescriptorProto.newBuilder();
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * EnumDescriptor.name => name
+ *
+ * Returns the name of this enum type.
+ */
+ @JRubyMethod(name = "name")
+ public IRubyObject getName(ThreadContext context) {
+ return this.name;
+ }
+
+ /*
+ * call-seq:
+ * EnumDescriptor.name = name
+ *
+ * Sets the name of this enum type. Cannot be called if the enum type has
+ * already been added to a pool.
+ */
+ @JRubyMethod(name = "name=")
+ public IRubyObject setName(ThreadContext context, IRubyObject name) {
+ this.name = name;
+ this.builder.setName(Utils.escapeIdentifier(name.asJavaString()));
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * EnumDescriptor.add_value(key, value)
+ *
+ * Adds a new key => value mapping to this enum type. Key must be given as a
+ * Ruby symbol. Cannot be called if the enum type has already been added to a
+ * pool. Will raise an exception if the key or value is already in use.
+ */
+ @JRubyMethod(name = "add_value")
+ public IRubyObject addValue(ThreadContext context, IRubyObject name, IRubyObject number) {
+ DescriptorProtos.EnumValueDescriptorProto.Builder valueBuilder = DescriptorProtos.EnumValueDescriptorProto.newBuilder();
+ valueBuilder.setName(name.asJavaString());
+ valueBuilder.setNumber(RubyNumeric.num2int(number));
+ this.builder.addValue(valueBuilder);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * EnumDescriptor.each(&block)
+ *
+ * Iterates over key => value mappings in this enum's definition, yielding to
+ * the block with (key, value) arguments for each one.
+ */
+ @JRubyMethod
+ public IRubyObject each(ThreadContext context, Block block) {
+ Ruby runtime = context.runtime;
+ for (Descriptors.EnumValueDescriptor enumValueDescriptor : descriptor.getValues()) {
+ block.yield(context, runtime.newArray(runtime.newSymbol(enumValueDescriptor.getName()),
+ runtime.newFixnum(enumValueDescriptor.getNumber())));
+ }
+ return runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * EnumDescriptor.enummodule => module
+ *
+ * Returns the Ruby module corresponding to this enum type. Cannot be called
+ * until the enum descriptor has been added to a pool.
+ */
+ @JRubyMethod
+ public IRubyObject enummodule(ThreadContext context) {
+ if (this.klazz == null) {
+ this.klazz = buildModuleFromDescriptor(context);
+ }
+ return this.klazz;
+ }
+
+ public void setDescriptor(Descriptors.EnumDescriptor descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ public Descriptors.EnumDescriptor getDescriptor() {
+ return this.descriptor;
+ }
+
+ public DescriptorProtos.EnumDescriptorProto.Builder getBuilder() {
+ return this.builder;
+ }
+
+ private RubyModule buildModuleFromDescriptor(ThreadContext context) {
+ Ruby runtime = context.runtime;
+ Utils.checkNameAvailability(context, name.asJavaString());
+
+ RubyModule enumModule = RubyModule.newModule(runtime);
+ for (Descriptors.EnumValueDescriptor value : descriptor.getValues()) {
+ enumModule.defineConstant(value.getName(), runtime.newFixnum(value.getNumber()));
+ }
+
+ enumModule.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this);
+ enumModule.defineAnnotatedMethods(RubyEnum.class);
+ return enumModule;
+ }
+
+ private IRubyObject name;
+ private RubyModule klazz;
+ private Descriptors.EnumDescriptor descriptor;
+ private DescriptorProtos.EnumDescriptorProto.Builder builder;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java
new file mode 100644
index 00000000..38226c4e
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java
@@ -0,0 +1,248 @@
+/*
+ * 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.DescriptorProtos;
+import com.google.protobuf.Descriptors;
+import org.jruby.*;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+@JRubyClass(name = "FieldDescriptor")
+public class RubyFieldDescriptor extends RubyObject {
+ public static void createRubyFileDescriptor(Ruby runtime) {
+ RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cFieldDescriptor = mProtobuf.defineClassUnder("FieldDescriptor", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyFieldDescriptor(runtime, klazz);
+ }
+ });
+ cFieldDescriptor.defineAnnotatedMethods(RubyFieldDescriptor.class);
+ }
+
+ public RubyFieldDescriptor(Ruby runtime, RubyClass klazz) {
+ super(runtime, klazz);
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.new => field
+ *
+ * Returns a new field descriptor. Its name, type, etc. must be set before it is
+ * added to a message type.
+ */
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context) {
+ builder = DescriptorProtos.FieldDescriptorProto.newBuilder();
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.label = label
+ *
+ * Sets the label on this field. Cannot be called if field is part of a message
+ * type already in a pool.
+ */
+ @JRubyMethod(name = "label=")
+ public IRubyObject setLabel(ThreadContext context, IRubyObject value) {
+ this.builder.setLabel(
+ DescriptorProtos.FieldDescriptorProto.Label.valueOf("LABEL_" + value.asJavaString().toUpperCase()));
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.name => name
+ *
+ * Returns the name of this field.
+ */
+ @JRubyMethod(name = "name")
+ public IRubyObject getName(ThreadContext context) {
+ return this.name;
+ }
+
+ @JRubyMethod(name = "subtype")
+ public IRubyObject getSubType(ThreadContext context) {
+ return subType;
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.name = name
+ *
+ * Sets the name of this field. Cannot be called once the containing message
+ * type, if any, is added to a pool.
+ */
+ @JRubyMethod(name = "name=")
+ public IRubyObject setName(ThreadContext context, IRubyObject value) {
+ String nameStr = value.asJavaString();
+ this.name = context.runtime.newString(nameStr);
+ this.builder.setName(Utils.escapeIdentifier(nameStr));
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.type => type
+ *
+ * Returns this field's type, as a Ruby symbol, or nil if not yet set.
+ *
+ * Valid field types are:
+ * :int32, :int64, :uint32, :uint64, :float, :double, :bool, :string,
+ * :bytes, :message.
+ */
+ @JRubyMethod(name = "type")
+ public IRubyObject getType(ThreadContext context) {
+ return Utils.fieldTypeToRuby(context, this.builder.getType());
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.type = type
+ *
+ * Sets this field's type. Cannot be called if field is part of a message type
+ * already in a pool.
+ */
+ @JRubyMethod(name = "type=")
+ public IRubyObject setType(ThreadContext context, IRubyObject value) {
+ this.builder.setType(DescriptorProtos.FieldDescriptorProto.Type.valueOf("TYPE_" + value.asJavaString().toUpperCase()));
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.number = number
+ *
+ * Sets the tag number for this field. Cannot be called if field is part of a
+ * message type already in a pool.
+ */
+ @JRubyMethod(name = "number=")
+ public IRubyObject setNumber(ThreadContext context, IRubyObject value) {
+ this.builder.setNumber(RubyNumeric.num2int(value));
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.submsg_name = submsg_name
+ *
+ * Sets the name of the message or enum type corresponding to this field, if it
+ * is a message or enum field (respectively). This type name will be resolved
+ * within the context of the pool to which the containing message type is added.
+ * Cannot be called on field that are not of message or enum type, or on fields
+ * that are part of a message type already added to a pool.
+ */
+ @JRubyMethod(name = "submsg_name=")
+ public IRubyObject setSubmsgName(ThreadContext context, IRubyObject name) {
+ this.builder.setTypeName("." + Utils.escapeIdentifier(name.asJavaString()));
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.get(message) => value
+ *
+ * Returns the value set for this field on the given message. Raises an
+ * exception if message is of the wrong type.
+ */
+ @JRubyMethod(name = "get")
+ public IRubyObject getValue(ThreadContext context, IRubyObject msgRb) {
+ RubyMessage message = (RubyMessage) msgRb;
+ if (message.getDescriptor() != fieldDef.getContainingType()) {
+ throw context.runtime.newTypeError("set method called on wrong message type");
+ }
+ return message.getField(context, fieldDef);
+ }
+
+ /*
+ * call-seq:
+ * FieldDescriptor.set(message, value)
+ *
+ * Sets the value corresponding to this field to the given value on the given
+ * message. Raises an exception if message is of the wrong type. Performs the
+ * ordinary type-checks for field setting.
+ */
+ @JRubyMethod(name = "set")
+ public IRubyObject setValue(ThreadContext context, IRubyObject msgRb, IRubyObject value) {
+ RubyMessage message = (RubyMessage) msgRb;
+ if (message.getDescriptor() != fieldDef.getContainingType()) {
+ throw context.runtime.newTypeError("set method called on wrong message type");
+ }
+ message.setField(context, fieldDef, value);
+ return context.runtime.getNil();
+ }
+
+ protected void setSubType(IRubyObject rubyDescriptor) {
+ this.subType = rubyDescriptor;
+ }
+
+ protected void setFieldDef(Descriptors.FieldDescriptor fieldDescriptor) {
+ this.fieldDef = fieldDescriptor;
+ }
+
+ protected void setOneofName(IRubyObject name) {
+ oneofName = name;
+ }
+
+ protected void setOneofIndex(int index) {
+ hasOneofIndex = true;
+ oneofIndex = index;
+ }
+
+ protected IRubyObject getOneofName() {
+ return oneofName;
+ }
+
+ protected Descriptors.FieldDescriptor getFieldDef() {
+ return fieldDef;
+ }
+
+ protected DescriptorProtos.FieldDescriptorProto build() {
+ if (hasOneofIndex)
+ builder.setOneofIndex(oneofIndex);
+ return this.builder.build();
+ }
+
+ private DescriptorProtos.FieldDescriptorProto.Builder builder;
+ private IRubyObject name;
+ private IRubyObject subType;
+ private IRubyObject oneofName;
+ private Descriptors.FieldDescriptor fieldDef;
+ private int oneofIndex;
+ private boolean hasOneofIndex = false;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java
new file mode 100644
index 00000000..b25dc6e1
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java
@@ -0,0 +1,434 @@
+/*
+ * 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.Descriptors;
+import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.MapEntry;
+import org.jruby.*;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.internal.runtime.methods.DynamicMethod;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.ByteList;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@JRubyClass(name = "Map", include = "Enumerable")
+public class RubyMap extends RubyObject {
+ public static void createRubyMap(Ruby runtime) {
+ RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cMap = protobuf.defineClassUnder("Map", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
+ return new RubyMap(ruby, rubyClass);
+ }
+ });
+ cMap.includeModule(runtime.getEnumerable());
+ cMap.defineAnnotatedMethods(RubyMap.class);
+ }
+
+ public RubyMap(Ruby ruby, RubyClass rubyClass) {
+ super(ruby, rubyClass);
+ }
+
+ /*
+ * call-seq:
+ * Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {})
+ * => new map
+ *
+ * Allocates a new Map container. This constructor may be called with 2, 3, or 4
+ * arguments. The first two arguments are always present and are symbols (taking
+ * on the same values as field-type symbols in message descriptors) that
+ * indicate the type of the map key and value fields.
+ *
+ * The supported key types are: :int32, :int64, :uint32, :uint64, :bool,
+ * :string, :bytes.
+ *
+ * The supported value types are: :int32, :int64, :uint32, :uint64, :bool,
+ * :string, :bytes, :enum, :message.
+ *
+ * The third argument, value_typeclass, must be present if value_type is :enum
+ * or :message. As in RepeatedField#new, this argument must be a message class
+ * (for :message) or enum module (for :enum).
+ *
+ * The last argument, if present, provides initial content for map. Note that
+ * this may be an ordinary Ruby hashmap or another Map instance with identical
+ * key and value types. Also note that this argument may be present whether or
+ * not value_typeclass is present (and it is unambiguously separate from
+ * value_typeclass because value_typeclass's presence is strictly determined by
+ * value_type). The contents of this initial hashmap or Map instance are
+ * shallow-copied into the new Map: the original map is unmodified, but
+ * references to underlying objects will be shared if the value type is a
+ * message type.
+ */
+
+ @JRubyMethod(required = 2, optional = 2)
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
+ this.table = new HashMap<IRubyObject, IRubyObject>();
+ this.keyType = Utils.rubyToFieldType(args[0]);
+ this.valueType = Utils.rubyToFieldType(args[1]);
+
+ switch(keyType) {
+ case INT32:
+ case INT64:
+ case UINT32:
+ case UINT64:
+ case BOOL:
+ case STRING:
+ case BYTES:
+ // These are OK.
+ break;
+ default:
+ throw context.runtime.newArgumentError("Invalid key type for map.");
+ }
+
+ int initValueArg = 2;
+ if (needTypeclass(this.valueType) && args.length > 2) {
+ this.valueTypeClass = args[2];
+ Utils.validateTypeClass(context, this.valueType, this.valueTypeClass);
+ initValueArg = 3;
+ } else {
+ this.valueTypeClass = context.runtime.getNilClass();
+ }
+
+ // Table value type is always UINT64: this ensures enough space to store the
+ // native_slot value.
+ if (args.length > initValueArg) {
+ mergeIntoSelf(context, args[initValueArg]);
+ }
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * Map.[]=(key, value) => value
+ *
+ * Inserts or overwrites the value at the given key with the given new value.
+ * Throws an exception if the key type is incorrect. Returns the new value that
+ * was just inserted.
+ */
+ @JRubyMethod(name = "[]=")
+ public IRubyObject indexSet(ThreadContext context, IRubyObject key, IRubyObject value) {
+ Utils.checkType(context, keyType, key, (RubyModule) valueTypeClass);
+ Utils.checkType(context, valueType, value, (RubyModule) valueTypeClass);
+ IRubyObject symbol;
+ if (valueType == Descriptors.FieldDescriptor.Type.ENUM &&
+ Utils.isRubyNum(value) &&
+ ! (symbol = RubyEnum.lookup(context, valueTypeClass, value)).isNil()) {
+ value = symbol;
+ }
+ this.table.put(key, value);
+ return value;
+ }
+
+ /*
+ * call-seq:
+ * Map.[](key) => value
+ *
+ * Accesses the element at the given key. Throws an exception if the key type is
+ * incorrect. Returns nil when the key is not present in the map.
+ */
+ @JRubyMethod(name = "[]")
+ public IRubyObject index(ThreadContext context, IRubyObject key) {
+ if (table.containsKey(key))
+ return this.table.get(key);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Map.==(other) => boolean
+ *
+ * Compares this map to another. Maps are equal if they have identical key sets,
+ * and for each key, the values in both maps compare equal. Elements are
+ * compared as per normal Ruby semantics, by calling their :== methods (or
+ * performing a more efficient comparison for primitive types).
+ *
+ * Maps with dissimilar key types or value types/typeclasses are never equal,
+ * even if value comparison (for example, between integers and floats) would
+ * have otherwise indicated that every element has equal value.
+ */
+ @JRubyMethod(name = "==")
+ public IRubyObject eq(ThreadContext context, IRubyObject _other) {
+ if (_other instanceof RubyHash)
+ return toHash(context).op_equal(context, _other);
+ RubyMap other = (RubyMap) _other;
+ if (this == other) return context.runtime.getTrue();
+ if (!typeCompatible(other) || this.table.size() != other.table.size())
+ return context.runtime.getFalse();
+ for (IRubyObject key : table.keySet()) {
+ if (! other.table.containsKey(key))
+ return context.runtime.getFalse();
+ if (! other.table.get(key).equals(table.get(key)))
+ return context.runtime.getFalse();
+ }
+ return context.runtime.getTrue();
+ }
+
+ /*
+ * call-seq:
+ * Map.inspect => string
+ *
+ * Returns a string representing this map's elements. It will be formatted as
+ * "{key => value, key => value, ...}", with each key and value string
+ * representation computed by its own #inspect method.
+ */
+ @JRubyMethod
+ public IRubyObject inspect() {
+ return toHash(getRuntime().getCurrentContext()).inspect();
+ }
+
+ /*
+ * call-seq:
+ * Map.hash => hash_value
+ *
+ * Returns a hash value based on this map's contents.
+ */
+ @JRubyMethod
+ public IRubyObject hash(ThreadContext context) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ for (IRubyObject key : table.keySet()) {
+ digest.update((byte) key.hashCode());
+ digest.update((byte) table.get(key).hashCode());
+ }
+ return context.runtime.newString(new ByteList(digest.digest()));
+ } catch (NoSuchAlgorithmException ignore) {
+ return context.runtime.newFixnum(System.identityHashCode(table));
+ }
+ }
+
+ /*
+ * call-seq:
+ * Map.keys => [list_of_keys]
+ *
+ * Returns the list of keys contained in the map, in unspecified order.
+ */
+ @JRubyMethod
+ public IRubyObject keys(ThreadContext context) {
+ return RubyArray.newArray(context.runtime, table.keySet());
+ }
+
+ /*
+ * call-seq:
+ * Map.values => [list_of_values]
+ *
+ * Returns the list of values contained in the map, in unspecified order.
+ */
+ @JRubyMethod
+ public IRubyObject values(ThreadContext context) {
+ return RubyArray.newArray(context.runtime, table.values());
+ }
+
+ /*
+ * call-seq:
+ * Map.clear
+ *
+ * Removes all entries from the map.
+ */
+ @JRubyMethod
+ public IRubyObject clear(ThreadContext context) {
+ table.clear();
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Map.each(&block)
+ *
+ * Invokes &block on each |key, value| pair in the map, in unspecified order.
+ * Note that Map also includes Enumerable; map thus acts like a normal Ruby
+ * sequence.
+ */
+ @JRubyMethod
+ public IRubyObject each(ThreadContext context, Block block) {
+ for (IRubyObject key : table.keySet()) {
+ block.yieldSpecific(context, key, table.get(key));
+ }
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * Map.delete(key) => old_value
+ *
+ * Deletes the value at the given key, if any, returning either the old value or
+ * nil if none was present. Throws an exception if the key is of the wrong type.
+ */
+ @JRubyMethod
+ public IRubyObject delete(ThreadContext context, IRubyObject key) {
+ return table.remove(key);
+ }
+
+ /*
+ * call-seq:
+ * Map.has_key?(key) => bool
+ *
+ * Returns true if the given key is present in the map. Throws an exception if
+ * the key has the wrong type.
+ */
+ @JRubyMethod(name = "has_key?")
+ public IRubyObject hasKey(ThreadContext context, IRubyObject key) {
+ return this.table.containsKey(key) ? context.runtime.getTrue() : context.runtime.getFalse();
+ }
+
+ /*
+ * call-seq:
+ * Map.length
+ *
+ * Returns the number of entries (key-value pairs) in the map.
+ */
+ @JRubyMethod
+ public IRubyObject length(ThreadContext context) {
+ return context.runtime.newFixnum(this.table.size());
+ }
+
+ /*
+ * call-seq:
+ * Map.dup => new_map
+ *
+ * Duplicates this map with a shallow copy. References to all non-primitive
+ * element objects (e.g., submessages) are shared.
+ */
+ @JRubyMethod
+ public IRubyObject dup(ThreadContext context) {
+ RubyMap newMap = newThisType(context);
+ for (Map.Entry<IRubyObject, IRubyObject> entry : table.entrySet()) {
+ newMap.table.put(entry.getKey(), entry.getValue());
+ }
+ return newMap;
+ }
+
+ @JRubyMethod(name = "to_h")
+ public RubyHash toHash(ThreadContext context) {
+ return RubyHash.newHash(context.runtime, table, context.runtime.getNil());
+ }
+
+ // Used by Google::Protobuf.deep_copy but not exposed directly.
+ protected IRubyObject deepCopy(ThreadContext context) {
+ RubyMap newMap = newThisType(context);
+ switch (valueType) {
+ case MESSAGE:
+ for (IRubyObject key : table.keySet()) {
+ RubyMessage message = (RubyMessage) table.get(key);
+ newMap.table.put(key.dup(), message.deepCopy(context));
+ }
+ break;
+ default:
+ for (IRubyObject key : table.keySet()) {
+ newMap.table.put(key.dup(), table.get(key).dup());
+ }
+ }
+ return newMap;
+ }
+
+ protected List<DynamicMessage> build(ThreadContext context, RubyDescriptor descriptor) {
+ List<DynamicMessage> list = new ArrayList<DynamicMessage>();
+ RubyClass rubyClass = (RubyClass) descriptor.msgclass(context);
+ Descriptors.FieldDescriptor keyField = descriptor.lookup("key").getFieldDef();
+ Descriptors.FieldDescriptor valueField = descriptor.lookup("value").getFieldDef();
+ for (IRubyObject key : table.keySet()) {
+ RubyMessage mapMessage = (RubyMessage) rubyClass.newInstance(context, Block.NULL_BLOCK);
+ mapMessage.setField(context, keyField, key);
+ mapMessage.setField(context, valueField, table.get(key));
+ list.add(mapMessage.build(context));
+ }
+ return list;
+ }
+
+ protected RubyMap mergeIntoSelf(final ThreadContext context, IRubyObject hashmap) {
+ if (hashmap instanceof RubyHash) {
+ ((RubyHash) hashmap).visitAll(new RubyHash.Visitor() {
+ @Override
+ public void visit(IRubyObject key, IRubyObject val) {
+ indexSet(context, key, val);
+ }
+ });
+ } else if (hashmap instanceof RubyMap) {
+ RubyMap other = (RubyMap) hashmap;
+ if (!typeCompatible(other)) {
+ throw context.runtime.newTypeError("Attempt to merge Map with mismatching types");
+ }
+ } else {
+ throw context.runtime.newTypeError("Unknown type merging into Map");
+ }
+ return this;
+ }
+
+ protected boolean typeCompatible(RubyMap other) {
+ return this.keyType == other.keyType &&
+ this.valueType == other.valueType &&
+ this.valueTypeClass == other.valueTypeClass;
+ }
+
+ private RubyMap newThisType(ThreadContext context) {
+ RubyMap newMap;
+ if (needTypeclass(valueType)) {
+ newMap = (RubyMap) metaClass.newInstance(context,
+ Utils.fieldTypeToRuby(context, keyType),
+ Utils.fieldTypeToRuby(context, valueType),
+ valueTypeClass, Block.NULL_BLOCK);
+ } else {
+ newMap = (RubyMap) metaClass.newInstance(context,
+ Utils.fieldTypeToRuby(context, keyType),
+ Utils.fieldTypeToRuby(context, valueType),
+ Block.NULL_BLOCK);
+ }
+ newMap.table = new HashMap<IRubyObject, IRubyObject>();
+ return newMap;
+ }
+
+ private boolean needTypeclass(Descriptors.FieldDescriptor.Type type) {
+ switch(type) {
+ case MESSAGE:
+ case ENUM:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private Descriptors.FieldDescriptor.Type keyType;
+ private Descriptors.FieldDescriptor.Type valueType;
+ private IRubyObject valueTypeClass;
+ private Map<IRubyObject, IRubyObject> table;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
new file mode 100644
index 00000000..04bc0b76
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
@@ -0,0 +1,744 @@
+/*
+ * 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 : builder.getAllFields().keySet()) {
+ if (fieldDescriptor.isRepeated()) {
+ dup.repeatedFields.put(fieldDescriptor, getRepeatedField(context, fieldDescriptor));
+ } else if (builder.hasField(fieldDescriptor)) {
+ dup.fields.put(fieldDescriptor, wrapField(context, fieldDescriptor, builder.getField(fieldDescriptor)));
+ }
+ }
+ for (Descriptors.FieldDescriptor fieldDescriptor : fields.keySet()) {
+ dup.fields.put(fieldDescriptor, fields.get(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")
+ 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.respondsTo("to_h")) {
+ value = Helpers.invoke(context, value, "to_h");
+ }
+ ret.fastASet(runtime.newString(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)));
+ }
+ 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();
+ if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE) {
+ typeClass = ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
+ } else if (fieldType == Descriptors.FieldDescriptor.Type.ENUM) {
+ typeClass = ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor)).enummodule(context);
+ }
+ Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
+ // Convert integer enum to symbol
+ if (fieldType == Descriptors.FieldDescriptor.Type.ENUM) {
+ 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());
+ }
+ }
+ this.fields.put(fieldDescriptor, value);
+ }
+ }
+ 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;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessageBuilderContext.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessageBuilderContext.java
new file mode 100644
index 00000000..a619b803
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessageBuilderContext.java
@@ -0,0 +1,217 @@
+/*
+ * 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.Descriptors;
+import org.jruby.*;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.Binding;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+@JRubyClass(name = "MessageBuilderContext")
+public class RubyMessageBuilderContext extends RubyObject {
+ public static void createRubyMessageBuilderContext(Ruby runtime) {
+ RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cMessageBuilderContext = protobuf.defineClassUnder("MessageBuilderContext", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyMessageBuilderContext(runtime, klazz);
+ }
+ });
+ cMessageBuilderContext.defineAnnotatedMethods(RubyMessageBuilderContext.class);
+ }
+
+ public RubyMessageBuilderContext(Ruby ruby, RubyClass klazz) {
+ super(ruby, klazz);
+ }
+
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context, IRubyObject descriptor, IRubyObject rubyBuilder) {
+ this.cFieldDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
+ this.cDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Descriptor");
+ this.cOneofDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::OneofDescriptor");
+ this.cOneofBuilderContext = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::OneofBuilderContext");
+ this.descriptor = (RubyDescriptor) descriptor;
+ this.builder = (RubyBuilder) rubyBuilder;
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * MessageBuilderContext.optional(name, type, number, type_class = nil)
+ *
+ * Defines a new optional field on this message type with the given type, tag
+ * number, and type class (for message and enum fields). The type must be a Ruby
+ * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
+ * string, if present (as accepted by FieldDescriptor#submsg_name=).
+ */
+ @JRubyMethod(required = 3, optional = 1)
+ public IRubyObject optional(ThreadContext context, IRubyObject[] args) {
+ Ruby runtime = context.runtime;
+ IRubyObject typeClass = runtime.getNil();
+ if (args.length > 3) typeClass = args[3];
+ msgdefAddField(context, "optional", args[0], args[1], args[2], typeClass);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * MessageBuilderContext.required(name, type, number, type_class = nil)
+ *
+ * Defines a new required field on this message type with the given type, tag
+ * number, and type class (for message and enum fields). The type must be a Ruby
+ * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
+ * string, if present (as accepted by FieldDescriptor#submsg_name=).
+ *
+ * Proto3 does not have required fields, but this method exists for
+ * completeness. Any attempt to add a message type with required fields to a
+ * pool will currently result in an error.
+ */
+ @JRubyMethod(required = 3, optional = 1)
+ public IRubyObject required(ThreadContext context, IRubyObject[] args) {
+ IRubyObject typeClass = context.runtime.getNil();
+ if (args.length > 3) typeClass = args[3];
+ msgdefAddField(context, "required", args[0], args[1], args[2], typeClass);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * MessageBuilderContext.repeated(name, type, number, type_class = nil)
+ *
+ * Defines a new repeated field on this message type with the given type, tag
+ * number, and type class (for message and enum fields). The type must be a Ruby
+ * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
+ * string, if present (as accepted by FieldDescriptor#submsg_name=).
+ */
+ @JRubyMethod(required = 3, optional = 1)
+ public IRubyObject repeated(ThreadContext context, IRubyObject[] args) {
+ IRubyObject typeClass = context.runtime.getNil();
+ if (args.length > 3) typeClass = args[3];
+ msgdefAddField(context, "repeated", args[0], args[1], args[2], typeClass);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * MessageBuilderContext.map(name, key_type, value_type, number,
+ * value_type_class = nil)
+ *
+ * Defines a new map field on this message type with the given key and value
+ * types, tag number, and type class (for message and enum value types). The key
+ * type must be :int32/:uint32/:int64/:uint64, :bool, or :string. The value type
+ * type must be a Ruby symbol (as accepted by FieldDescriptor#type=) and the
+ * type_class must be a string, if present (as accepted by
+ * FieldDescriptor#submsg_name=).
+ */
+ @JRubyMethod(required = 4, optional = 1)
+ public IRubyObject map(ThreadContext context, IRubyObject[] args) {
+ Ruby runtime = context.runtime;
+ IRubyObject name = args[0];
+ IRubyObject keyType = args[1];
+ IRubyObject valueType = args[2];
+ IRubyObject number = args[3];
+ IRubyObject typeClass = args.length > 4 ? args[4] : context.runtime.getNil();
+
+ // Validate the key type. We can't accept enums, messages, or floats/doubles
+ // as map keys. (We exclude these explicitly, and the field-descriptor setter
+ // below then ensures that the type is one of the remaining valid options.)
+ if (keyType.equals(RubySymbol.newSymbol(runtime, "float")) ||
+ keyType.equals(RubySymbol.newSymbol(runtime, "double")) ||
+ keyType.equals(RubySymbol.newSymbol(runtime, "enum")) ||
+ keyType.equals(RubySymbol.newSymbol(runtime, "message")))
+ throw runtime.newArgumentError("Cannot add a map field with a float, double, enum, or message type.");
+
+ // Create a new message descriptor for the map entry message, and create a
+ // repeated submessage field here with that type.
+ RubyDescriptor mapentryDesc = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK);
+ IRubyObject mapentryDescName = RubySymbol.newSymbol(runtime, name).id2name(context);
+ mapentryDesc.setName(context, mapentryDescName);
+ mapentryDesc.setMapEntry(true);
+
+ //optional <type> key = 1;
+ RubyFieldDescriptor keyField = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
+ keyField.setName(context, runtime.newString("key"));
+ keyField.setLabel(context, RubySymbol.newSymbol(runtime, "optional"));
+ keyField.setNumber(context, runtime.newFixnum(1));
+ keyField.setType(context, keyType);
+ mapentryDesc.addField(context, keyField);
+
+ //optional <type> value = 2;
+ RubyFieldDescriptor valueField = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
+ valueField.setName(context, runtime.newString("value"));
+ valueField.setLabel(context, RubySymbol.newSymbol(runtime, "optional"));
+ valueField.setNumber(context, runtime.newFixnum(2));
+ valueField.setType(context, valueType);
+ if (! typeClass.isNil()) valueField.setSubmsgName(context, typeClass);
+ mapentryDesc.addField(context, valueField);
+
+ // Add the map-entry message type to the current builder, and use the type to
+ // create the map field itself.
+ this.builder.pendingList.add(mapentryDesc);
+
+ msgdefAddField(context, "repeated", name, runtime.newSymbol("message"), number, mapentryDescName);
+ return runtime.getNil();
+ }
+
+ @JRubyMethod
+ public IRubyObject oneof(ThreadContext context, IRubyObject name, Block block) {
+ RubyOneofDescriptor oneofdef = (RubyOneofDescriptor)
+ cOneofDescriptor.newInstance(context, Block.NULL_BLOCK);
+ RubyOneofBuilderContext ctx = (RubyOneofBuilderContext)
+ cOneofBuilderContext.newInstance(context, oneofdef, Block.NULL_BLOCK);
+ oneofdef.setName(context, name);
+ Binding binding = block.getBinding();
+ binding.setSelf(ctx);
+ block.yieldSpecific(context);
+ descriptor.addOneof(context, oneofdef);
+ return context.runtime.getNil();
+ }
+
+ private void msgdefAddField(ThreadContext context, String label, IRubyObject name,
+ IRubyObject type, IRubyObject number, IRubyObject typeClass) {
+ descriptor.addField(context,
+ Utils.msgdefCreateField(context, label, name, type, number, typeClass, cFieldDescriptor));
+ }
+
+ private RubyDescriptor descriptor;
+ private RubyBuilder builder;
+ private RubyClass cFieldDescriptor;
+ private RubyClass cOneofDescriptor;
+ private RubyClass cOneofBuilderContext;
+ private RubyClass cDescriptor;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofBuilderContext.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofBuilderContext.java
new file mode 100644
index 00000000..c9b99e04
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofBuilderContext.java
@@ -0,0 +1,84 @@
+/*
+ * 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 org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+@JRubyClass(name = "OneofBuilderContext")
+public class RubyOneofBuilderContext extends RubyObject {
+ public static void createRubyOneofBuilderContext(Ruby runtime) {
+ RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyModule internal = protobuf.defineModuleUnder("Internal");
+ RubyClass cRubyOneofBuidlerContext = internal.defineClassUnder("OneofBuilderContext", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
+ return new RubyOneofBuilderContext(ruby, rubyClass);
+ }
+ });
+ cRubyOneofBuidlerContext.defineAnnotatedMethods(RubyOneofBuilderContext.class);
+ }
+
+ public RubyOneofBuilderContext(Ruby ruby, RubyClass rubyClass) {
+ super(ruby, rubyClass);
+ }
+
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context, IRubyObject oneofdef) {
+ this.descriptor = (RubyOneofDescriptor) oneofdef;
+ this.cFieldDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
+ return this;
+ }
+
+ @JRubyMethod(required = 3, optional = 1)
+ public IRubyObject optional(ThreadContext context, IRubyObject[] args) {
+ IRubyObject name = args[0];
+ IRubyObject type = args[1];
+ IRubyObject number = args[2];
+ IRubyObject typeClass = args.length > 3 ? args[3] : context.runtime.getNil();
+ RubyFieldDescriptor fieldDescriptor = Utils.msgdefCreateField(context, "optional",
+ name, type, number, typeClass, cFieldDescriptor);
+ descriptor.addField(context, fieldDescriptor);
+ return this;
+ }
+
+ private RubyOneofDescriptor descriptor;
+ private RubyClass cFieldDescriptor;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java
new file mode 100644
index 00000000..cc4ab662
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java
@@ -0,0 +1,124 @@
+package com.google.protobuf.jruby;
+
+import com.google.protobuf.DescriptorProtos;
+import com.google.protobuf.Descriptors;
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+import java.util.*;
+
+@JRubyClass(name = "OneofDescriptor", include = "Enumerable")
+public class RubyOneofDescriptor extends RubyObject {
+
+ public static void createRubyOneofDescriptor(Ruby runtime) {
+ RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cRubyOneofDescriptor = protobuf.defineClassUnder("OneofDescriptor", runtime.getObject(), new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
+ return new RubyOneofDescriptor(ruby, rubyClass);
+ }
+ });
+ cRubyOneofDescriptor.defineAnnotatedMethods(RubyOneofDescriptor.class);
+ cRubyOneofDescriptor.includeModule(runtime.getEnumerable());
+ }
+
+ public RubyOneofDescriptor(Ruby ruby, RubyClass rubyClass) {
+ super(ruby, rubyClass);
+ }
+
+ @JRubyMethod
+ public IRubyObject initialize(ThreadContext context) {
+ builder = DescriptorProtos.OneofDescriptorProto.newBuilder();
+ fields = new ArrayList<RubyFieldDescriptor>();
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * OneofDescriptor.name => name
+ *
+ * Returns the name of this oneof.
+ */
+ @JRubyMethod(name = "name")
+ public IRubyObject getName(ThreadContext context) {
+ return name;
+ }
+
+ /*
+ * call-seq:
+ * OneofDescriptor.name = name
+ *
+ * Sets a new name for this oneof. The oneof must not have been added to a
+ * message descriptor yet.
+ */
+ @JRubyMethod(name = "name=")
+ public IRubyObject setName(ThreadContext context, IRubyObject name) {
+ this.name = context.runtime.newString(name.asJavaString());
+ this.builder.setName(name.asJavaString());
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * OneofDescriptor.add_field(field) => nil
+ *
+ * Adds a field to this oneof. The field may have been added to this oneof in
+ * the past, or the message to which this oneof belongs (if any), but may not
+ * have already been added to any other oneof or message. Otherwise, an
+ * exception is raised.
+ *
+ * All fields added to the oneof via this method will be automatically added to
+ * the message to which this oneof belongs, if it belongs to one currently, or
+ * else will be added to any message to which the oneof is later added at the
+ * time that it is added.
+ */
+ @JRubyMethod(name = "add_field")
+ public IRubyObject addField(ThreadContext context, IRubyObject obj) {
+ RubyFieldDescriptor fieldDescriptor = (RubyFieldDescriptor) obj;
+ fieldDescriptor.setOneofName(this.name);
+ fields.add(fieldDescriptor);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * OneofDescriptor.each(&block) => nil
+ *
+ * Iterates through fields in this oneof, yielding to the block on each one.
+ */
+ @JRubyMethod
+ public IRubyObject each(ThreadContext context, Block block) {
+ for (RubyFieldDescriptor field : fields) {
+ block.yieldSpecific(context, field);
+ }
+ return context.runtime.getNil();
+ }
+
+ public DescriptorProtos.OneofDescriptorProto build(int index) {
+ for (RubyFieldDescriptor field: fields) {
+ field.setOneofIndex(index);
+ }
+ return this.builder.build();
+ }
+
+ protected Collection<RubyFieldDescriptor> getFields() {
+ return fields;
+ }
+
+ protected Descriptors.OneofDescriptor getOneofDescriptor() {
+ RubyFieldDescriptor fieldDescriptor = fields.get(0);
+ return fieldDescriptor.getFieldDef().getContainingOneof();
+ }
+
+ private IRubyObject name;
+ private DescriptorProtos.OneofDescriptorProto.Builder builder;
+ private List<RubyFieldDescriptor> fields;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java
new file mode 100644
index 00000000..cb3fcd48
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java
@@ -0,0 +1,118 @@
+/*
+ * 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 org.jruby.Ruby;
+import org.jruby.RubyModule;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.anno.JRubyModule;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+@JRubyModule(name = "Protobuf")
+public class RubyProtobuf {
+
+ public static void createProtobuf(Ruby runtime) {
+ RubyModule mGoogle = runtime.getModule("Google");
+ RubyModule mProtobuf = mGoogle.defineModuleUnder("Protobuf");
+ mProtobuf.defineAnnotatedMethods(RubyProtobuf.class);
+ }
+
+ /*
+ * call-seq:
+ * Google::Protobuf.encode(msg) => bytes
+ *
+ * Encodes the given message object to protocol buffers wire format. This is an
+ * alternative to the #encode method on msg's class.
+ */
+ @JRubyMethod(meta = true)
+ public static IRubyObject encode(ThreadContext context, IRubyObject self, IRubyObject message) {
+ return RubyMessage.encode(context, message.getMetaClass(), message);
+ }
+
+ /*
+ * call-seq:
+ * Google::Protobuf.decode(class, bytes) => msg
+ *
+ * Decodes the given bytes as protocol buffers wire format under the
+ * interpretation given by the given class's message definition. This is an
+ * alternative to the #decode method on the given class.
+ */
+ @JRubyMethod(meta = true)
+ public static IRubyObject decode(ThreadContext context, IRubyObject self, IRubyObject klazz, IRubyObject message) {
+ return RubyMessage.decode(context, klazz, message);
+ }
+
+ /*
+ * call-seq:
+ * Google::Protobuf.encode_json(msg) => json_string
+ *
+ * Encodes the given message object to its JSON representation. This is an
+ * alternative to the #encode_json method on msg's class.
+ */
+ @JRubyMethod(name = "encode_json", meta = true)
+ public static IRubyObject encodeJson(ThreadContext context, IRubyObject self, IRubyObject message) {
+ return RubyMessage.encodeJson(context, message.getMetaClass(), message);
+ }
+
+ /*
+ * call-seq:
+ * Google::Protobuf.decode_json(class, json_string) => msg
+ *
+ * Decodes the given JSON string under the interpretation given by the given
+ * class's message definition. This is an alternative to the #decode_json method
+ * on the given class.
+ */
+ @JRubyMethod(name = "decode_json", meta = true)
+ public static IRubyObject decodeJson(ThreadContext context, IRubyObject self, IRubyObject klazz, IRubyObject message) {
+ return RubyMessage.decodeJson(context, klazz, message);
+ }
+
+ /*
+ * call-seq:
+ * Google::Protobuf.deep_copy(obj) => copy_of_obj
+ *
+ * Performs a deep copy of either a RepeatedField instance or a message object,
+ * recursively copying its members.
+ */
+ @JRubyMethod(name = "deep_copy", meta = true)
+ public static IRubyObject deepCopy(ThreadContext context, IRubyObject self, IRubyObject message) {
+ if (message instanceof RubyMessage) {
+ return ((RubyMessage) message).deepCopy(context);
+ } else if (message instanceof RubyRepeatedField) {
+ return ((RubyRepeatedField) message).deepCopy(context);
+ } else {
+ return ((RubyMap) message).deepCopy(context);
+ }
+ }
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java
new file mode 100644
index 00000000..9788317a
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java
@@ -0,0 +1,391 @@
+/*
+ * 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.Descriptors;
+import org.jruby.*;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+@JRubyClass(name = "RepeatedClass", include = "Enumerable")
+public class RubyRepeatedField extends RubyObject {
+ public static void createRubyRepeatedField(Ruby runtime) {
+ RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
+ RubyClass cRepeatedField = mProtobuf.defineClassUnder("RepeatedField", runtime.getObject(),
+ new ObjectAllocator() {
+ @Override
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new RubyRepeatedField(runtime, klazz);
+ }
+ });
+ cRepeatedField.defineAnnotatedMethods(RubyRepeatedField.class);
+ cRepeatedField.includeModule(runtime.getEnumerable());
+ }
+
+ public RubyRepeatedField(Ruby runtime, RubyClass klazz) {
+ super(runtime, klazz);
+ }
+
+ public RubyRepeatedField(Ruby runtime, RubyClass klazz, Descriptors.FieldDescriptor.Type fieldType, IRubyObject typeClass) {
+ this(runtime, klazz);
+ this.fieldType = fieldType;
+ this.storage = runtime.newArray();
+ this.typeClass = typeClass;
+ }
+
+ @JRubyMethod(required = 1, optional = 2)
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
+ Ruby runtime = context.runtime;
+ this.storage = runtime.newArray();
+ IRubyObject ary = null;
+ if (!(args[0] instanceof RubySymbol)) {
+ throw runtime.newArgumentError("Expected Symbol for type name");
+ }
+ this.fieldType = Utils.rubyToFieldType(args[0]);
+ if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE
+ || fieldType == Descriptors.FieldDescriptor.Type.ENUM) {
+ if (args.length < 2)
+ throw runtime.newArgumentError("Expected at least 2 arguments for message/enum");
+ typeClass = args[1];
+ if (args.length > 2)
+ ary = args[2];
+ Utils.validateTypeClass(context, fieldType, typeClass);
+ } else {
+ if (args.length > 2)
+ throw runtime.newArgumentError("Too many arguments: expected 1 or 2");
+ if (args.length > 1)
+ ary = args[1];
+ }
+ if (ary != null) {
+ RubyArray arr = ary.convertToArray();
+ for (int i = 0; i < arr.size(); i++) {
+ this.storage.add(arr.eltInternal(i));
+ }
+ }
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.[]=(index, value)
+ *
+ * Sets the element at the given index. On out-of-bounds assignments, extends
+ * the array and fills the hole (if any) with default values.
+ */
+ @JRubyMethod(name = "[]=")
+ public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) {
+ Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
+ this.storage.set(RubyNumeric.num2int(index), value);
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.[](index) => value
+ *
+ * Accesses the element at the given index. Throws an exception on out-of-bounds
+ * errors.
+ */
+ @JRubyMethod(name = "[]")
+ public IRubyObject index(ThreadContext context, IRubyObject index) {
+ return this.storage.eltInternal(RubyNumeric.num2int(index));
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.insert(*args)
+ *
+ * Pushes each arg in turn onto the end of the repeated field.
+ */
+ @JRubyMethod(rest = true)
+ public IRubyObject insert(ThreadContext context, IRubyObject[] args) {
+ for (int i = 0; i < args.length; i++) {
+ Utils.checkType(context, fieldType, args[i], (RubyModule) typeClass);
+ this.storage.add(args[i]);
+ }
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.push(value)
+ *
+ * Adds a new element to the repeated field.
+ */
+ @JRubyMethod(name = {"push", "<<"})
+ public IRubyObject push(ThreadContext context, IRubyObject value) {
+ Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
+ this.storage.add(value);
+ return this;
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.pop => value
+ *
+ * Removes the last element and returns it. Throws an exception if the repeated
+ * field is empty.
+ */
+ @JRubyMethod
+ public IRubyObject pop(ThreadContext context) {
+ IRubyObject ret = this.storage.last();
+ this.storage.remove(ret);
+ return ret;
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.replace(list)
+ *
+ * Replaces the contents of the repeated field with the given list of elements.
+ */
+ @JRubyMethod
+ public IRubyObject replace(ThreadContext context, IRubyObject list) {
+ RubyArray arr = (RubyArray) list;
+ checkArrayElementType(context, arr);
+ this.storage = arr;
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.clear
+ *
+ * Clears (removes all elements from) this repeated field.
+ */
+ @JRubyMethod
+ public IRubyObject clear(ThreadContext context) {
+ this.storage.clear();
+ return context.runtime.getNil();
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.length
+ *
+ * Returns the length of this repeated field.
+ */
+ @JRubyMethod(name = {"count", "length"})
+ public IRubyObject length(ThreadContext context) {
+ return context.runtime.newFixnum(this.storage.size());
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.+(other) => repeated field
+ *
+ * Returns a new repeated field that contains the concatenated list of this
+ * repeated field's elements and other's elements. The other (second) list may
+ * be either another repeated field or a Ruby array.
+ */
+ @JRubyMethod(name = "+")
+ public IRubyObject plus(ThreadContext context, IRubyObject list) {
+ RubyRepeatedField dup = (RubyRepeatedField) dup(context);
+ if (list instanceof RubyArray) {
+ checkArrayElementType(context, (RubyArray) list);
+ dup.storage.addAll((RubyArray) list);
+ } else {
+ RubyRepeatedField repeatedField = (RubyRepeatedField) list;
+ if (! fieldType.equals(repeatedField.fieldType) || (typeClass != null && !
+ typeClass.equals(repeatedField.typeClass)))
+ throw context.runtime.newArgumentError("Attempt to append RepeatedField with different element type.");
+ dup.storage.addAll((RubyArray) repeatedField.toArray(context));
+ }
+ return dup;
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.hash => hash_value
+ *
+ * Returns a hash value computed from this repeated field's elements.
+ */
+ @JRubyMethod
+ public IRubyObject hash(ThreadContext context) {
+ int hashCode = System.identityHashCode(this.storage);
+ return context.runtime.newFixnum(hashCode);
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.==(other) => boolean
+ *
+ * Compares this repeated field to another. Repeated fields are equal if their
+ * element types are equal, their lengths are equal, and each element is equal.
+ * Elements are compared as per normal Ruby semantics, by calling their :==
+ * methods (or performing a more efficient comparison for primitive types).
+ */
+ @JRubyMethod(name = "==")
+ public IRubyObject eq(ThreadContext context, IRubyObject other) {
+ return this.toArray(context).op_equal(context, other);
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.each(&block)
+ *
+ * Invokes the block once for each element of the repeated field. RepeatedField
+ * also includes Enumerable; combined with this method, the repeated field thus
+ * acts like an ordinary Ruby sequence.
+ */
+ @JRubyMethod
+ public IRubyObject each(ThreadContext context, Block block) {
+ this.storage.each(context, block);
+ return context.runtime.getNil();
+ }
+
+ @JRubyMethod(name = {"to_ary", "to_a"})
+ public IRubyObject toArray(ThreadContext context) {
+ for (int i = 0; i < this.storage.size(); i++) {
+ IRubyObject defaultValue = defaultValue(context);
+ if (storage.eltInternal(i).isNil()) {
+ storage.set(i, defaultValue);
+ }
+ }
+ return this.storage;
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.dup => repeated_field
+ *
+ * Duplicates this repeated field with a shallow copy. References to all
+ * non-primitive element objects (e.g., submessages) are shared.
+ */
+ @JRubyMethod
+ public IRubyObject dup(ThreadContext context) {
+ RubyRepeatedField dup = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass);
+ for (int i = 0; i < this.storage.size(); i++) {
+ dup.push(context, this.storage.eltInternal(i));
+ }
+ return dup;
+ }
+
+ /*
+ * call-seq:
+ * RepeatedField.inspect => string
+ *
+ * Returns a string representing this repeated field's elements. It will be
+ * formated as "[<element>, <element>, ...]", with each element's string
+ * representation computed by its own #inspect method.
+ */
+ @JRubyMethod
+ public IRubyObject inspect() {
+ StringBuilder str = new StringBuilder("[");
+ for (int i = 0; i < this.storage.size(); i++) {
+ str.append(storage.eltInternal(i).inspect());
+ str.append(", ");
+ }
+
+ if (str.length() > 1) {
+ str.replace(str.length() - 2, str.length(), "]");
+ } else {
+ str.append("]");
+ }
+
+ return getRuntime().newString(str.toString());
+ }
+
+ // Java API
+ protected IRubyObject get(int index) {
+ return this.storage.eltInternal(index);
+ }
+
+ protected RubyRepeatedField deepCopy(ThreadContext context) {
+ RubyRepeatedField copy = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass);
+ for (int i = 0; i < size(); i++) {
+ IRubyObject value = storage.eltInternal(i);
+ if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE) {
+ copy.storage.add(((RubyMessage) value).deepCopy(context));
+ } else {
+ copy.storage.add(value);
+ }
+ }
+ return copy;
+ }
+
+ protected int size() {
+ return this.storage.size();
+ }
+
+ private IRubyObject defaultValue(ThreadContext context) {
+ SentinelOuterClass.Sentinel sentinel = SentinelOuterClass.Sentinel.getDefaultInstance();
+ Object value;
+ switch (fieldType) {
+ case INT32:
+ value = sentinel.getDefaultInt32();
+ break;
+ case INT64:
+ value = sentinel.getDefaultInt64();
+ break;
+ case UINT32:
+ value = sentinel.getDefaultUnit32();
+ break;
+ case UINT64:
+ value = sentinel.getDefaultUint64();
+ break;
+ case FLOAT:
+ value = sentinel.getDefaultFloat();
+ break;
+ case DOUBLE:
+ value = sentinel.getDefaultDouble();
+ break;
+ case BOOL:
+ value = sentinel.getDefaultBool();
+ break;
+ case BYTES:
+ value = sentinel.getDefaultBytes();
+ break;
+ case STRING:
+ value = sentinel.getDefaultString();
+ break;
+ default:
+ return context.runtime.getNil();
+ }
+ return Utils.wrapPrimaryValue(context, fieldType, value);
+ }
+
+ private void checkArrayElementType(ThreadContext context, RubyArray arr) {
+ for (int i = 0; i < arr.getLength(); i++) {
+ Utils.checkType(context, fieldType, arr.eltInternal(i), (RubyModule) typeClass);
+ }
+ }
+
+ private RubyArray storage;
+ private Descriptors.FieldDescriptor.Type fieldType;
+ private IRubyObject typeClass;
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java b/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java
new file mode 100644
index 00000000..54f2c729
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java
@@ -0,0 +1,776 @@
+/*
+ * 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.
+ */
+
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: sentinel.proto
+
+package com.google.protobuf.jruby;
+
+public final class SentinelOuterClass {
+ private SentinelOuterClass() {}
+ public static void registerAllExtensions(
+ com.google.protobuf.ExtensionRegistry registry) {
+ }
+ public interface SentinelOrBuilder extends
+ // @@protoc_insertion_point(interface_extends:com.google.protobuf.jruby.Sentinel)
+ com.google.protobuf.MessageOrBuilder {
+
+ /**
+ * <code>optional int32 default_int32 = 1;</code>
+ */
+ int getDefaultInt32();
+
+ /**
+ * <code>optional int64 default_int64 = 2;</code>
+ */
+ long getDefaultInt64();
+
+ /**
+ * <code>optional uint32 default_unit32 = 3;</code>
+ */
+ int getDefaultUnit32();
+
+ /**
+ * <code>optional uint64 default_uint64 = 4;</code>
+ */
+ long getDefaultUint64();
+
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ java.lang.String getDefaultString();
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ com.google.protobuf.ByteString
+ getDefaultStringBytes();
+
+ /**
+ * <code>optional bool default_bool = 6;</code>
+ */
+ boolean getDefaultBool();
+
+ /**
+ * <code>optional float default_float = 7;</code>
+ */
+ float getDefaultFloat();
+
+ /**
+ * <code>optional double default_double = 8;</code>
+ */
+ double getDefaultDouble();
+
+ /**
+ * <code>optional bytes default_bytes = 9;</code>
+ */
+ com.google.protobuf.ByteString getDefaultBytes();
+ }
+ /**
+ * Protobuf type {@code com.google.protobuf.jruby.Sentinel}
+ */
+ public static final class Sentinel extends
+ com.google.protobuf.GeneratedMessage implements
+ // @@protoc_insertion_point(message_implements:com.google.protobuf.jruby.Sentinel)
+ SentinelOrBuilder {
+ // Use Sentinel.newBuilder() to construct.
+ private Sentinel(com.google.protobuf.GeneratedMessage.Builder builder) {
+ super(builder);
+ }
+ private Sentinel() {
+ defaultInt32_ = 0;
+ defaultInt64_ = 0L;
+ defaultUnit32_ = 0;
+ defaultUint64_ = 0L;
+ defaultString_ = "";
+ defaultBool_ = false;
+ defaultFloat_ = 0F;
+ defaultDouble_ = 0D;
+ defaultBytes_ = com.google.protobuf.ByteString.EMPTY;
+ }
+
+ @java.lang.Override
+ public final com.google.protobuf.UnknownFieldSet
+ getUnknownFields() {
+ return com.google.protobuf.UnknownFieldSet.getDefaultInstance();
+ }
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable
+ .ensureFieldAccessorsInitialized(
+ com.google.protobuf.jruby.SentinelOuterClass.Sentinel.class, com.google.protobuf.jruby.SentinelOuterClass.Sentinel.Builder.class);
+ }
+
+ public static final com.google.protobuf.Parser<Sentinel> PARSER =
+ new com.google.protobuf.AbstractParser<Sentinel>() {
+ public Sentinel parsePartialFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ Builder builder = newBuilder();
+ try {
+ builder.mergeFrom(input, extensionRegistry);
+ } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+ throw e.setUnfinishedMessage(builder.buildPartial());
+ } catch (java.io.IOException e) {
+ throw new com.google.protobuf.InvalidProtocolBufferException(
+ e.getMessage()).setUnfinishedMessage(builder.buildPartial());
+ }
+ return builder.buildPartial();
+ }
+ };
+
+ @java.lang.Override
+ public com.google.protobuf.Parser<Sentinel> getParserForType() {
+ return PARSER;
+ }
+
+ public static final int DEFAULT_INT32_FIELD_NUMBER = 1;
+ private int defaultInt32_;
+ /**
+ * <code>optional int32 default_int32 = 1;</code>
+ */
+ public int getDefaultInt32() {
+ return defaultInt32_;
+ }
+
+ public static final int DEFAULT_INT64_FIELD_NUMBER = 2;
+ private long defaultInt64_;
+ /**
+ * <code>optional int64 default_int64 = 2;</code>
+ */
+ public long getDefaultInt64() {
+ return defaultInt64_;
+ }
+
+ public static final int DEFAULT_UNIT32_FIELD_NUMBER = 3;
+ private int defaultUnit32_;
+ /**
+ * <code>optional uint32 default_unit32 = 3;</code>
+ */
+ public int getDefaultUnit32() {
+ return defaultUnit32_;
+ }
+
+ public static final int DEFAULT_UINT64_FIELD_NUMBER = 4;
+ private long defaultUint64_;
+ /**
+ * <code>optional uint64 default_uint64 = 4;</code>
+ */
+ public long getDefaultUint64() {
+ return defaultUint64_;
+ }
+
+ public static final int DEFAULT_STRING_FIELD_NUMBER = 5;
+ private java.lang.Object defaultString_;
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ public java.lang.String getDefaultString() {
+ java.lang.Object ref = defaultString_;
+ if (ref instanceof java.lang.String) {
+ return (java.lang.String) ref;
+ } else {
+ com.google.protobuf.ByteString bs =
+ (com.google.protobuf.ByteString) ref;
+ java.lang.String s = bs.toStringUtf8();
+ if (bs.isValidUtf8()) {
+ defaultString_ = s;
+ }
+ return s;
+ }
+ }
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ public com.google.protobuf.ByteString
+ getDefaultStringBytes() {
+ java.lang.Object ref = defaultString_;
+ if (ref instanceof java.lang.String) {
+ com.google.protobuf.ByteString b =
+ com.google.protobuf.ByteString.copyFromUtf8(
+ (java.lang.String) ref);
+ defaultString_ = b;
+ return b;
+ } else {
+ return (com.google.protobuf.ByteString) ref;
+ }
+ }
+
+ public static final int DEFAULT_BOOL_FIELD_NUMBER = 6;
+ private boolean defaultBool_;
+ /**
+ * <code>optional bool default_bool = 6;</code>
+ */
+ public boolean getDefaultBool() {
+ return defaultBool_;
+ }
+
+ public static final int DEFAULT_FLOAT_FIELD_NUMBER = 7;
+ private float defaultFloat_;
+ /**
+ * <code>optional float default_float = 7;</code>
+ */
+ public float getDefaultFloat() {
+ return defaultFloat_;
+ }
+
+ public static final int DEFAULT_DOUBLE_FIELD_NUMBER = 8;
+ private double defaultDouble_;
+ /**
+ * <code>optional double default_double = 8;</code>
+ */
+ public double getDefaultDouble() {
+ return defaultDouble_;
+ }
+
+ public static final int DEFAULT_BYTES_FIELD_NUMBER = 9;
+ private com.google.protobuf.ByteString defaultBytes_;
+ /**
+ * <code>optional bytes default_bytes = 9;</code>
+ */
+ public com.google.protobuf.ByteString getDefaultBytes() {
+ return defaultBytes_;
+ }
+
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return PARSER.parseFrom(data);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return PARSER.parseFrom(data, extensionRegistry);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return PARSER.parseFrom(data);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return PARSER.parseFrom(data, extensionRegistry);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return PARSER.parseFrom(input);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return PARSER.parseFrom(input, extensionRegistry);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return PARSER.parseDelimitedFrom(input);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return PARSER.parseDelimitedFrom(input, extensionRegistry);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return PARSER.parseFrom(input);
+ }
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return PARSER.parseFrom(input, extensionRegistry);
+ }
+
+ public static Builder newBuilder() { return new Builder(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(com.google.protobuf.jruby.SentinelOuterClass.Sentinel prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ /**
+ * Protobuf type {@code com.google.protobuf.jruby.Sentinel}
+ */
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder<Builder> implements
+ // @@protoc_insertion_point(builder_implements:com.google.protobuf.jruby.Sentinel)
+ com.google.protobuf.jruby.SentinelOuterClass.SentinelOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable
+ .ensureFieldAccessorsInitialized(
+ com.google.protobuf.jruby.SentinelOuterClass.Sentinel.class, com.google.protobuf.jruby.SentinelOuterClass.Sentinel.Builder.class);
+ }
+
+ // Construct using com.google.protobuf.jruby.SentinelOuterClass.Sentinel.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ public Builder clear() {
+ super.clear();
+ defaultInt32_ = 0;
+
+ defaultInt64_ = 0L;
+
+ defaultUnit32_ = 0;
+
+ defaultUint64_ = 0L;
+
+ defaultString_ = "";
+
+ defaultBool_ = false;
+
+ defaultFloat_ = 0F;
+
+ defaultDouble_ = 0D;
+
+ defaultBytes_ = com.google.protobuf.ByteString.EMPTY;
+
+ return this;
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_descriptor;
+ }
+
+ public com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstanceForType() {
+ return com.google.protobuf.jruby.SentinelOuterClass.Sentinel.getDefaultInstance();
+ }
+
+ public com.google.protobuf.jruby.SentinelOuterClass.Sentinel build() {
+ com.google.protobuf.jruby.SentinelOuterClass.Sentinel result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ public com.google.protobuf.jruby.SentinelOuterClass.Sentinel buildPartial() {
+ com.google.protobuf.jruby.SentinelOuterClass.Sentinel result = new com.google.protobuf.jruby.SentinelOuterClass.Sentinel(this);
+ result.defaultInt32_ = defaultInt32_;
+ result.defaultInt64_ = defaultInt64_;
+ result.defaultUnit32_ = defaultUnit32_;
+ result.defaultUint64_ = defaultUint64_;
+ result.defaultString_ = defaultString_;
+ result.defaultBool_ = defaultBool_;
+ result.defaultFloat_ = defaultFloat_;
+ result.defaultDouble_ = defaultDouble_;
+ result.defaultBytes_ = defaultBytes_;
+ onBuilt();
+ return result;
+ }
+
+
+ private int defaultInt32_ ;
+ /**
+ * <code>optional int32 default_int32 = 1;</code>
+ */
+ public int getDefaultInt32() {
+ return defaultInt32_;
+ }
+ /**
+ * <code>optional int32 default_int32 = 1;</code>
+ */
+ public Builder setDefaultInt32(int value) {
+
+ defaultInt32_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional int32 default_int32 = 1;</code>
+ */
+ public Builder clearDefaultInt32() {
+
+ defaultInt32_ = 0;
+ onChanged();
+ return this;
+ }
+
+ private long defaultInt64_ ;
+ /**
+ * <code>optional int64 default_int64 = 2;</code>
+ */
+ public long getDefaultInt64() {
+ return defaultInt64_;
+ }
+ /**
+ * <code>optional int64 default_int64 = 2;</code>
+ */
+ public Builder setDefaultInt64(long value) {
+
+ defaultInt64_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional int64 default_int64 = 2;</code>
+ */
+ public Builder clearDefaultInt64() {
+
+ defaultInt64_ = 0L;
+ onChanged();
+ return this;
+ }
+
+ private int defaultUnit32_ ;
+ /**
+ * <code>optional uint32 default_unit32 = 3;</code>
+ */
+ public int getDefaultUnit32() {
+ return defaultUnit32_;
+ }
+ /**
+ * <code>optional uint32 default_unit32 = 3;</code>
+ */
+ public Builder setDefaultUnit32(int value) {
+
+ defaultUnit32_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional uint32 default_unit32 = 3;</code>
+ */
+ public Builder clearDefaultUnit32() {
+
+ defaultUnit32_ = 0;
+ onChanged();
+ return this;
+ }
+
+ private long defaultUint64_ ;
+ /**
+ * <code>optional uint64 default_uint64 = 4;</code>
+ */
+ public long getDefaultUint64() {
+ return defaultUint64_;
+ }
+ /**
+ * <code>optional uint64 default_uint64 = 4;</code>
+ */
+ public Builder setDefaultUint64(long value) {
+
+ defaultUint64_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional uint64 default_uint64 = 4;</code>
+ */
+ public Builder clearDefaultUint64() {
+
+ defaultUint64_ = 0L;
+ onChanged();
+ return this;
+ }
+
+ private java.lang.Object defaultString_ = "";
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ public java.lang.String getDefaultString() {
+ java.lang.Object ref = defaultString_;
+ if (!(ref instanceof java.lang.String)) {
+ com.google.protobuf.ByteString bs =
+ (com.google.protobuf.ByteString) ref;
+ java.lang.String s = bs.toStringUtf8();
+ if (bs.isValidUtf8()) {
+ defaultString_ = s;
+ }
+ return s;
+ } else {
+ return (java.lang.String) ref;
+ }
+ }
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ public com.google.protobuf.ByteString
+ getDefaultStringBytes() {
+ java.lang.Object ref = defaultString_;
+ if (ref instanceof String) {
+ com.google.protobuf.ByteString b =
+ com.google.protobuf.ByteString.copyFromUtf8(
+ (java.lang.String) ref);
+ defaultString_ = b;
+ return b;
+ } else {
+ return (com.google.protobuf.ByteString) ref;
+ }
+ }
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ public Builder setDefaultString(
+ java.lang.String value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+
+ defaultString_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ public Builder clearDefaultString() {
+
+ defaultString_ = getDefaultInstance().getDefaultString();
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional string default_string = 5;</code>
+ */
+ public Builder setDefaultStringBytes(
+ com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+
+ defaultString_ = value;
+ onChanged();
+ return this;
+ }
+
+ private boolean defaultBool_ ;
+ /**
+ * <code>optional bool default_bool = 6;</code>
+ */
+ public boolean getDefaultBool() {
+ return defaultBool_;
+ }
+ /**
+ * <code>optional bool default_bool = 6;</code>
+ */
+ public Builder setDefaultBool(boolean value) {
+
+ defaultBool_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional bool default_bool = 6;</code>
+ */
+ public Builder clearDefaultBool() {
+
+ defaultBool_ = false;
+ onChanged();
+ return this;
+ }
+
+ private float defaultFloat_ ;
+ /**
+ * <code>optional float default_float = 7;</code>
+ */
+ public float getDefaultFloat() {
+ return defaultFloat_;
+ }
+ /**
+ * <code>optional float default_float = 7;</code>
+ */
+ public Builder setDefaultFloat(float value) {
+
+ defaultFloat_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional float default_float = 7;</code>
+ */
+ public Builder clearDefaultFloat() {
+
+ defaultFloat_ = 0F;
+ onChanged();
+ return this;
+ }
+
+ private double defaultDouble_ ;
+ /**
+ * <code>optional double default_double = 8;</code>
+ */
+ public double getDefaultDouble() {
+ return defaultDouble_;
+ }
+ /**
+ * <code>optional double default_double = 8;</code>
+ */
+ public Builder setDefaultDouble(double value) {
+
+ defaultDouble_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional double default_double = 8;</code>
+ */
+ public Builder clearDefaultDouble() {
+
+ defaultDouble_ = 0D;
+ onChanged();
+ return this;
+ }
+
+ private com.google.protobuf.ByteString defaultBytes_ = com.google.protobuf.ByteString.EMPTY;
+ /**
+ * <code>optional bytes default_bytes = 9;</code>
+ */
+ public com.google.protobuf.ByteString getDefaultBytes() {
+ return defaultBytes_;
+ }
+ /**
+ * <code>optional bytes default_bytes = 9;</code>
+ */
+ public Builder setDefaultBytes(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+
+ defaultBytes_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * <code>optional bytes default_bytes = 9;</code>
+ */
+ public Builder clearDefaultBytes() {
+
+ defaultBytes_ = getDefaultInstance().getDefaultBytes();
+ onChanged();
+ return this;
+ }
+ public final Builder setUnknownFields(
+ final com.google.protobuf.UnknownFieldSet unknownFields) {
+ return this;
+ }
+
+ public final Builder mergeUnknownFields(
+ final com.google.protobuf.UnknownFieldSet unknownFields) {
+ return this;
+ }
+
+
+ // @@protoc_insertion_point(builder_scope:com.google.protobuf.jruby.Sentinel)
+ }
+
+ // @@protoc_insertion_point(class_scope:com.google.protobuf.jruby.Sentinel)
+ private static final com.google.protobuf.jruby.SentinelOuterClass.Sentinel defaultInstance;static {
+ defaultInstance = new com.google.protobuf.jruby.SentinelOuterClass.Sentinel();
+ }
+
+ public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ }
+
+ private static final com.google.protobuf.Descriptors.Descriptor
+ internal_static_com_google_protobuf_jruby_Sentinel_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable;
+
+ public static com.google.protobuf.Descriptors.FileDescriptor
+ getDescriptor() {
+ return descriptor;
+ }
+ private static com.google.protobuf.Descriptors.FileDescriptor
+ descriptor;
+ static {
+ java.lang.String[] descriptorData = {
+ "\n\016sentinel.proto\022\031com.google.protobuf.jr" +
+ "uby\"\334\001\n\010Sentinel\022\025\n\rdefault_int32\030\001 \001(\005\022" +
+ "\025\n\rdefault_int64\030\002 \001(\003\022\026\n\016default_unit32" +
+ "\030\003 \001(\r\022\026\n\016default_uint64\030\004 \001(\004\022\026\n\016defaul" +
+ "t_string\030\005 \001(\t\022\024\n\014default_bool\030\006 \001(\010\022\025\n\r" +
+ "default_float\030\007 \001(\002\022\026\n\016default_double\030\010 " +
+ "\001(\001\022\025\n\rdefault_bytes\030\t \001(\014B\002H\002b\006proto3"
+ };
+ com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+ new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() {
+ public com.google.protobuf.ExtensionRegistry assignDescriptors(
+ com.google.protobuf.Descriptors.FileDescriptor root) {
+ descriptor = root;
+ return null;
+ }
+ };
+ com.google.protobuf.Descriptors.FileDescriptor
+ .internalBuildGeneratedFileFrom(descriptorData,
+ new com.google.protobuf.Descriptors.FileDescriptor[] {
+ }, assigner);
+ internal_static_com_google_protobuf_jruby_Sentinel_descriptor =
+ getDescriptor().getMessageTypes().get(0);
+ internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_com_google_protobuf_jruby_Sentinel_descriptor,
+ new java.lang.String[] { "DefaultInt32", "DefaultInt64", "DefaultUnit32", "DefaultUint64", "DefaultString", "DefaultBool", "DefaultFloat", "DefaultDouble", "DefaultBytes", });
+ }
+
+ // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/Utils.java b/ruby/src/main/java/com/google/protobuf/jruby/Utils.java
new file mode 100644
index 00000000..596a0979
--- /dev/null
+++ b/ruby/src/main/java/com/google/protobuf/jruby/Utils.java
@@ -0,0 +1,300 @@
+/*
+ * 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.ByteString;
+import com.google.protobuf.DescriptorProtos;
+import com.google.protobuf.Descriptors;
+import org.jcodings.Encoding;
+import org.jcodings.specific.ASCIIEncoding;
+import org.jcodings.specific.USASCIIEncoding;
+import org.jcodings.specific.UTF8Encoding;
+import org.jruby.*;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+import java.math.BigInteger;
+
+public class Utils {
+ public static Descriptors.FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) {
+ return Descriptors.FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase());
+ }
+
+ public static IRubyObject fieldTypeToRuby(ThreadContext context, Descriptors.FieldDescriptor.Type type) {
+ return fieldTypeToRuby(context, type.name());
+ }
+
+ public static IRubyObject fieldTypeToRuby(ThreadContext context, DescriptorProtos.FieldDescriptorProto.Type type) {
+ return fieldTypeToRuby(context, type.name());
+ }
+
+ private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) {
+
+ return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase());
+ }
+
+ public static void checkType(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType,
+ IRubyObject value, RubyModule typeClass) {
+ Ruby runtime = context.runtime;
+ Object val;
+ switch(fieldType) {
+ case INT32:
+ case INT64:
+ case UINT32:
+ case UINT64:
+ if (!isRubyNum(value)) {
+ throw runtime.newTypeError("Expected number type for integral field.");
+ }
+ switch(fieldType) {
+ case INT32:
+ RubyNumeric.num2int(value);
+ break;
+ case INT64:
+ RubyNumeric.num2long(value);
+ break;
+ case UINT32:
+ num2uint(value);
+ break;
+ default:
+ num2ulong(context.runtime, value);
+ break;
+ }
+ checkIntTypePrecision(context, fieldType, value);
+ break;
+ case FLOAT:
+ if (!isRubyNum(value))
+ throw runtime.newTypeError("Expected number type for float field.");
+ break;
+ case DOUBLE:
+ if (!isRubyNum(value))
+ throw runtime.newTypeError("Expected number type for double field.");
+ break;
+ case BOOL:
+ if (!(value instanceof RubyBoolean))
+ throw runtime.newTypeError("Invalid argument for boolean field.");
+ break;
+ case BYTES:
+ case STRING:
+ validateStringEncoding(context.runtime, fieldType, value);
+ break;
+ case MESSAGE:
+ if (value.getMetaClass() != typeClass) {
+ throw runtime.newTypeError(value, typeClass);
+ }
+ break;
+ case ENUM:
+ if (value instanceof RubySymbol) {
+ Descriptors.EnumDescriptor enumDescriptor =
+ ((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR)).getDescriptor();
+ val = enumDescriptor.findValueByName(value.asJavaString());
+ if (val == null)
+ throw runtime.newRangeError("Enum value " + value + " is not found.");
+ } else if(!isRubyNum(value)) {
+ throw runtime.newTypeError("Expected number or symbol type for enum field.");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ public static IRubyObject wrapPrimaryValue(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, Object value) {
+ Ruby runtime = context.runtime;
+ switch (fieldType) {
+ case INT32:
+ return runtime.newFixnum((Integer) value);
+ case INT64:
+ return runtime.newFixnum((Long) value);
+ case UINT32:
+ return runtime.newFixnum(((Integer) value) & (-1l >>> 32));
+ case UINT64:
+ long ret = (Long) value;
+ return ret >= 0 ? runtime.newFixnum(ret) :
+ RubyBignum.newBignum(runtime, UINT64_COMPLEMENTARY.add(new BigInteger(ret + "")));
+ case FLOAT:
+ return runtime.newFloat((Float) value);
+ case DOUBLE:
+ return runtime.newFloat((Double) value);
+ case BOOL:
+ return (Boolean) value ? runtime.getTrue() : runtime.getFalse();
+ case BYTES:
+ return runtime.newString(((ByteString) value).toStringUtf8());
+ case STRING:
+ return runtime.newString(value.toString());
+ default:
+ return runtime.getNil();
+ }
+ }
+
+ public static int num2uint(IRubyObject value) {
+ long longVal = RubyNumeric.num2long(value);
+ if (longVal > UINT_MAX)
+ throw value.getRuntime().newRangeError("Integer " + longVal + " too big to convert to 'unsigned int'");
+ long num = longVal;
+ if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE)
+ // encode to UINT32
+ num = (-longVal ^ (-1l >>> 32) ) + 1;
+ RubyNumeric.checkInt(value, num);
+ return (int) num;
+ }
+
+ public static long num2ulong(Ruby runtime, IRubyObject value) {
+ if (value instanceof RubyFloat) {
+ RubyBignum bignum = RubyBignum.newBignum(runtime, ((RubyFloat) value).getDoubleValue());
+ return RubyBignum.big2ulong(bignum);
+ } else if (value instanceof RubyBignum) {
+ return RubyBignum.big2ulong((RubyBignum) value);
+ } else {
+ return RubyNumeric.num2long(value);
+ }
+ }
+
+ public static void validateStringEncoding(Ruby runtime, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
+ if (!(value instanceof RubyString))
+ throw runtime.newTypeError("Invalid argument for string field.");
+ Encoding encoding = ((RubyString) value).getEncoding();
+ switch(type) {
+ case BYTES:
+ if (encoding != ASCIIEncoding.INSTANCE)
+ throw runtime.newTypeError("Encoding for bytes fields" +
+ " must be \"ASCII-8BIT\", but was " + encoding);
+ break;
+ case STRING:
+ if (encoding != UTF8Encoding.INSTANCE
+ && encoding != USASCIIEncoding.INSTANCE)
+ throw runtime.newTypeError("Encoding for string fields" +
+ " must be \"UTF-8\" or \"ASCII\", but was " + encoding);
+ break;
+ default:
+ break;
+ }
+ }
+
+ public static void checkNameAvailability(ThreadContext context, String name) {
+ if (context.runtime.getObject().getConstantAt(name) != null)
+ throw context.runtime.newNameError(name + " is already defined", name);
+ }
+
+ /**
+ * Replace invalid "." in descriptor with __DOT__
+ * @param name
+ * @return
+ */
+ public static String escapeIdentifier(String name) {
+ return name.replace(".", BADNAME_REPLACEMENT);
+ }
+
+ /**
+ * Replace __DOT__ in descriptor name with "."
+ * @param name
+ * @return
+ */
+ public static String unescapeIdentifier(String name) {
+ return name.replace(BADNAME_REPLACEMENT, ".");
+ }
+
+ public static boolean isMapEntry(Descriptors.FieldDescriptor fieldDescriptor) {
+ return fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE &&
+ fieldDescriptor.isRepeated() &&
+ fieldDescriptor.getMessageType().getOptions().getMapEntry();
+ }
+
+ public static RubyFieldDescriptor msgdefCreateField(ThreadContext context, String label, IRubyObject name,
+ IRubyObject type, IRubyObject number, IRubyObject typeClass, RubyClass cFieldDescriptor) {
+ Ruby runtime = context.runtime;
+ RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
+ fieldDef.setLabel(context, runtime.newString(label));
+ fieldDef.setName(context, name);
+ fieldDef.setType(context, type);
+ fieldDef.setNumber(context, number);
+
+ if (!typeClass.isNil()) {
+ if (!(typeClass instanceof RubyString)) {
+ throw runtime.newArgumentError("expected string for type class");
+ }
+ fieldDef.setSubmsgName(context, typeClass);
+ }
+ return fieldDef;
+ }
+
+ protected static void checkIntTypePrecision(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
+ if (value instanceof RubyFloat) {
+ double doubleVal = RubyNumeric.num2dbl(value);
+ if (Math.floor(doubleVal) != doubleVal) {
+ throw context.runtime.newRangeError("Non-integral floating point value assigned to integer field.");
+ }
+ }
+ if (type == Descriptors.FieldDescriptor.Type.UINT32 || type == Descriptors.FieldDescriptor.Type.UINT64) {
+ if (RubyNumeric.num2dbl(value) < 0) {
+ throw context.runtime.newRangeError("Assigning negative value to unsigned integer field.");
+ }
+ }
+ }
+
+ protected static boolean isRubyNum(Object value) {
+ return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum;
+ }
+
+ protected static void validateTypeClass(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
+ Ruby runtime = context.runtime;
+ if (!(value instanceof RubyModule)) {
+ throw runtime.newArgumentError("TypeClass has incorrect type");
+ }
+ RubyModule klass = (RubyModule) value;
+ IRubyObject descriptor = klass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR);
+ if (descriptor.isNil()) {
+ throw runtime.newArgumentError("Type class has no descriptor. Please pass a " +
+ "class or enum as returned by the DescriptorPool.");
+ }
+ if (type == Descriptors.FieldDescriptor.Type.MESSAGE) {
+ if (! (descriptor instanceof RubyDescriptor)) {
+ throw runtime.newArgumentError("Descriptor has an incorrect type");
+ }
+ } else if (type == Descriptors.FieldDescriptor.Type.ENUM) {
+ if (! (descriptor instanceof RubyEnumDescriptor)) {
+ throw runtime.newArgumentError("Descriptor has an incorrect type");
+ }
+ }
+ }
+
+ public static String BADNAME_REPLACEMENT = "__DOT__";
+
+ public static String DESCRIPTOR_INSTANCE_VAR = "@descriptor";
+
+ public static String EQUAL_SIGN = "=";
+
+ private static BigInteger UINT64_COMPLEMENTARY = new BigInteger("18446744073709551616"); //Math.pow(2, 64)
+
+ private static long UINT_MAX = 0xffffffffl;
+}
diff --git a/ruby/src/main/java/google/ProtobufJavaService.java b/ruby/src/main/java/google/ProtobufJavaService.java
new file mode 100644
index 00000000..bffb492a
--- /dev/null
+++ b/ruby/src/main/java/google/ProtobufJavaService.java
@@ -0,0 +1,60 @@
+/*
+ * 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 google;
+
+import com.google.protobuf.jruby.*;
+import org.jruby.Ruby;
+import org.jruby.runtime.load.BasicLibraryService;
+
+import java.io.IOException;
+
+public class ProtobufJavaService implements BasicLibraryService {
+ @Override
+ public boolean basicLoad(Ruby ruby) throws IOException {
+ ruby.defineModule("Google");
+ RubyProtobuf.createProtobuf(ruby);
+ RubyDescriptor.createRubyDescriptor(ruby);
+ RubyBuilder.createRubyBuilder(ruby);
+ RubyFieldDescriptor.createRubyFileDescriptor(ruby);
+ RubyMessageBuilderContext.createRubyMessageBuilderContext(ruby);
+ RubyEnumDescriptor.createRubyEnumDescriptor(ruby);
+ RubyEnumBuilderContext.createRubyEnumBuilderContext(ruby);
+ RubyDescriptorPool.createRubyDescriptorPool(ruby);
+ RubyRepeatedField.createRubyRepeatedField(ruby);
+ RubyFieldDescriptor.createRubyFileDescriptor(ruby);
+ RubyMap.createRubyMap(ruby);
+ RubyOneofDescriptor.createRubyOneofDescriptor(ruby);
+ RubyOneofBuilderContext.createRubyOneofBuilderContext(ruby);
+ return true;
+ }
+}
diff --git a/ruby/src/main/sentinel.proto b/ruby/src/main/sentinel.proto
new file mode 100644
index 00000000..89a1ae19
--- /dev/null
+++ b/ruby/src/main/sentinel.proto
@@ -0,0 +1,15 @@
+syntax = "proto3";
+package com.google.protobuf.jruby;
+option optimize_for = CODE_SIZE;
+
+message Sentinel {
+ optional int32 default_int32 = 1;
+ optional int64 default_int64 = 2;
+ optional uint32 default_unit32 = 3;
+ optional uint64 default_uint64 = 4;
+ optional string default_string = 5;
+ optional bool default_bool = 6;
+ optional float default_float = 7;
+ optional double default_double = 8;
+ optional bytes default_bytes = 9;
+}