diff options
author | Isaiah Peng <issaria@gmail.com> | 2014-12-24 15:48:41 +0100 |
---|---|---|
committer | Isaiah Peng <issaria@gmail.com> | 2015-03-10 23:14:08 +0100 |
commit | 27e2b57830c328b83286e055752bf92790587953 (patch) | |
tree | 6cf4de2cbcb24d53b2475ed15d6a61a98712685c /ruby/src/main/java/com | |
parent | a5f7bb8ebb60d636c21c18ad2ffeda80e8f80a48 (diff) | |
download | protobuf-27e2b57830c328b83286e055752bf92790587953.tar.gz protobuf-27e2b57830c328b83286e055752bf92790587953.tar.bz2 protobuf-27e2b57830c328b83286e055752bf92790587953.zip |
add jruby support by protobuf-java reflection API
Diffstat (limited to 'ruby/src/main/java/com')
16 files changed, 4392 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; +} |