diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java index bfe7ec325..b80925331 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java @@ -35,6 +35,8 @@ package com.google.protobuf.jruby; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; +import java.util.HashMap; +import java.util.Map; import org.jruby.*; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; @@ -44,182 +46,186 @@ 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) { + 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); - cFieldDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::FieldDescriptor"); - cOneofDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::OneofDescriptor"); + } + }); + cDescriptor.includeModule(runtime.getEnumerable()); + cDescriptor.defineAnnotatedMethods(RubyDescriptor.class); + cFieldDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::FieldDescriptor"); + cOneofDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::OneofDescriptor"); + } + + public RubyDescriptor(Ruby runtime, RubyClass klazz) { + super(runtime, klazz); + } + + /* + * call-seq: + * Descriptor.name => name + * + * Returns the name of this message type as a fully-qualified string (e.g., + * My.Package.MessageType). + */ + @JRubyMethod(name = "name") + public IRubyObject getName(ThreadContext context) { + return name; + } + + /* + * 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 Helpers.nullToNil(fieldDescriptors.get(fieldName), context.nil); + } + + /* + * 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) { + return 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 entry : fieldDescriptors.entrySet()) { + block.yield(context, entry.getValue()); + } + return context.nil; + } + + /* + * call-seq: + * Descriptor.file_descriptor + * + * Returns the FileDescriptor object this message belongs to. + */ + @JRubyMethod(name = "file_descriptor") + public IRubyObject getFileDescriptor(ThreadContext context) { + return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor); + } + + /* + * 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 : oneofDescriptors.values()) { + block.yieldSpecific(context, oneofDescriptor); + } + return context.nil; + } + + /* + * 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) { + return Helpers.nullToNil(oneofDescriptors.get(Utils.symToString(name)), context.nil); + } + + protected FieldDescriptor getField(String name) { + return descriptor.findFieldByName(name); + } + + protected void setDescriptor( + ThreadContext context, Descriptor descriptor, RubyDescriptorPool pool) { + Ruby runtime = context.runtime; + Map cache = new HashMap(); + this.descriptor = descriptor; + + // Populate the field caches + fieldDescriptors = new HashMap(); + oneofDescriptors = new HashMap(); + + for (FieldDescriptor fieldDescriptor : descriptor.getFields()) { + RubyFieldDescriptor fd = + (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK); + fd.setDescriptor(context, fieldDescriptor, pool); + fieldDescriptors.put(runtime.newString(fieldDescriptor.getName()), fd); + cache.put(fieldDescriptor, fd); } - public RubyDescriptor(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); + for (OneofDescriptor oneofDescriptor : descriptor.getRealOneofs()) { + RubyOneofDescriptor ood = + (RubyOneofDescriptor) cOneofDescriptor.newInstance(context, Block.NULL_BLOCK); + ood.setDescriptor(context, oneofDescriptor, cache); + oneofDescriptors.put(runtime.newString(oneofDescriptor.getName()), ood); } - /* - * call-seq: - * Descriptor.name => name - * - * Returns the name of this message type as a fully-qualified string (e.g., - * My.Package.MessageType). - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - return name; - } + // Make sure our class is built + this.klazz = buildClassFromDescriptor(context); + } - /* - * 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 Helpers.nullToNil(fieldDescriptors.get(fieldName), context.nil); - } + protected void setName(IRubyObject name) { + this.name = name; + } - /* - * 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) { - return klazz; - } + private RubyClass buildClassFromDescriptor(ThreadContext context) { + Ruby runtime = context.runtime; - /* - * 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 entry : fieldDescriptors.entrySet()) { - block.yield(context, entry.getValue()); - } - return context.nil; - } - - /* - * call-seq: - * Descriptor.file_descriptor - * - * Returns the FileDescriptor object this message belongs to. - */ - @JRubyMethod(name = "file_descriptor") - public IRubyObject getFileDescriptor(ThreadContext context) { - return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor); - } - - /* - * 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 : oneofDescriptors.values()) { - block.yieldSpecific(context, oneofDescriptor); - } - return context.nil; - } - - /* - * 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) { - return Helpers.nullToNil(oneofDescriptors.get(Utils.symToString(name)), context.nil); - } - - protected FieldDescriptor getField(String name) { - return descriptor.findFieldByName(name); - } - - protected void setDescriptor(ThreadContext context, Descriptor descriptor, RubyDescriptorPool pool) { - Ruby runtime = context.runtime; - Map cache = new HashMap(); - this.descriptor = descriptor; - - // Populate the field caches - fieldDescriptors = new HashMap(); - oneofDescriptors = new HashMap(); - - for (FieldDescriptor fieldDescriptor : descriptor.getFields()) { - RubyFieldDescriptor fd = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK); - fd.setDescriptor(context, fieldDescriptor, pool); - fieldDescriptors.put(runtime.newString(fieldDescriptor.getName()), fd); - cache.put(fieldDescriptor, fd); - } - - for (OneofDescriptor oneofDescriptor : descriptor.getRealOneofs()) { - RubyOneofDescriptor ood = (RubyOneofDescriptor) cOneofDescriptor.newInstance(context, Block.NULL_BLOCK); - ood.setDescriptor(context, oneofDescriptor, cache); - oneofDescriptors.put(runtime.newString(oneofDescriptor.getName()), ood); - } - - // Make sure our class is built - this.klazz = buildClassFromDescriptor(context); - } - - protected void setName(IRubyObject name) { - this.name = name; - } - - private RubyClass 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); - } + 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()); - RubyModule messageExts = runtime.getClassFromPath("Google::Protobuf::MessageExts"); - klass.include(new IRubyObject[] {messageExts}); - klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this); - klass.defineAnnotatedMethods(RubyMessage.class); - // Workaround for https://github.com/jruby/jruby/issues/7154 - klass.searchMethod("respond_to?").setIsBuiltin(false); - return klass; - } + // rb_define_class_id + RubyClass klass = RubyClass.newClass(runtime, runtime.getObject()); + klass.setAllocator(allocator); + klass.makeMetaClass(runtime.getObject().getMetaClass()); + klass.inherit(runtime.getObject()); + RubyModule messageExts = runtime.getClassFromPath("Google::Protobuf::MessageExts"); + klass.include(new IRubyObject[] {messageExts}); + klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this); + klass.defineAnnotatedMethods(RubyMessage.class); + // Workaround for https://github.com/jruby/jruby/issues/7154 + klass.searchMethod("respond_to?").setIsBuiltin(false); + return klass; + } - private static RubyClass cFieldDescriptor; - private static RubyClass cOneofDescriptor; + private static RubyClass cFieldDescriptor; + private static RubyClass cOneofDescriptor; - private Descriptor descriptor; - private IRubyObject name; - private Map fieldDescriptors; - private Map oneofDescriptors; - private RubyClass klazz; + private Descriptor descriptor; + private IRubyObject name; + private Map fieldDescriptors; + private Map oneofDescriptors; + private RubyClass klazz; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java index 6cdb34142..d65b412a0 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java @@ -38,6 +38,10 @@ import com.google.protobuf.Descriptors.DescriptorValidationException; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.InvalidProtocolBufferException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.jruby.*; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; @@ -45,136 +49,147 @@ import org.jruby.exceptions.RaiseException; import org.jruby.runtime.*; import org.jruby.runtime.builtin.IRubyObject; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -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) { + 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); - cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor"); - cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor"); + cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class); + descriptorPool = + (RubyDescriptorPool) + cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK); + cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor"); + cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor"); + } + + public RubyDescriptorPool(Ruby runtime, RubyClass klazz) { + super(runtime, klazz); + this.fileDescriptors = new ArrayList<>(); + this.symtab = new HashMap(); + } + + @JRubyMethod + public IRubyObject build(ThreadContext context, Block block) { + RubyClass cBuilder = + (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::Builder"); + RubyBasicObject ctx = (RubyBasicObject) cBuilder.newInstance(context, this, Block.NULL_BLOCK); + ctx.instance_eval(context, block); + ctx.callMethod(context, "build"); // Needs to be called to support the deprecated syntax + return context.nil; + } + + /* + * call-seq: + * DescriptorPool.lookup(name) => descriptor + * + * Finds a Descriptor or EnumDescriptor by name and returns it, or nil if none + * exists with the given name. + * + * This currently lazy loads the ruby descriptor objects as they are requested. + * This allows us to leave the heavy lifting to the java library + */ + @JRubyMethod + public IRubyObject lookup(ThreadContext context, IRubyObject name) { + return Helpers.nullToNil(symtab.get(name), context.nil); + } + + /* + * 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; + } + + @JRubyMethod(required = 1) + public IRubyObject add_serialized_file(ThreadContext context, IRubyObject data) { + byte[] bin = data.convertToString().getBytes(); + try { + FileDescriptorProto.Builder builder = FileDescriptorProto.newBuilder().mergeFrom(bin); + registerFileDescriptor(context, builder); + } catch (InvalidProtocolBufferException e) { + throw RaiseException.from( + context.runtime, + (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), + e.getMessage()); + } + return context.nil; + } + + protected void registerFileDescriptor( + ThreadContext context, FileDescriptorProto.Builder builder) { + final FileDescriptor fd; + try { + fd = FileDescriptor.buildFrom(builder.build(), existingFileDescriptors()); + } catch (DescriptorValidationException e) { + throw context.runtime.newRuntimeError(e.getMessage()); } - public RubyDescriptorPool(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - this.fileDescriptors = new ArrayList<>(); - this.symtab = new HashMap(); + String packageName = fd.getPackage(); + if (!packageName.isEmpty()) { + packageName = packageName + "."; } - @JRubyMethod - public IRubyObject build(ThreadContext context, Block block) { - RubyClass cBuilder = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::Builder"); - RubyBasicObject ctx = (RubyBasicObject) cBuilder.newInstance(context, this, Block.NULL_BLOCK); - ctx.instance_eval(context, block); - ctx.callMethod(context, "build"); // Needs to be called to support the deprecated syntax - return context.nil; - } + // Need to make sure enums are registered first in case anything references them + for (EnumDescriptor ed : fd.getEnumTypes()) registerEnumDescriptor(context, ed, packageName); + for (Descriptor message : fd.getMessageTypes()) + registerDescriptor(context, message, packageName); - /* - * call-seq: - * DescriptorPool.lookup(name) => descriptor - * - * Finds a Descriptor or EnumDescriptor by name and returns it, or nil if none - * exists with the given name. - * - * This currently lazy loads the ruby descriptor objects as they are requested. - * This allows us to leave the heavy lifting to the java library - */ - @JRubyMethod - public IRubyObject lookup(ThreadContext context, IRubyObject name) { - return Helpers.nullToNil(symtab.get(name), context.nil); - } + // Mark this as a loaded file + fileDescriptors.add(fd); + } - /* - * 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; - } + private void registerDescriptor(ThreadContext context, Descriptor descriptor, String parentPath) { + String fullName = parentPath + descriptor.getName(); + String fullPath = fullName + "."; + RubyString name = context.runtime.newString(fullName); - @JRubyMethod(required = 1) - public IRubyObject add_serialized_file (ThreadContext context, IRubyObject data ) { - byte[] bin = data.convertToString().getBytes(); - try { - FileDescriptorProto.Builder builder = FileDescriptorProto.newBuilder().mergeFrom(bin); - registerFileDescriptor(context, builder); - } catch (InvalidProtocolBufferException e) { - throw RaiseException.from(context.runtime, (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), e.getMessage()); - } - return context.nil; - } + RubyDescriptor des = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK); + des.setName(name); + des.setDescriptor(context, descriptor, this); + symtab.put(name, des); - protected void registerFileDescriptor(ThreadContext context, FileDescriptorProto.Builder builder) { - final FileDescriptor fd; - try { - fd = FileDescriptor.buildFrom(builder.build(), existingFileDescriptors()); - } catch (DescriptorValidationException e) { - throw context.runtime.newRuntimeError(e.getMessage()); - } + // Need to make sure enums are registered first in case anything references them + for (EnumDescriptor ed : descriptor.getEnumTypes()) + registerEnumDescriptor(context, ed, fullPath); + for (Descriptor message : descriptor.getNestedTypes()) + registerDescriptor(context, message, fullPath); + } - String packageName = fd.getPackage(); - if (!packageName.isEmpty()) { - packageName = packageName + "."; - } + private void registerEnumDescriptor( + ThreadContext context, EnumDescriptor descriptor, String parentPath) { + RubyString name = context.runtime.newString(parentPath + descriptor.getName()); + RubyEnumDescriptor des = + (RubyEnumDescriptor) cEnumDescriptor.newInstance(context, Block.NULL_BLOCK); + des.setName(name); + des.setDescriptor(context, descriptor); + symtab.put(name, des); + } - // Need to make sure enums are registered first in case anything references them - for (EnumDescriptor ed : fd.getEnumTypes()) registerEnumDescriptor(context, ed, packageName); - for (Descriptor message : fd.getMessageTypes()) registerDescriptor(context, message, packageName); + private FileDescriptor[] existingFileDescriptors() { + return fileDescriptors.toArray(new FileDescriptor[fileDescriptors.size()]); + } - // Mark this as a loaded file - fileDescriptors.add(fd); - } + private static RubyClass cDescriptor; + private static RubyClass cEnumDescriptor; + private static RubyDescriptorPool descriptorPool; - private void registerDescriptor(ThreadContext context, Descriptor descriptor, String parentPath) { - String fullName = parentPath + descriptor.getName(); - String fullPath = fullName + "."; - RubyString name = context.runtime.newString(fullName); - - RubyDescriptor des = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK); - des.setName(name); - des.setDescriptor(context, descriptor, this); - symtab.put(name, des); - - // Need to make sure enums are registered first in case anything references them - for (EnumDescriptor ed : descriptor.getEnumTypes()) registerEnumDescriptor(context, ed, fullPath); - for (Descriptor message : descriptor.getNestedTypes()) registerDescriptor(context, message, fullPath); - } - - private void registerEnumDescriptor(ThreadContext context, EnumDescriptor descriptor, String parentPath) { - RubyString name = context.runtime.newString(parentPath + descriptor.getName()); - RubyEnumDescriptor des = (RubyEnumDescriptor) cEnumDescriptor.newInstance(context, Block.NULL_BLOCK); - des.setName(name); - des.setDescriptor(context, descriptor); - symtab.put(name, des); - } - - private FileDescriptor[] existingFileDescriptors() { - return fileDescriptors.toArray(new FileDescriptor[fileDescriptors.size()]); - } - - private static RubyClass cDescriptor; - private static RubyClass cEnumDescriptor; - private static RubyDescriptorPool descriptorPool; - - private List fileDescriptors; - private Map symtab; + private List fileDescriptors; + private Map symtab; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java index 17525dfe4..95d961e11 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java @@ -38,41 +38,41 @@ 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 rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv); - return rubyEnumDescriptor.numberToName(context, number); - } + /* + * 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 rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv); + return rubyEnumDescriptor.numberToName(context, number); + } - /* - * 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 rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv); - return rubyEnumDescriptor.nameToNumber(context, name); - } + /* + * 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 rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv); + return rubyEnumDescriptor.nameToNumber(context, name); + } - /* - * 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); - } + /* + * 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/RubyEnumDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java index 26f00db66..65328676e 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java @@ -39,8 +39,8 @@ import com.google.protobuf.Descriptors.FileDescriptor; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; -import org.jruby.RubyObject; import org.jruby.RubyNumeric; +import org.jruby.RubyObject; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.Block; @@ -50,132 +50,147 @@ 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) { + 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); + } + }); + cEnumDescriptor.includeModule(runtime.getEnumerable()); + cEnumDescriptor.defineAnnotatedMethods(RubyEnumDescriptor.class); + } + + public RubyEnumDescriptor(Ruby runtime, RubyClass klazz) { + super(runtime, klazz); + } + + /* + * 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.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 (EnumValueDescriptor enumValueDescriptor : descriptor.getValues()) { + block.yield( + context, + runtime.newArray( + runtime.newSymbol(enumValueDescriptor.getName()), + runtime.newFixnum(enumValueDescriptor.getNumber()))); + } + return context.nil; + } + + /* + * 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) { + return module; + } + + /* + * call-seq: + * EnumDescriptor.file_descriptor + * + * Returns the FileDescriptor object this enum belongs to. + */ + @JRubyMethod(name = "file_descriptor") + public IRubyObject getFileDescriptor(ThreadContext context) { + return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor); + } + + public boolean isValidValue(ThreadContext context, IRubyObject value) { + EnumValueDescriptor enumValue; + + if (Utils.isRubyNum(value)) { + enumValue = descriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); + } else { + enumValue = descriptor.findValueByName(value.asJavaString()); } - public RubyEnumDescriptor(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); + return enumValue != null; + } + + protected IRubyObject nameToNumber(ThreadContext context, IRubyObject name) { + EnumValueDescriptor value = descriptor.findValueByName(name.asJavaString()); + return value == null ? context.nil : context.runtime.newFixnum(value.getNumber()); + } + + protected IRubyObject numberToName(ThreadContext context, IRubyObject number) { + EnumValueDescriptor value = descriptor.findValueByNumber(RubyNumeric.num2int(number)); + return value == null ? context.nil : context.runtime.newSymbol(value.getName()); + } + + protected void setDescriptor(ThreadContext context, EnumDescriptor descriptor) { + this.descriptor = descriptor; + this.module = buildModuleFromDescriptor(context); + } + + protected void setName(IRubyObject name) { + this.name = name; + } + + private RubyModule buildModuleFromDescriptor(ThreadContext context) { + Ruby runtime = context.runtime; + + RubyModule enumModule = RubyModule.newModule(runtime); + boolean defaultValueRequiredButNotFound = + descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3; + for (EnumValueDescriptor value : descriptor.getValues()) { + String name = value.getName(); + // Make sure its a valid constant name before trying to create it + if (Character.isUpperCase(name.codePointAt(0))) { + enumModule.defineConstant(name, runtime.newFixnum(value.getNumber())); + } else { + runtime + .getWarnings() + .warn( + "Enum value " + + name + + " does not start with an uppercase letter as is required for Ruby" + + " constants."); + } + if (value.getNumber() == 0) { + defaultValueRequiredButNotFound = false; + } } - /* - * call-seq: - * EnumDescriptor.name => name - * - * Returns the name of this enum type. - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - return this.name; + if (defaultValueRequiredButNotFound) { + throw Utils.createTypeError( + context, "Enum definition " + name + " does not contain a value for '0'"); } + enumModule.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this); + enumModule.defineAnnotatedMethods(RubyEnum.class); + return enumModule; + } - /* - * 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 (EnumValueDescriptor enumValueDescriptor : descriptor.getValues()) { - block.yield(context, runtime.newArray(runtime.newSymbol(enumValueDescriptor.getName()), - runtime.newFixnum(enumValueDescriptor.getNumber()))); - } - return context.nil; - } - - /* - * 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) { - return module; - } - - /* - * call-seq: - * EnumDescriptor.file_descriptor - * - * Returns the FileDescriptor object this enum belongs to. - */ - @JRubyMethod(name = "file_descriptor") - public IRubyObject getFileDescriptor(ThreadContext context) { - return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor); - } - - public boolean isValidValue(ThreadContext context, IRubyObject value) { - EnumValueDescriptor enumValue; - - if (Utils.isRubyNum(value)) { - enumValue = descriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); - } else { - enumValue = descriptor.findValueByName(value.asJavaString()); - } - - return enumValue != null; - } - - protected IRubyObject nameToNumber(ThreadContext context, IRubyObject name) { - EnumValueDescriptor value = descriptor.findValueByName(name.asJavaString()); - return value == null ? context.nil : context.runtime.newFixnum(value.getNumber()); - } - - protected IRubyObject numberToName(ThreadContext context, IRubyObject number) { - EnumValueDescriptor value = descriptor.findValueByNumber(RubyNumeric.num2int(number)); - return value == null ? context.nil : context.runtime.newSymbol(value.getName()); - } - - protected void setDescriptor(ThreadContext context, EnumDescriptor descriptor) { - this.descriptor = descriptor; - this.module = buildModuleFromDescriptor(context); - } - - protected void setName(IRubyObject name) { - this.name = name; - } - - private RubyModule buildModuleFromDescriptor(ThreadContext context) { - Ruby runtime = context.runtime; - - RubyModule enumModule = RubyModule.newModule(runtime); - boolean defaultValueRequiredButNotFound = descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3; - for (EnumValueDescriptor value : descriptor.getValues()) { - String name = value.getName(); - // Make sure its a valid constant name before trying to create it - if (Character.isUpperCase(name.codePointAt(0))) { - enumModule.defineConstant(name, runtime.newFixnum(value.getNumber())); - } else { - runtime.getWarnings().warn("Enum value " + name + " does not start with an uppercase letter as is required for Ruby constants."); - } - if (value.getNumber() == 0) { - defaultValueRequiredButNotFound = false; - } - } - - if (defaultValueRequiredButNotFound) { - throw Utils.createTypeError(context, "Enum definition " + name + " does not contain a value for '0'"); - } - enumModule.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this); - enumModule.defineAnnotatedMethods(RubyEnum.class); - return enumModule; - } - - private EnumDescriptor descriptor; - private EnumDescriptorProto.Builder builder; - private IRubyObject name; - private RubyModule module; + private EnumDescriptor descriptor; + private EnumDescriptorProto.Builder builder; + private IRubyObject name; + private RubyModule module; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java index e9594d831..bc1fe0cbe 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java @@ -43,228 +43,237 @@ import org.jruby.runtime.builtin.IRubyObject; @JRubyClass(name = "FieldDescriptor") public class RubyFieldDescriptor extends RubyObject { - public static void createRubyFieldDescriptor(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) { + public static void createRubyFieldDescriptor(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); + } + }); + cFieldDescriptor.defineAnnotatedMethods(RubyFieldDescriptor.class); + } + + public RubyFieldDescriptor(Ruby runtime, RubyClass klazz) { + super(runtime, klazz); + } + + /* + * call-seq: + * FieldDescriptor.default => default + * + * Returns this field's default, as a Ruby object, or nil if not yet set. + */ + // VALUE FieldDescriptor_default(VALUE _self) { + // DEFINE_SELF(FieldDescriptor, self, _self); + // return layout_get_default(self->fielddef); + // } + + /* + * call-seq: + * FieldDescriptor.label => label + * + * Returns this field's label (i.e., plurality), as a Ruby symbol. + * + * Valid field labels are: + * :optional, :repeated + */ + @JRubyMethod(name = "label") + public IRubyObject getLabel(ThreadContext context) { + if (label == null) { + calculateLabel(context); } + return label; + } - public RubyFieldDescriptor(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); + /* + * call-seq: + * FieldDescriptor.name => name + * + * Returns the name of this field as a Ruby String, or nil if it is not set. + */ + @JRubyMethod(name = "name") + public IRubyObject getName(ThreadContext context) { + return this.name; + } + + /* + * call-seq: + * FieldDescriptor.subtype => message_or_enum_descriptor + * + * Returns the message or enum descriptor corresponding to this field's type if + * it is a message or enum field, respectively, or nil otherwise. Cannot be + * called *until* the containing message type is added to a pool (and thus + * resolved). + */ + @JRubyMethod(name = "subtype") + public IRubyObject getSubtype(ThreadContext context) { + if (subtype == null) { + calculateSubtype(context); } + return subtype; + } - /* - * call-seq: - * FieldDescriptor.default => default - * - * Returns this field's default, as a Ruby object, or nil if not yet set. - */ - // VALUE FieldDescriptor_default(VALUE _self) { - // DEFINE_SELF(FieldDescriptor, self, _self); - // return layout_get_default(self->fielddef); - // } + /* + * 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, descriptor.getType()); + } - /* - * call-seq: - * FieldDescriptor.label => label - * - * Returns this field's label (i.e., plurality), as a Ruby symbol. - * - * Valid field labels are: - * :optional, :repeated - */ - @JRubyMethod(name = "label") - public IRubyObject getLabel(ThreadContext context) { - if (label == null) { - calculateLabel(context); - } - return label; + /* + * call-seq: + * FieldDescriptor.number => number + * + * Returns the tag number for this field. + */ + @JRubyMethod(name = "number") + public IRubyObject getNumber(ThreadContext context) { + return this.number; + } + + /* + * call-seq: + * FieldDescriptor.submsg_name => submsg_name + * + * Returns the name of the message or enum type corresponding to this field, if + * it is a message or enum field (respectively), or nil otherwise. This type + * name will be resolved within the context of the pool to which the containing + * message type is added. + */ + // VALUE FieldDescriptor_submsg_name(VALUE _self) { + // DEFINE_SELF(FieldDescriptor, self, _self); + // switch (upb_fielddef_type(self->fielddef)) { + // case UPB_TYPE_ENUM: + // return rb_str_new2( + // upb_enumdef_fullname(upb_fielddef_enumsubdef(self->fielddef))); + // case UPB_TYPE_MESSAGE: + // return rb_str_new2( + // upb_msgdef_fullname(upb_fielddef_msgsubdef(self->fielddef))); + // default: + // return Qnil; + // } + // } + /* + * 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.clear(message) + * + * Clears the field from the message if it's set. + */ + @JRubyMethod(name = "clear") + public IRubyObject clearValue(ThreadContext context, IRubyObject message) { + return ((RubyMessage) message).clearField(context, descriptor); + } + + /* + * 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 message) { + return ((RubyMessage) message).getField(context, descriptor); + } + + /* + * call-seq: + * FieldDescriptor.has?(message) => boolean + * + * Returns whether the value is set on the given message. Raises an + * exception when calling for fields that do not have presence. + */ + @JRubyMethod(name = "has?") + public IRubyObject has(ThreadContext context, IRubyObject message) { + return ((RubyMessage) message).hasField(context, descriptor); + } + + /* + * 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 message, IRubyObject value) { + ((RubyMessage) message).setField(context, descriptor, value); + return context.nil; + } + + protected void setDescriptor( + ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool) { + if (descriptor.isRequired() + && descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) { + throw Utils.createTypeError( + context, + descriptor.getName() + + " is labeled required but required fields are unsupported in proto3"); } + this.descriptor = descriptor; + this.name = context.runtime.newString(descriptor.getName()); + this.pool = pool; + } - /* - * call-seq: - * FieldDescriptor.name => name - * - * Returns the name of this field as a Ruby String, or nil if it is not set. - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - return this.name; + private void calculateLabel(ThreadContext context) { + if (descriptor.isRepeated()) { + this.label = context.runtime.newSymbol("repeated"); + } else if (descriptor.isOptional()) { + this.label = context.runtime.newSymbol("optional"); + } else { + this.label = context.nil; } + } - /* - * call-seq: - * FieldDescriptor.subtype => message_or_enum_descriptor - * - * Returns the message or enum descriptor corresponding to this field's type if - * it is a message or enum field, respectively, or nil otherwise. Cannot be - * called *until* the containing message type is added to a pool (and thus - * resolved). - */ - @JRubyMethod(name = "subtype") - public IRubyObject getSubtype(ThreadContext context) { - if (subtype == null) { - calculateSubtype(context); - } - return subtype; + private void calculateSubtype(ThreadContext context) { + FieldDescriptor.Type fdType = descriptor.getType(); + if (fdType == FieldDescriptor.Type.MESSAGE) { + RubyString messageName = context.runtime.newString(descriptor.getMessageType().getFullName()); + this.subtype = pool.lookup(context, messageName); + } else if (fdType == FieldDescriptor.Type.ENUM) { + RubyString enumName = context.runtime.newString(descriptor.getEnumType().getFullName()); + this.subtype = pool.lookup(context, enumName); + } else { + this.subtype = context.nil; } + } - /* - * 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, descriptor.getType()); - } + private static final String DOT = "."; - /* - * call-seq: - * FieldDescriptor.number => number - * - * Returns the tag number for this field. - */ - @JRubyMethod(name = "number") - public IRubyObject getNumber(ThreadContext context) { - return this.number; - } - - /* - * call-seq: - * FieldDescriptor.submsg_name => submsg_name - * - * Returns the name of the message or enum type corresponding to this field, if - * it is a message or enum field (respectively), or nil otherwise. This type - * name will be resolved within the context of the pool to which the containing - * message type is added. - */ - // VALUE FieldDescriptor_submsg_name(VALUE _self) { - // DEFINE_SELF(FieldDescriptor, self, _self); - // switch (upb_fielddef_type(self->fielddef)) { - // case UPB_TYPE_ENUM: - // return rb_str_new2( - // upb_enumdef_fullname(upb_fielddef_enumsubdef(self->fielddef))); - // case UPB_TYPE_MESSAGE: - // return rb_str_new2( - // upb_msgdef_fullname(upb_fielddef_msgsubdef(self->fielddef))); - // default: - // return Qnil; - // } - // } - /* - * 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.clear(message) - * - * Clears the field from the message if it's set. - */ - @JRubyMethod(name = "clear") - public IRubyObject clearValue(ThreadContext context, IRubyObject message) { - return ((RubyMessage) message).clearField(context, descriptor); - } - - /* - * 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 message) { - return ((RubyMessage) message).getField(context, descriptor); - } - - /* - * call-seq: - * FieldDescriptor.has?(message) => boolean - * - * Returns whether the value is set on the given message. Raises an - * exception when calling for fields that do not have presence. - */ - @JRubyMethod(name = "has?") - public IRubyObject has(ThreadContext context, IRubyObject message) { - return ((RubyMessage) message).hasField(context, descriptor); - } - - /* - * 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 message, IRubyObject value) { - ((RubyMessage) message).setField(context, descriptor, value); - return context.nil; - } - - protected void setDescriptor(ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool) { - if (descriptor.isRequired() && descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) { - throw Utils.createTypeError(context, descriptor.getName() + " is labeled required but required fields are unsupported in proto3"); - } - this.descriptor = descriptor; - this.name = context.runtime.newString(descriptor.getName()); - this.pool = pool; - } - - private void calculateLabel(ThreadContext context) { - if (descriptor.isRepeated()) { - this.label = context.runtime.newSymbol("repeated"); - } else if (descriptor.isOptional()) { - this.label = context.runtime.newSymbol("optional"); - } else { - this.label = context.nil; - } - } - - private void calculateSubtype(ThreadContext context) { - FieldDescriptor.Type fdType = descriptor.getType(); - if (fdType == FieldDescriptor.Type.MESSAGE) { - RubyString messageName = context.runtime.newString(descriptor.getMessageType().getFullName()); - this.subtype = pool.lookup(context, messageName); - } else if (fdType == FieldDescriptor.Type.ENUM) { - RubyString enumName = context.runtime.newString(descriptor.getEnumType().getFullName()); - this.subtype = pool.lookup(context, enumName); - } else { - this.subtype = context.nil; - } - } - - private static final String DOT = "."; - - private FieldDescriptor descriptor; - private IRubyObject name; - private IRubyObject label; - private IRubyObject number; - private IRubyObject subtype; - private RubyDescriptorPool pool; + private FieldDescriptor descriptor; + private IRubyObject name; + private IRubyObject label; + private IRubyObject number; + private IRubyObject subtype; + private RubyDescriptorPool pool; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java index b3e1816c7..972510b02 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java @@ -32,7 +32,6 @@ package com.google.protobuf.jruby; -import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.FileDescriptor.Syntax.*; import com.google.protobuf.Descriptors.GenericDescriptor; @@ -46,61 +45,67 @@ import org.jruby.runtime.builtin.IRubyObject; @JRubyClass(name = "FileDescriptor") public class RubyFileDescriptor extends RubyObject { - public static void createRubyFileDescriptor(Ruby runtime) { - RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf"); - cFileDescriptor = mProtobuf.defineClassUnder("FileDescriptor", runtime.getObject(), new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + public static void createRubyFileDescriptor(Ruby runtime) { + RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf"); + cFileDescriptor = + mProtobuf.defineClassUnder( + "FileDescriptor", + runtime.getObject(), + new ObjectAllocator() { + @Override + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { return new RubyFileDescriptor(runtime, klazz); - } - }); - cFileDescriptor.defineAnnotatedMethods(RubyFileDescriptor.class); + } + }); + cFileDescriptor.defineAnnotatedMethods(RubyFileDescriptor.class); + } + + public static RubyFileDescriptor getRubyFileDescriptor( + ThreadContext context, GenericDescriptor descriptor) { + RubyFileDescriptor rfd = + (RubyFileDescriptor) cFileDescriptor.newInstance(context, Block.NULL_BLOCK); + rfd.fileDescriptor = descriptor.getFile(); + return rfd; + } + + public RubyFileDescriptor(Ruby runtime, RubyClass klazz) { + super(runtime, klazz); + } + + /* + * call-seq: + * FileDescriptor.name => name + * + * Returns the name of the file. + */ + @JRubyMethod(name = "name") + public IRubyObject getName(ThreadContext context) { + String name = fileDescriptor.getName(); + return name == null ? context.nil : context.runtime.newString(name); + } + + /* + * call-seq: + * FileDescriptor.syntax => syntax + * + * Returns this file descriptors syntax. + * + * Valid syntax versions are: + * :proto2 or :proto3. + */ + @JRubyMethod(name = "syntax") + public IRubyObject getSyntax(ThreadContext context) { + switch (fileDescriptor.getSyntax()) { + case PROTO2: + return context.runtime.newSymbol("proto2"); + case PROTO3: + return context.runtime.newSymbol("proto3"); + default: + return context.nil; } + } - public static RubyFileDescriptor getRubyFileDescriptor(ThreadContext context, GenericDescriptor descriptor) { - RubyFileDescriptor rfd = (RubyFileDescriptor) cFileDescriptor.newInstance(context, Block.NULL_BLOCK); - rfd.fileDescriptor = descriptor.getFile(); - return rfd; - } + private static RubyClass cFileDescriptor; - public RubyFileDescriptor(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - } - - /* - * call-seq: - * FileDescriptor.name => name - * - * Returns the name of the file. - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - String name = fileDescriptor.getName(); - return name == null ? context.nil : context.runtime.newString(name); - } - - /* - * call-seq: - * FileDescriptor.syntax => syntax - * - * Returns this file descriptors syntax. - * - * Valid syntax versions are: - * :proto2 or :proto3. - */ - @JRubyMethod(name = "syntax") - public IRubyObject getSyntax(ThreadContext context) { - switch (fileDescriptor.getSyntax()) { - case PROTO2: - return context.runtime.newSymbol("proto2"); - case PROTO3: - return context.runtime.newSymbol("proto3"); - default: - return context.nil; - } - } - - private static RubyClass cFileDescriptor; - - private FileDescriptor fileDescriptor; + private FileDescriptor fileDescriptor; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java index 7956eebe7..8727b13cf 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java @@ -34,6 +34,13 @@ package com.google.protobuf.jruby; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.DynamicMessage; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.jruby.*; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; @@ -43,432 +50,438 @@ import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; -import java.nio.ByteBuffer; -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) { + 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, :fixed32, + * :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :bool, :string, :bytes. + * + * The supported value types are: :int32, :int64, :uint32, :uint64, :fixed32, + * :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :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(); + this.keyType = Utils.rubyToFieldType(args[0]); + this.valueType = Utils.rubyToFieldType(args[1]); + + switch (keyType) { + case STRING: + case BYTES: + this.keyTypeIsString = true; + break; + case INT32: + case INT64: + case SINT32: + case SINT64: + case UINT32: + case UINT64: + case FIXED32: + case FIXED64: + case SFIXED32: + case SFIXED64: + case BOOL: + // 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(); + } + + 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) { + checkFrozen(); + + /* + * String types for keys return a different error than + * other types for keys, so deal with them specifically first + */ + if (keyTypeIsString && !(key instanceof RubySymbol || key instanceof RubyString)) { + throw Utils.createTypeError(context, "Expected string for map key"); + } + key = Utils.checkType(context, keyType, "key", key, (RubyModule) valueTypeClass); + value = Utils.checkType(context, valueType, "value", value, (RubyModule) valueTypeClass); + IRubyObject symbol; + if (valueType == 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) { + key = Utils.symToString(key); + return Helpers.nullToNil(table.get(key), context.nil); + } + + /* + * 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 singleLevelHash(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 singleLevelHash(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.newFixnum(ByteBuffer.wrap(digest.digest()).getLong()); + } 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) { + checkFrozen(); + table.clear(); + return context.nil; + } + + /* + * 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.nil; + } + + /* + * 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) { + checkFrozen(); + 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(name = {"length", "size"}) + 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 entry : table.entrySet()) { + newMap.table.put(entry.getKey(), entry.getValue()); + } + return newMap; + } + + @JRubyMethod(name = "to_h") + public RubyHash toHash(ThreadContext context) { + Map mapForHash = new HashMap(); + + table.forEach( + (key, value) -> { + if (!value.isNil()) { + if (value.respondsTo("to_h")) { + value = Helpers.invoke(context, value, "to_h"); + } else if (value.respondsTo("to_a")) { + value = Helpers.invoke(context, value, "to_a"); } - }); - 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, :fixed32, - * :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :bool, :string, :bytes. - * - * The supported value types are: :int32, :int64, :uint32, :uint64, :fixed32, - * :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :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(); - this.keyType = Utils.rubyToFieldType(args[0]); - this.valueType = Utils.rubyToFieldType(args[1]); - - switch(keyType) { - case STRING: - case BYTES: - this.keyTypeIsString = true; - break; - case INT32: - case INT64: - case SINT32: - case SINT64: - case UINT32: - case UINT64: - case FIXED32: - case FIXED64: - case SFIXED32: - case SFIXED64: - case BOOL: - // 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(); - } - - 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) { - checkFrozen(); - - /* - * String types for keys return a different error than - * other types for keys, so deal with them specifically first - */ - if (keyTypeIsString && !(key instanceof RubySymbol || key instanceof RubyString)) { - throw Utils.createTypeError(context, "Expected string for map key"); - } - key = Utils.checkType(context, keyType, "key", key, (RubyModule) valueTypeClass); - value = Utils.checkType(context, valueType, "value", value, (RubyModule) valueTypeClass); - IRubyObject symbol; - if (valueType == 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) { - key = Utils.symToString(key); - return Helpers.nullToNil(table.get(key), context.nil); - } - - /* - * 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 singleLevelHash(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 singleLevelHash(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.newFixnum(ByteBuffer.wrap(digest.digest()).getLong()); - } 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) { - checkFrozen(); - table.clear(); - return context.nil; - } - - /* - * 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.nil; - } - - /* - * 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) { - checkFrozen(); - 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(name = {"length", "size"}) - 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 entry : table.entrySet()) { - newMap.table.put(entry.getKey(), entry.getValue()); - } - return newMap; - } - - @JRubyMethod(name = "to_h") - public RubyHash toHash(ThreadContext context) { - Map mapForHash = new HashMap(); - - table.forEach((key, value) -> { - if (!value.isNil()) { - if (value.respondsTo("to_h")) { - value = Helpers.invoke(context, value, "to_h"); - } else if (value.respondsTo("to_a")) { - value = Helpers.invoke(context, value, "to_a"); - } - mapForHash.put(key, value); - } + mapForHash.put(key, value); + } }); - return RubyHash.newHash(context.runtime, mapForHash, context.nil); - } + return RubyHash.newHash(context.runtime, mapForHash, context.nil); + } - // 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 build(ThreadContext context, RubyDescriptor descriptor, int depth, int recursionLimit) { - List list = new ArrayList(); - RubyClass rubyClass = (RubyClass) descriptor.msgclass(context); - FieldDescriptor keyField = descriptor.getField("key"); - FieldDescriptor valueField = descriptor.getField("value"); + // 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 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, depth + 1, recursionLimit)); + 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 list; } + return newMap; + } - protected RubyMap mergeIntoSelf(final ThreadContext context, IRubyObject hashmap) { - if (hashmap instanceof RubyHash) { - ((RubyHash) hashmap).visitAll(context, new RubyHash.Visitor() { + protected List build( + ThreadContext context, RubyDescriptor descriptor, int depth, int recursionLimit) { + List list = new ArrayList(); + RubyClass rubyClass = (RubyClass) descriptor.msgclass(context); + FieldDescriptor keyField = descriptor.getField("key"); + FieldDescriptor valueField = descriptor.getField("value"); + 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, depth + 1, recursionLimit)); + } + return list; + } + + protected RubyMap mergeIntoSelf(final ThreadContext context, IRubyObject hashmap) { + if (hashmap instanceof RubyHash) { + ((RubyHash) hashmap) + .visitAll( + context, + new RubyHash.Visitor() { @Override public void visit(IRubyObject key, IRubyObject val) { - if (val instanceof RubyHash && !valueTypeClass.isNil()) { - val = ((RubyClass) valueTypeClass).newInstance(context, val, Block.NULL_BLOCK); - } - indexSet(context, key, val); + if (val instanceof RubyHash && !valueTypeClass.isNil()) { + val = ((RubyClass) valueTypeClass).newInstance(context, val, Block.NULL_BLOCK); + } + indexSet(context, key, val); } - }, null); - } else if (hashmap instanceof RubyMap) { - RubyMap other = (RubyMap) hashmap; - if (!typeCompatible(other)) { - throw Utils.createTypeError(context, "Attempt to merge Map with mismatching types"); - } - } else { - throw Utils.createTypeError(context, "Unknown type merging into Map"); - } - return this; + }, + null); + } else if (hashmap instanceof RubyMap) { + RubyMap other = (RubyMap) hashmap; + if (!typeCompatible(other)) { + throw Utils.createTypeError(context, "Attempt to merge Map with mismatching types"); + } + } else { + throw Utils.createTypeError(context, "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; + 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(); + return newMap; + } - 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(); - return newMap; + /* + * toHash calls toHash on values, for some camparisons we only need + * a hash with the original objects still as values + */ + private RubyHash singleLevelHash(ThreadContext context) { + return RubyHash.newHash(context.runtime, table, context.nil); + } + + private boolean needTypeclass(FieldDescriptor.Type type) { + switch (type) { + case MESSAGE: + case ENUM: + return true; + default: + return false; } + } - /* - * toHash calls toHash on values, for some camparisons we only need - * a hash with the original objects still as values - */ - private RubyHash singleLevelHash(ThreadContext context) { - return RubyHash.newHash(context.runtime, table, context.nil); - } - - private boolean needTypeclass(FieldDescriptor.Type type) { - switch(type) { - case MESSAGE: - case ENUM: - return true; - default: - return false; - } - } - - private FieldDescriptor.Type keyType; - private FieldDescriptor.Type valueType; - private IRubyObject valueTypeClass; - private Map table; - private boolean keyTypeIsString = false; + private FieldDescriptor.Type keyType; + private FieldDescriptor.Type valueType; + private IRubyObject valueTypeClass; + private Map table; + private boolean keyTypeIsString = false; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java index 2167f15dc..301b95798 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java @@ -32,19 +32,25 @@ package com.google.protobuf.jruby; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedInputStream; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; -import com.google.protobuf.ByteString; -import com.google.protobuf.CodedInputStream; import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.UnknownFieldSet; import com.google.protobuf.util.JsonFormat; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.jruby.*; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; @@ -54,1297 +60,1405 @@ import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; -import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - public class RubyMessage extends RubyObject { - private final String DEFAULT_VALUE = "google.protobuf.FieldDescriptorProto.default_value"; - private final String TYPE = "type"; - - public RubyMessage(Ruby runtime, RubyClass klazz, Descriptor descriptor) { - super(runtime, klazz); - - this.descriptor = descriptor; - this.cRepeatedField = (RubyClass) runtime.getClassFromPath("Google::Protobuf::RepeatedField"); - this.cMap = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Map"); - this.builder = DynamicMessage.newBuilder(descriptor); - this.fields = new HashMap(); - this.oneofCases = new HashMap(); - this.proto3 = descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3; - } - - /* - * 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; - if (args.length == 1) { - if (!(args[0] instanceof RubyHash)) { - throw runtime.newArgumentError("expected Hash arguments."); - } - RubyHash hash = args[0].convertToHash(); - hash.visitAll(context, new RubyHash.Visitor() { - @Override - public void visit(IRubyObject key, IRubyObject value) { - if (!(key instanceof RubySymbol) && !(key instanceof RubyString)) { - throw Utils.createTypeError(context, - "Expected string or symbols as hash keys in initialization map."); - } - final FieldDescriptor fieldDescriptor = findField(context, key, ignoreUnknownFieldsOnInit); - - if (value == null || value.isNil()) return; - - if (fieldDescriptor.isMapField()) { - if (!(value instanceof RubyHash)) - throw runtime.newArgumentError("Expected Hash object as initializer value for map field '" + key.asJavaString() + "' (given " + value.getMetaClass() + ")."); - - final RubyMap map = newMapForField(context, fieldDescriptor); - map.mergeIntoSelf(context, value); - fields.put(fieldDescriptor, map); - } else if (fieldDescriptor.isRepeated()) { - if (!(value instanceof RubyArray)) - throw runtime.newArgumentError("Expected array as initializer value for repeated field '" + key.asJavaString() + "' (given " + value.getMetaClass() + ")."); - fields.put(fieldDescriptor, rubyToRepeatedField(context, fieldDescriptor, value)); - } else { - OneofDescriptor oneof = fieldDescriptor.getContainingOneof(); - if (oneof != null) { - oneofCases.put(oneof, fieldDescriptor); - } - - if (value instanceof RubyHash && fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE) { - RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - RubyClass typeClass = (RubyClass) descriptor.msgclass(context); - value = (IRubyObject) typeClass.newInstance(context, value, Block.NULL_BLOCK); - fields.put(fieldDescriptor, value); - } else { - indexSet(context, key, value); - } - - } - } - }, null); - } - 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) { - FieldDescriptor fieldDescriptor = findField(context, fieldName); - return setFieldInternal(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) { - FieldDescriptor fieldDescriptor = findField(context, fieldName); - return getFieldInternal(context, fieldDescriptor); - } - - /* - * call-seq: - * Message.inspect => string - * - * Returns a human-readable string representing this message. It will be - * formatted as "". Each - * field's value is represented according to its own #inspect method. - */ - @JRubyMethod(name = {"inspect", "to_s"}) - public IRubyObject inspect() { - ThreadContext context = getRuntime().getCurrentContext(); - String cname = metaClass.getName(); - String colon = ": "; - String comma = ", "; - StringBuilder sb = new StringBuilder("<"); - boolean addComma = false; - - sb.append(cname).append(colon); - - for (FieldDescriptor fd : descriptor.getFields()) { - if (fd.hasPresence() && !fields.containsKey(fd)) { - continue; - } - if (addComma) { - sb.append(comma); - } else { - addComma = true; - } - - sb.append(fd.getName()).append(colon); - - IRubyObject value = getFieldInternal(context, fd); - if (value instanceof RubyBoolean) { - // Booleans don't implement internal "inspect" methods so have to call handle them manually - sb.append(value.isTrue() ? "true" : "false"); - } else { - sb.append(value.inspect()); - } - } - sb.append(">"); - - return context.runtime.newString(sb.toString()); - } - - /* - * call-seq: - * Message.hash => hash_value - * - * Returns a hash value that represents this message's field values. - */ - @JRubyMethod - public IRubyObject hash(ThreadContext context) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - for (FieldDescriptor fd : descriptor.getFields()) { - digest.update((byte) getFieldInternal(context, fd).hashCode()); - } - return context.runtime.newFixnum(ByteBuffer.wrap(digest.digest()).getLong()); - } catch (NoSuchAlgorithmException ignore) { - return context.runtime.newFixnum(System.identityHashCode(this)); - } - } - - /* - * call-seq: - * Message.==(other) => boolean - * - * Performs a deep comparison of this message with another. Messages are equal - * if they have the same type and if each field is equal according to the :== - * method's semantics (a more efficient comparison may actually be done if the - * field is of a primitive type). - */ - @JRubyMethod(name = {"==", "eql?"}) - 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 (FieldDescriptor fdef : descriptor.getFields()) { - IRubyObject thisVal = getFieldInternal(context, fdef); - IRubyObject thatVal = message.getFieldInternal(context, fdef); - IRubyObject ret = thisVal.callMethod(context, "==", thatVal); - if (!ret.isTrue()) { - return runtime.getFalse(); - } - } - return runtime.getTrue(); - } - - /* - * call-seq: - * Message.respond_to?(method_name, search_private_and_protected) => boolean - * - * Parallels method_missing, returning true when this object implements a method with the given - * method_name. - */ - @JRubyMethod(name="respond_to?", required = 1, optional = 1) - public IRubyObject respondTo(ThreadContext context, IRubyObject [] args) { - String methodName = args[0].asJavaString(); - if (descriptor.findFieldByName(methodName) != null) { - return context.runtime.getTrue(); - } - RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); - IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]); - if (!oneofDescriptor.isNil()) { - return context.runtime.getTrue(); - } - if (methodName.startsWith(CLEAR_PREFIX)) { - String strippedMethodName = methodName.substring(6); - oneofDescriptor = rubyDescriptor.lookupOneof(context, context.runtime.newSymbol(strippedMethodName)); - if (!oneofDescriptor.isNil()) { - return context.runtime.getTrue(); - } - - if (descriptor.findFieldByName(strippedMethodName) != null) { - return context.runtime.getTrue(); - } - } - if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) { - String strippedMethodName = methodName.substring(4, methodName.length() - 1); - FieldDescriptor fieldDescriptor = descriptor.findFieldByName(strippedMethodName); - if (fieldDescriptor != null && - (!proto3 || fieldDescriptor.getContainingOneof() == null || fieldDescriptor - .getContainingOneof().isSynthetic()) && - fieldDescriptor.hasPresence()) { - return context.runtime.getTrue(); - } - oneofDescriptor = rubyDescriptor.lookupOneof(context, RubyString.newString(context.runtime, strippedMethodName)); - if (!oneofDescriptor.isNil()) { - return context.runtime.getTrue(); - } - } - if (methodName.endsWith(AS_VALUE_SUFFIX)) { - FieldDescriptor fieldDescriptor = descriptor.findFieldByName( - methodName.substring(0, methodName.length() - 9)); - if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { - return context.runtime.getTrue(); - } - } - if (methodName.endsWith(CONST_SUFFIX)) { - FieldDescriptor fieldDescriptor = descriptor.findFieldByName( - methodName.substring(0, methodName.length() - 6)); - if (fieldDescriptor != null) { - if (fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) { - return context.runtime.getTrue(); - } - } - } - if (methodName.endsWith(Utils.EQUAL_SIGN)) { - String strippedMethodName = methodName.substring(0, methodName.length() - 1); - FieldDescriptor fieldDescriptor = descriptor.findFieldByName(strippedMethodName); - if (fieldDescriptor != null) { - return context.runtime.getTrue(); - } - if (strippedMethodName.endsWith(AS_VALUE_SUFFIX)) { - strippedMethodName = methodName.substring(0, strippedMethodName.length() - 9); - fieldDescriptor = descriptor.findFieldByName(strippedMethodName); - if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { - return context.runtime.getTrue(); - } - } - } - boolean includePrivate = false; - if (args.length == 2) { - includePrivate = context.runtime.getTrue().equals(args[1]); - } - return metaClass.respondsToMethod(methodName, includePrivate) ? context.runtime.getTrue() : context.runtime.getFalse(); - } - - /* - * call-seq: - * Message.method_missing(*args) - * - * Provides accessors and setters and methods to clear and check for presence of - * message fields according to their field names. - * - * For any field whose name does not conflict with a built-in method, an - * accessor is provided with the same name as the field, and a setter is - * 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 - * - * This method also provides read-only accessors for oneofs. If a oneof exists - * with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to - * the name of the field in that oneof that is currently set, or nil if none. - * - * It also provides methods of the form 'clear_fieldname' to clear the value - * of the field 'fieldname'. For basic data types, this will set the default - * value of the field. - * - * Additionally, it provides methods of the form 'has_fieldname?', which returns - * true if the field 'fieldname' is set in the message object, else false. For - * 'proto3' syntax, calling this for a basic type field will result in an error. - */ - @JRubyMethod(name = "method_missing", rest = true) - public IRubyObject methodMissing(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.runtime; - String methodName = args[0].asJavaString(); - RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); - - if (args.length == 1) { - // If we find a Oneof return it's name (use lookupOneof because it has an index) - IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]); - - if (!oneofDescriptor.isNil()) { - RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor; - OneofDescriptor ood = rubyOneofDescriptor.getDescriptor(); - - // Check to see if we set this through ruby - FieldDescriptor fieldDescriptor = oneofCases.get(ood); - - if (fieldDescriptor == null) { - // See if we set this from decoding a message - fieldDescriptor = builder.getOneofFieldDescriptor(ood); - - if (fieldDescriptor == null) { - return context.nil; - } else { - // Cache it so we don't need to do multiple checks next time - oneofCases.put(ood, fieldDescriptor); - return runtime.newSymbol(fieldDescriptor.getName()); - } - } else { - return runtime.newSymbol(fieldDescriptor.getName()); - } - } - - // If we find a field return its value - FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName); - - if (fieldDescriptor != null) { - return getFieldInternal(context, fieldDescriptor); - } - - if (methodName.startsWith(CLEAR_PREFIX)) { - methodName = methodName.substring(6); - oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName)); - if (!oneofDescriptor.isNil()) { - fieldDescriptor = oneofCases.get(((RubyOneofDescriptor) oneofDescriptor).getDescriptor()); - if (fieldDescriptor == null) { - // Clearing an already cleared oneof; return here to avoid NoMethodError. - return context.nil; - } - } - - if (fieldDescriptor == null) { - fieldDescriptor = descriptor.findFieldByName(methodName); - } - - if (fieldDescriptor != null) { - return clearFieldInternal(context, fieldDescriptor); - } - - } else if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) { - methodName = methodName.substring(4, methodName.length() - 1); // Trim "has_" and "?" off the field name - oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName)); - if (!oneofDescriptor.isNil()) { - RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor; - return oneofCases.containsKey(rubyOneofDescriptor.getDescriptor()) ? runtime.getTrue() : runtime.getFalse(); - } - - fieldDescriptor = descriptor.findFieldByName(methodName); - - if (fieldDescriptor != null && - (!proto3 || fieldDescriptor.getContainingOneof() == null || fieldDescriptor - .getContainingOneof().isSynthetic()) && - fieldDescriptor.hasPresence()) { - return fields.containsKey(fieldDescriptor) ? runtime.getTrue() - : runtime.getFalse(); - } - - } else if (methodName.endsWith(AS_VALUE_SUFFIX)) { - methodName = methodName.substring(0, methodName.length() - 9); - fieldDescriptor = descriptor.findFieldByName(methodName); - - if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { - IRubyObject value = getFieldInternal(context, fieldDescriptor); - - if (!value.isNil() && value instanceof RubyMessage) { - return ((RubyMessage) value).index(context, runtime.newString("value")); - } - - return value; - } - - } else if (methodName.endsWith(CONST_SUFFIX)) { - methodName = methodName.substring(0, methodName.length() - 6); - fieldDescriptor = descriptor.findFieldByName(methodName); - if (fieldDescriptor != null && fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) { - IRubyObject enumValue = getFieldInternal(context, fieldDescriptor); - - if (!enumValue.isNil()) { - EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType(); - if (enumValue instanceof RubyRepeatedField) { - RubyArray values = (RubyArray) ((RubyRepeatedField) enumValue).toArray(context); - RubyArray retValues = runtime.newArray(values.getLength()); - for (int i = 0; i < values.getLength(); i++) { - String val = values.eltInternal(i).toString(); - retValues.store((long) i, runtime.newFixnum(enumDescriptor.findValueByName(val).getNumber())); - } - return retValues; - } - - return runtime.newFixnum(enumDescriptor.findValueByName(enumValue.asJavaString()).getNumber()); - } - } - } - - } else if (args.length == 2 && methodName.endsWith(Utils.EQUAL_SIGN)) { - - methodName = methodName.substring(0, methodName.length() - 1); // Trim equals sign - FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName); - if (fieldDescriptor != null) { - return setFieldInternal(context, fieldDescriptor, args[1]); - } - - IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, RubyString.newString(context.runtime, methodName)); - if (!oneofDescriptor.isNil()) { - throw runtime.newRuntimeError("Oneof accessors are read-only."); - } - - if (methodName.endsWith(AS_VALUE_SUFFIX)) { - methodName = methodName.substring(0, methodName.length() - 9); - - fieldDescriptor = descriptor.findFieldByName(methodName); - - if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { - if (args[1].isNil()) { - return setFieldInternal(context, fieldDescriptor, args[1]); - } - - RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context); - RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK); - msg.indexSet(context, runtime.newString("value"), args[1]); - return setFieldInternal(context, fieldDescriptor, msg); - } - } - - } - - return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK); - } - - /** - * 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); - for (FieldDescriptor fieldDescriptor : this.descriptor.getFields()) { - if (fieldDescriptor.isRepeated()) { - dup.fields.put(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor)); - } else if (fields.containsKey(fieldDescriptor)) { - dup.setFieldInternal(context, fieldDescriptor, fields.get(fieldDescriptor)); - } else if (this.builder.hasField(fieldDescriptor)) { - dup.fields.put(fieldDescriptor, wrapField(context, fieldDescriptor, this.builder.getField(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, options = {}) => bytes - * - * Encodes the given message object to its serialized form in protocol buffers - * wire format. - * @param options [Hash] options for the encoder - * recursion_limit: set to maximum encoding depth for message (default is 64) - */ - @JRubyMethod(required = 1, optional = 1, meta = true) - public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject[] args) { - if (recv != args[0].getMetaClass()) { - throw context.runtime.newArgumentError("Tried to encode a " + args[0].getMetaClass() + " message with " + recv); - } - RubyMessage message = (RubyMessage) args[0]; - int recursionLimitInt = SINK_MAXIMUM_NESTING; - - if (args.length > 1) { - RubyHash options = (RubyHash) args[1]; - IRubyObject recursionLimit = options.fastARef(context.runtime.newSymbol("recursion_limit")); - - if (recursionLimit != null) { - recursionLimitInt = ((RubyNumeric) recursionLimit).getIntValue(); - } - } - return context.runtime.newString(new ByteList(message.build(context, 0, recursionLimitInt).toByteArray())); - } - - /* - * call-seq: - * MessageClass.decode(data, options = {}) => message - * - * Decodes the given data (as a string containing bytes in protocol buffers wire - * format) under the interpretation given by this message class's definition - * and returns a message object with the corresponding field values. - * @param options [Hash] options for the decoder - * recursion_limit: set to maximum decoding depth for message (default is 100) - */ - @JRubyMethod(required = 1, optional = 1, meta = true) - public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyObject[] args) { - IRubyObject data = args[0]; - byte[] bin = data.convertToString().getBytes(); - CodedInputStream input = CodedInputStream.newInstance(bin); - RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK); - - if (args.length == 2) { - if (!(args[1] instanceof RubyHash)) { - throw context.runtime.newArgumentError("Expected hash arguments."); - } - - IRubyObject recursionLimit = ((RubyHash) args[1]).fastARef(context.runtime.newSymbol("recursion_limit")); - if (recursionLimit != null) { - input.setRecursionLimit(((RubyNumeric) recursionLimit).getIntValue()); - } - } - - try { - ret.builder.mergeFrom(input); - } catch (Exception e) { - throw RaiseException.from(context.runtime, (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), e.getMessage()); - } - - if (!ret.proto3) { - // Need to reset unknown values in repeated enum fields - ret.builder.getUnknownFields().asMap().forEach((i, values) -> { - FieldDescriptor fd = ret.builder.getDescriptorForType().findFieldByNumber(i); - if (fd != null && fd.isRepeated() && fd.getType() == FieldDescriptor.Type.ENUM) { - EnumDescriptor ed = fd.getEnumType(); - values.getVarintList().forEach(value -> { - ret.builder.addRepeatedField(fd, ed.findValueByNumberCreatingIfUnknown(value.intValue())); - }); - } - }); - } - - return ret; - } - - /* - * call-seq: - * MessageClass.encode_json(msg, options = {}) => json_string - * - * Encodes the given message object into its serialized JSON representation. - * @param options [Hash] options for the decoder - * preserve_proto_fieldnames: set true to use original fieldnames (default is to camelCase) - * emit_defaults: set true to emit 0/false values (default is to omit them) - */ - @JRubyMethod(name = "encode_json", required = 1, optional = 1, meta = true) - public static IRubyObject encodeJson(ThreadContext context, IRubyObject recv, IRubyObject[] args) { - Ruby runtime = context.runtime; - RubyMessage message = (RubyMessage) args[0]; - JsonFormat.Printer printer = JsonFormat.printer().omittingInsignificantWhitespace(); - String result; - - if (args.length > 1) { - RubyHash options; - if (args[1] instanceof RubyHash) { - options = (RubyHash) args[1]; - } else if (args[1].respondsTo("to_h")) { - options = (RubyHash) args[1].callMethod(context, "to_h"); - } else { - throw runtime.newArgumentError("Expected hash arguments."); - } - - IRubyObject emitDefaults = options.fastARef(runtime.newSymbol("emit_defaults")); - IRubyObject preserveNames = options.fastARef(runtime.newSymbol("preserve_proto_fieldnames")); - - if (emitDefaults != null && emitDefaults.isTrue()) { - printer = printer.includingDefaultValueFields(); - } - - if (preserveNames != null && preserveNames.isTrue()) { - printer = printer.preservingProtoFieldNames(); - } - } - printer = printer.usingTypeRegistry(JsonFormat.TypeRegistry.newBuilder().add(message.descriptor).build()); - - try { - result = printer.print(message.build(context, 0, SINK_MAXIMUM_NESTING)); - } catch (InvalidProtocolBufferException e) { - throw runtime.newRuntimeError(e.getMessage()); - } catch (IllegalArgumentException e) { - throw createParseError(context, e.getMessage()); - } - - return runtime.newString(result); - } - - /* - * call-seq: - * MessageClass.decode_json(data, options = {}) => message - * - * Decodes the given data (as a string containing bytes in protocol buffers wire - * format) under the interpretation given by this message class's definition - * and returns a message object with the corresponding field values. - * - * @param options [Hash] options for the decoder - * ignore_unknown_fields: set true to ignore unknown fields (default is to - * raise an error) - */ - @JRubyMethod(name = "decode_json", required = 1, optional = 1, meta = true) - public static IRubyObject decodeJson(ThreadContext context, IRubyObject recv, IRubyObject[] args) { - Ruby runtime = context.runtime; - boolean ignoreUnknownFields = false; - IRubyObject data = args[0]; - JsonFormat.Parser parser = JsonFormat.parser(); - - if (args.length == 2) { - if (!(args[1] instanceof RubyHash)) { - throw runtime.newArgumentError("Expected hash arguments."); - } - - IRubyObject ignoreSetting = ((RubyHash) args[1]).fastARef(runtime.newSymbol("ignore_unknown_fields")); - if (ignoreSetting != null && ignoreSetting.isTrue()) { - parser = parser.ignoringUnknownFields(); - } - } - - if (!(data instanceof RubyString)) { - throw runtime.newArgumentError("Expected string for JSON data."); - } - - RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK); - parser = parser.usingTypeRegistry(JsonFormat.TypeRegistry.newBuilder().add(ret.descriptor).build()); - - try { - parser.merge(data.asJavaString(), ret.builder); - } catch(InvalidProtocolBufferException e) { - throw createParseError(context, e.getMessage().replace("Cannot find", "No such")); - } - - if (isWrapper(ret.descriptor)) { - throw runtime.newRuntimeError("Parsing a wrapper type from JSON at the top level does not work."); - } - - return ret; - } - - @JRubyMethod(name = "to_h") - public IRubyObject toHash(ThreadContext context) { - Ruby runtime = context.runtime; - RubyHash ret = RubyHash.newHash(runtime); - for (FieldDescriptor fdef : this.descriptor.getFields()) { - IRubyObject value = getFieldInternal(context, fdef, proto3); - - if (!value.isNil()) { - if (fdef.isRepeated() && !fdef.isMapField()) { - if (!proto3 && ((RubyRepeatedField) value).size() == 0) continue; // Don't output empty repeated fields for proto2 - if (fdef.getType() != FieldDescriptor.Type.MESSAGE) { - value = Helpers.invoke(context, value, "to_a"); - } else { - RubyArray ary = value.convertToArray(); - for (int i = 0; i < ary.size(); i++) { - IRubyObject submsg = Helpers.invoke(context, ary.eltInternal(i), "to_h"); - ary.eltInternalSet(i, submsg); - } - - value = ary.to_ary(); - } - } else if (value.respondsTo("to_h")) { - value = Helpers.invoke(context, value, "to_h"); - } else if (value.respondsTo("to_a")) { - value = Helpers.invoke(context, value, "to_a"); - } - } - if (proto3 || !value.isNil()) { - ret.fastASet(runtime.newSymbol(fdef.getName()), value); - } - } - return ret; - } - - protected DynamicMessage build(ThreadContext context, int depth, int recursionLimit) { - if (depth >= recursionLimit) { - throw context.runtime.newRuntimeError("Recursion limit exceeded during encoding."); - } - - RubySymbol typeBytesSymbol = RubySymbol.newSymbol(context.runtime, "TYPE_BYTES"); - - // Handle the typical case where the fields.keySet contain the fieldDescriptors - for (FieldDescriptor fieldDescriptor : fields.keySet()) { - IRubyObject value = fields.get(fieldDescriptor); - - if (value instanceof RubyMap) { - builder.clearField(fieldDescriptor); - RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - for (DynamicMessage kv : ((RubyMap) value).build(context, mapDescriptor, depth, recursionLimit)) { - builder.addRepeatedField(fieldDescriptor, kv); - } - - } else if (value instanceof RubyRepeatedField) { - RubyRepeatedField repeatedField = (RubyRepeatedField) value; - - builder.clearField(fieldDescriptor); - for (int i = 0; i < repeatedField.size(); i++) { - Object item = convert(context, fieldDescriptor, repeatedField.get(i), depth, recursionLimit, - /*isDefaultValueForBytes*/ false); - builder.addRepeatedField(fieldDescriptor, item); - } - - } else if (!value.isNil()) { - /** - * Detect the special case where default_value strings are provided for byte fields. - * If so, disable normal string encoding behavior within convert. - * For a more detailed explanation of other possible workarounds, see the comments - * above {@code com.google.protobuf.Internal#stringDefaultValue() - * stringDefaultValue}. - */ - boolean isDefaultStringForBytes = false; - if (DEFAULT_VALUE.equals(fieldDescriptor.getFullName())) { - FieldDescriptor enumFieldDescriptorForType = - this.builder.getDescriptorForType().findFieldByName(TYPE); - if (typeBytesSymbol.equals(fields.get(enumFieldDescriptorForType))) { - isDefaultStringForBytes = true; - } - } - builder.setField(fieldDescriptor, convert(context, fieldDescriptor, value, depth, recursionLimit, isDefaultStringForBytes)); - } - } - - // Handle cases where {@code fields} doesn't contain the value until after getFieldInternal - // is called - typical of a deserialized message. Skip non-maps and descriptors that already - // have an entry in {@code fields}. - for (FieldDescriptor fieldDescriptor : descriptor.getFields()) { - if (!fieldDescriptor.isMapField()) { - continue; - } - IRubyObject value = fields.get(fieldDescriptor); - if (value!=null) { - continue; - } - value = getFieldInternal(context, fieldDescriptor); - if (value instanceof RubyMap) { - builder.clearField(fieldDescriptor); - RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, - fieldDescriptor); - for (DynamicMessage kv : ((RubyMap) value).build(context, mapDescriptor, depth, recursionLimit)) { - builder.addRepeatedField(fieldDescriptor, kv); - } - } - } - return builder.build(); - } - - // Internal use only, called by Google::Protobuf.deep_copy - protected IRubyObject deepCopy(ThreadContext context) { - RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK); - for (FieldDescriptor fdef : descriptor.getFields()) { - if (fdef.isRepeated()) { - copy.fields.put(fdef, this.getRepeatedField(context, fdef).deepCopy(context)); - } else if (fields.containsKey(fdef)) { - copy.setFieldInternal(context, fdef, fields.get(fdef)); - } else if (builder.hasField(fdef)) { - copy.fields.put(fdef, wrapField(context, fdef, builder.getField(fdef))); - } - } - return copy; - } - - protected IRubyObject clearField(ThreadContext context, FieldDescriptor fieldDescriptor) { - validateMessageType(context, fieldDescriptor, "clear"); - return clearFieldInternal(context, fieldDescriptor); - } - - protected void discardUnknownFields(ThreadContext context) { - discardUnknownFields(context, builder); - } - - protected IRubyObject getField(ThreadContext context, FieldDescriptor fieldDescriptor) { - validateMessageType(context, fieldDescriptor, "get"); - return getFieldInternal(context, fieldDescriptor); - } - - protected IRubyObject hasField(ThreadContext context, FieldDescriptor fieldDescriptor) { - validateMessageType(context, fieldDescriptor, "has?"); - if (!fieldDescriptor.hasPresence()) { - throw context.runtime.newArgumentError("does not track presence"); - } - return fields.containsKey(fieldDescriptor) ? context.runtime.getTrue() : context.runtime.getFalse(); - } - - protected IRubyObject setField(ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) { - validateMessageType(context, fieldDescriptor, "set"); - return setFieldInternal(context, fieldDescriptor, value); - } - - private RubyRepeatedField getRepeatedField(ThreadContext context, FieldDescriptor fieldDescriptor) { - if (fields.containsKey(fieldDescriptor)) { - return (RubyRepeatedField) fields.get(fieldDescriptor); - } - int count = this.builder.getRepeatedFieldCount(fieldDescriptor); - RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor); - for (int i = 0; i < count; i++) { - ret.push(context, new IRubyObject[] {wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i))}); - } - fields.put(fieldDescriptor, ret); - return ret; - } - - private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) { - this.builder.mergeFrom(dynamicMessage); - return this; - } - - private IRubyObject clearFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) { - OneofDescriptor ood = fieldDescriptor.getContainingOneof(); - if (ood != null) oneofCases.remove(ood); - fields.remove(fieldDescriptor); - builder.clearField(fieldDescriptor); - return context.nil; - } - - private void discardUnknownFields(ThreadContext context, Message.Builder messageBuilder) { - messageBuilder.setUnknownFields(UnknownFieldSet.getDefaultInstance()); - messageBuilder.getAllFields().forEach((fd, value) -> { - if (fd.getType() == FieldDescriptor.Type.MESSAGE) { - if (fd.isRepeated()) { - messageBuilder.clearField(fd); - ((List) value).forEach((val) -> { - Message.Builder submessageBuilder = ((DynamicMessage) val).toBuilder(); - discardUnknownFields(context, submessageBuilder); - messageBuilder.addRepeatedField(fd, submessageBuilder.build()); - }); - } else { - Message.Builder submessageBuilder = ((DynamicMessage) value).toBuilder(); - discardUnknownFields(context, submessageBuilder); - messageBuilder.setField(fd, submessageBuilder.build()); - } - } - }); - } - - private FieldDescriptor findField(ThreadContext context, IRubyObject fieldName) { - return findField(context, fieldName, false); - } - - private FieldDescriptor findField(ThreadContext context, IRubyObject fieldName, boolean ignoreUnknownField) { - String nameStr = fieldName.asJavaString(); - FieldDescriptor ret = this.descriptor.findFieldByName(nameStr); - if (ret == null && !ignoreUnknownField) { - throw context.runtime.newArgumentError("field " + fieldName.asJavaString() + " is not found"); - } - return ret; - } - - // convert a ruby object to protobuf type, skip type check since it is checked on the way in - private Object convert(ThreadContext context, - FieldDescriptor fieldDescriptor, - IRubyObject value, int depth, int recursionLimit, - boolean isDefaultStringForBytes) { - Object val = null; - switch (fieldDescriptor.getType()) { - case INT32: - case SFIXED32: - case SINT32: - val = RubyNumeric.num2int(value); - break; - case INT64: - case SFIXED64: - case SINT64: - val = RubyNumeric.num2long(value); - break; - case FIXED32: - case UINT32: - val = Utils.num2uint(value); - break; - case FIXED64: - case UINT64: - val = Utils.num2ulong(context.runtime, value); - break; - case FLOAT: - val = (float) RubyNumeric.num2dbl(value); - break; - case DOUBLE: - val = (double) RubyNumeric.num2dbl(value); - break; - case BOOL: - val = value.isTrue(); - break; - case BYTES: - val = ByteString.copyFrom(((RubyString) value).getBytes()); - break; - case STRING: - if (isDefaultStringForBytes) { - val = ((RubyString) value).getByteList().toString(); - } else { - val = value.asJavaString(); - } - break; - case MESSAGE: - val = ((RubyMessage) value).build(context, depth + 1, recursionLimit); - break; - case ENUM: - EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType(); - if (Utils.isRubyNum(value)) { - val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); - } else { - val = enumDescriptor.findValueByName(value.asJavaString()); - } - break; - default: - break; - } - - return val; - } - - private static RaiseException createParseError(ThreadContext context, String message) { - if (parseErrorClass == null) { - parseErrorClass = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"); - } - return RaiseException.from(context.runtime, parseErrorClass, message); - } - - private IRubyObject wrapField(ThreadContext context, FieldDescriptor fieldDescriptor, Object value) { - return wrapField(context, fieldDescriptor, value, false); - } - - private IRubyObject wrapField(ThreadContext context, FieldDescriptor fieldDescriptor, Object value, boolean encodeBytes) { - if (value == null) { - return context.runtime.getNil(); - } - Ruby runtime = context.runtime; - - switch (fieldDescriptor.getType()) { - case INT32: - case INT64: - case FIXED32: - case SINT32: - case FIXED64: - case SINT64: - case SFIXED64: - case SFIXED32: - case UINT32: - case UINT64: - case FLOAT: - case DOUBLE: - case BOOL: - case BYTES: - case STRING: - return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value, encodeBytes); - 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: - EnumValueDescriptor enumValueDescriptor = (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, FieldDescriptor fieldDescriptor) { - IRubyObject typeClass = context.runtime.getNilClass(); - IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor); - FieldDescriptor.Type type = fieldDescriptor.getType(); - - if (type == FieldDescriptor.Type.MESSAGE) { - typeClass = ((RubyDescriptor) descriptor).msgclass(context); - - } else if (type == FieldDescriptor.Type.ENUM) { - typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context); - } - - RubyRepeatedField field = new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass); - field.setName(fieldDescriptor.getName()); - - return field; - } - - private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) { - return getFieldInternal(context, fieldDescriptor, true); - } - - private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor, - boolean returnDefaults) { - OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof(); - if (oneofDescriptor != null) { - if (oneofCases.get(oneofDescriptor) == fieldDescriptor) { - IRubyObject value = fields.get(fieldDescriptor); - if (value == null) { - FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor); - if (oneofCase != null) { - Object builderValue = builder.getField(oneofCase); - if (builderValue != null) { - boolean encodeBytes = oneofCase.hasDefaultValue() && builderValue.equals(oneofCase.getDefaultValue()); - value = wrapField(context, oneofCase, builderValue, encodeBytes); - } - } - if (value == null) { - return context.nil; - } else { - return value; - } - } else { - return value; - } - } else { - FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor); - if (oneofCase != fieldDescriptor) { - if (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE - || !returnDefaults) { - return context.nil; - } else { - return wrapField(context, fieldDescriptor, - fieldDescriptor.getDefaultValue(), true); - } - } - if (returnDefaults || builder.hasField(fieldDescriptor)) { - Object rawValue = builder.getField(oneofCase); - boolean encodeBytes = oneofCase.hasDefaultValue() && rawValue.equals(oneofCase.getDefaultValue()); - IRubyObject value = wrapField(context, oneofCase, rawValue, encodeBytes); - fields.put(fieldDescriptor, value); - return value; - } else { - return context.nil; - } - } - } - - if (fieldDescriptor.isMapField()) { - RubyMap map = (RubyMap) fields.get(fieldDescriptor); - if (map == null) { - map = newMapForField(context, fieldDescriptor); - int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor); - FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1); - 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)); - } + private final String DEFAULT_VALUE = "google.protobuf.FieldDescriptorProto.default_value"; + private final String TYPE = "type"; + + public RubyMessage(Ruby runtime, RubyClass klazz, Descriptor descriptor) { + super(runtime, klazz); + + this.descriptor = descriptor; + this.cRepeatedField = (RubyClass) runtime.getClassFromPath("Google::Protobuf::RepeatedField"); + this.cMap = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Map"); + this.builder = DynamicMessage.newBuilder(descriptor); + this.fields = new HashMap(); + this.oneofCases = new HashMap(); + this.proto3 = descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3; + } + + /* + * 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; + if (args.length == 1) { + if (!(args[0] instanceof RubyHash)) { + throw runtime.newArgumentError("expected Hash arguments."); + } + RubyHash hash = args[0].convertToHash(); + hash.visitAll( + context, + new RubyHash.Visitor() { + @Override + public void visit(IRubyObject key, IRubyObject value) { + if (!(key instanceof RubySymbol) && !(key instanceof RubyString)) { + throw Utils.createTypeError( + context, "Expected string or symbols as hash keys in initialization map."); + } + final FieldDescriptor fieldDescriptor = + findField(context, key, ignoreUnknownFieldsOnInit); + + if (value == null || value.isNil()) return; + + if (fieldDescriptor.isMapField()) { + if (!(value instanceof RubyHash)) + throw runtime.newArgumentError( + "Expected Hash object as initializer value for map field '" + + key.asJavaString() + + "' (given " + + value.getMetaClass() + + ")."); + + final RubyMap map = newMapForField(context, fieldDescriptor); + map.mergeIntoSelf(context, value); fields.put(fieldDescriptor, map); - } - return map; - } - - if (fieldDescriptor.isRepeated()) { - return getRepeatedField(context, fieldDescriptor); - } - - if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE || - builder.hasField(fieldDescriptor) || fields.containsKey(fieldDescriptor)) { - if (fields.containsKey(fieldDescriptor)) { - return fields.get(fieldDescriptor); - } else if (returnDefaults || builder.hasField(fieldDescriptor)) { - Object rawValue = builder.getField(fieldDescriptor); - boolean encodeBytes = fieldDescriptor.hasDefaultValue() && rawValue.equals(fieldDescriptor.getDefaultValue()); - IRubyObject value = wrapField(context, fieldDescriptor, rawValue, encodeBytes); - if (builder.hasField(fieldDescriptor)) { - fields.put(fieldDescriptor, value); - } - return value; - } - } - return context.nil; - } - - private IRubyObject setFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) { - testFrozen("can't modify frozen " + getMetaClass()); - - if (fieldDescriptor.isMapField()) { - if (!(value instanceof RubyMap)) { - throw Utils.createTypeError(context, "Expected Map instance"); - } - RubyMap thisMap = (RubyMap) getFieldInternal(context, fieldDescriptor); - thisMap.mergeIntoSelf(context, value); - - } else if (fieldDescriptor.isRepeated()) { - if (value instanceof RubyRepeatedField) { - fields.put(fieldDescriptor, value); - } else { - throw Utils.createTypeError(context, "Expected repeated field array"); - } - - } else { - boolean addValue = true; - FieldDescriptor.Type fieldType = fieldDescriptor.getType(); - OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof(); - - // Determine the typeclass, if any - IRubyObject typeClass = context.runtime.getObject(); - if (fieldType == FieldDescriptor.Type.MESSAGE) { - typeClass = ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context); - if (value.isNil()){ - addValue = false; - } - } else if (fieldType == FieldDescriptor.Type.ENUM) { - typeClass = ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor)).enummodule(context); - value = enumToSymbol(context, fieldDescriptor.getEnumType(), value); - } - - if (oneofDescriptor != null) { - FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor); - - // Remove the existing field if we are setting a different field in the Oneof - if (oneofCase != null && oneofCase != fieldDescriptor) { - fields.remove(oneofCase); + } else if (fieldDescriptor.isRepeated()) { + if (!(value instanceof RubyArray)) + throw runtime.newArgumentError( + "Expected array as initializer value for repeated field '" + + key.asJavaString() + + "' (given " + + value.getMetaClass() + + ")."); + fields.put(fieldDescriptor, rubyToRepeatedField(context, fieldDescriptor, value)); + } else { + OneofDescriptor oneof = fieldDescriptor.getContainingOneof(); + if (oneof != null) { + oneofCases.put(oneof, fieldDescriptor); } - // Keep track of what Oneofs are set - if (value.isNil()) { - oneofCases.remove(oneofDescriptor); - if (!oneofDescriptor.isSynthetic()) { - addValue = false; - } + if (value instanceof RubyHash + && fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE) { + RubyDescriptor descriptor = + (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); + RubyClass typeClass = (RubyClass) descriptor.msgclass(context); + value = (IRubyObject) typeClass.newInstance(context, value, Block.NULL_BLOCK); + fields.put(fieldDescriptor, value); } else { - oneofCases.put(oneofDescriptor, fieldDescriptor); + indexSet(context, key, value); } + } } + }, + null); + } + return this; + } - if (addValue) { - value = Utils.checkType(context, fieldType, fieldDescriptor.getName(), value, (RubyModule) typeClass); - fields.put(fieldDescriptor, value); - } else { - fields.remove(fieldDescriptor); - } - } - return context.nil; + /* + * 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) { + FieldDescriptor fieldDescriptor = findField(context, fieldName); + return setFieldInternal(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) { + FieldDescriptor fieldDescriptor = findField(context, fieldName); + return getFieldInternal(context, fieldDescriptor); + } + + /* + * call-seq: + * Message.inspect => string + * + * Returns a human-readable string representing this message. It will be + * formatted as "". Each + * field's value is represented according to its own #inspect method. + */ + @JRubyMethod(name = {"inspect", "to_s"}) + public IRubyObject inspect() { + ThreadContext context = getRuntime().getCurrentContext(); + String cname = metaClass.getName(); + String colon = ": "; + String comma = ", "; + StringBuilder sb = new StringBuilder("<"); + boolean addComma = false; + + sb.append(cname).append(colon); + + for (FieldDescriptor fd : descriptor.getFields()) { + if (fd.hasPresence() && !fields.containsKey(fd)) { + continue; + } + if (addComma) { + sb.append(comma); + } else { + addComma = true; + } + + sb.append(fd.getName()).append(colon); + + IRubyObject value = getFieldInternal(context, fd); + if (value instanceof RubyBoolean) { + // Booleans don't implement internal "inspect" methods so have to call handle them manually + sb.append(value.isTrue() ? "true" : "false"); + } else { + sb.append(value.inspect()); + } + } + sb.append(">"); + + return context.runtime.newString(sb.toString()); + } + + /* + * call-seq: + * Message.hash => hash_value + * + * Returns a hash value that represents this message's field values. + */ + @JRubyMethod + public IRubyObject hash(ThreadContext context) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + for (FieldDescriptor fd : descriptor.getFields()) { + digest.update((byte) getFieldInternal(context, fd).hashCode()); + } + return context.runtime.newFixnum(ByteBuffer.wrap(digest.digest()).getLong()); + } catch (NoSuchAlgorithmException ignore) { + return context.runtime.newFixnum(System.identityHashCode(this)); + } + } + + /* + * call-seq: + * Message.==(other) => boolean + * + * Performs a deep comparison of this message with another. Messages are equal + * if they have the same type and if each field is equal according to the :== + * method's semantics (a more efficient comparison may actually be done if the + * field is of a primitive type). + */ + @JRubyMethod(name = {"==", "eql?"}) + 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(); } - private IRubyObject getDescriptorForField(ThreadContext context, FieldDescriptor fieldDescriptor) { - RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); - RubyFieldDescriptor fd = (RubyFieldDescriptor) thisRbDescriptor.lookup(context, context.runtime.newString(fieldDescriptor.getName())); - return fd.getSubtype(context); + for (FieldDescriptor fdef : descriptor.getFields()) { + IRubyObject thisVal = getFieldInternal(context, fdef); + IRubyObject thatVal = message.getFieldInternal(context, fdef); + IRubyObject ret = thisVal.callMethod(context, "==", thatVal); + if (!ret.isTrue()) { + return runtime.getFalse(); + } } + return runtime.getTrue(); + } - private IRubyObject enumToSymbol(ThreadContext context, EnumDescriptor enumDescriptor, IRubyObject value) { - if (value instanceof RubySymbol) { - return (RubySymbol) value; - } else if (Utils.isRubyNum(value)) { - EnumValueDescriptor enumValue = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); - if (enumValue.getIndex() != -1) { - return context.runtime.newSymbol(enumValue.getName()); - } else { - return value; - } - } else if (value instanceof RubyString) { - return ((RubyString) value).intern(); - } - - return context.runtime.newSymbol("UNKNOWN"); + /* + * call-seq: + * Message.respond_to?(method_name, search_private_and_protected) => boolean + * + * Parallels method_missing, returning true when this object implements a method with the given + * method_name. + */ + @JRubyMethod(name = "respond_to?", required = 1, optional = 1) + public IRubyObject respondTo(ThreadContext context, IRubyObject[] args) { + String methodName = args[0].asJavaString(); + if (descriptor.findFieldByName(methodName) != null) { + return context.runtime.getTrue(); } - - private RubyRepeatedField rubyToRepeatedField(ThreadContext context, - FieldDescriptor fieldDescriptor, IRubyObject value) { - RubyArray arr = value.convertToArray(); - RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor); - IRubyObject[] values = new IRubyObject[arr.size()]; - FieldDescriptor.Type fieldType = fieldDescriptor.getType(); - - RubyModule typeClass = null; - if (fieldType == FieldDescriptor.Type.MESSAGE) { - RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - typeClass = (RubyModule) descriptor.msgclass(context); - } else if (fieldType == FieldDescriptor.Type.ENUM) { - RubyEnumDescriptor enumDescriptor = (RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor); - typeClass = (RubyModule) enumDescriptor.enummodule(context); - } - - for (int i = 0; i < arr.size(); i++) { - IRubyObject item = arr.eltInternal(i); - if (item.isNil()) { - throw Utils.createTypeError(context, "nil message not allowed here."); - } - if (item instanceof RubyHash && typeClass != null) { - values[i] = ((RubyClass) typeClass).newInstance(context, item, Block.NULL_BLOCK); - } else { - if (fieldType == FieldDescriptor.Type.ENUM) { - item = enumToSymbol(context, fieldDescriptor.getEnumType(), item); - } - - values[i] = item; - } - } - repeatedField.push(context, values); - - return repeatedField; + RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); + IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]); + if (!oneofDescriptor.isNil()) { + return context.runtime.getTrue(); } + if (methodName.startsWith(CLEAR_PREFIX)) { + String strippedMethodName = methodName.substring(6); + oneofDescriptor = + rubyDescriptor.lookupOneof(context, context.runtime.newSymbol(strippedMethodName)); + if (!oneofDescriptor.isNil()) { + return context.runtime.getTrue(); + } - private RubyMap newMapForField(ThreadContext context, FieldDescriptor fieldDescriptor) { - RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1); - 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 (descriptor.findFieldByName(strippedMethodName) != null) { + return context.runtime.getTrue(); + } + } + if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) { + String strippedMethodName = methodName.substring(4, methodName.length() - 1); + FieldDescriptor fieldDescriptor = descriptor.findFieldByName(strippedMethodName); + if (fieldDescriptor != null + && (!proto3 + || fieldDescriptor.getContainingOneof() == null + || fieldDescriptor.getContainingOneof().isSynthetic()) + && fieldDescriptor.hasPresence()) { + return context.runtime.getTrue(); + } + oneofDescriptor = + rubyDescriptor.lookupOneof( + context, RubyString.newString(context.runtime, strippedMethodName)); + if (!oneofDescriptor.isNil()) { + return context.runtime.getTrue(); + } + } + if (methodName.endsWith(AS_VALUE_SUFFIX)) { + FieldDescriptor fieldDescriptor = + descriptor.findFieldByName(methodName.substring(0, methodName.length() - 9)); + if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { + return context.runtime.getTrue(); + } + } + if (methodName.endsWith(CONST_SUFFIX)) { + FieldDescriptor fieldDescriptor = + descriptor.findFieldByName(methodName.substring(0, methodName.length() - 6)); + if (fieldDescriptor != null) { + if (fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) { + return context.runtime.getTrue(); + } + } + } + if (methodName.endsWith(Utils.EQUAL_SIGN)) { + String strippedMethodName = methodName.substring(0, methodName.length() - 1); + FieldDescriptor fieldDescriptor = descriptor.findFieldByName(strippedMethodName); + if (fieldDescriptor != null) { + return context.runtime.getTrue(); + } + if (strippedMethodName.endsWith(AS_VALUE_SUFFIX)) { + strippedMethodName = methodName.substring(0, strippedMethodName.length() - 9); + fieldDescriptor = descriptor.findFieldByName(strippedMethodName); + if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { + return context.runtime.getTrue(); + } + } + } + boolean includePrivate = false; + if (args.length == 2) { + includePrivate = context.runtime.getTrue().equals(args[1]); + } + return metaClass.respondsToMethod(methodName, includePrivate) + ? context.runtime.getTrue() + : context.runtime.getFalse(); + } - if (valueField.getType() == 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); + /* + * call-seq: + * Message.method_missing(*args) + * + * Provides accessors and setters and methods to clear and check for presence of + * message fields according to their field names. + * + * For any field whose name does not conflict with a built-in method, an + * accessor is provided with the same name as the field, and a setter is + * 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 + * + * This method also provides read-only accessors for oneofs. If a oneof exists + * with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to + * the name of the field in that oneof that is currently set, or nil if none. + * + * It also provides methods of the form 'clear_fieldname' to clear the value + * of the field 'fieldname'. For basic data types, this will set the default + * value of the field. + * + * Additionally, it provides methods of the form 'has_fieldname?', which returns + * true if the field 'fieldname' is set in the message object, else false. For + * 'proto3' syntax, calling this for a basic type field will result in an error. + */ + @JRubyMethod(name = "method_missing", rest = true) + public IRubyObject methodMissing(ThreadContext context, IRubyObject[] args) { + Ruby runtime = context.runtime; + String methodName = args[0].asJavaString(); + RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); - } else if (valueField.getType() == FieldDescriptor.Type.ENUM) { - RubyFieldDescriptor rubyFieldDescriptor = (RubyFieldDescriptor) mapDescriptor.lookup(context, - context.runtime.newString("value")); - RubyEnumDescriptor rubyEnumDescriptor = (RubyEnumDescriptor) rubyFieldDescriptor.getSubtype(context); - return (RubyMap) cMap.newInstance(context, keyType, valueType, - rubyEnumDescriptor.enummodule(context), Block.NULL_BLOCK); + if (args.length == 1) { + // If we find a Oneof return it's name (use lookupOneof because it has an index) + IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]); + if (!oneofDescriptor.isNil()) { + RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor; + OneofDescriptor ood = rubyOneofDescriptor.getDescriptor(); + + // Check to see if we set this through ruby + FieldDescriptor fieldDescriptor = oneofCases.get(ood); + + if (fieldDescriptor == null) { + // See if we set this from decoding a message + fieldDescriptor = builder.getOneofFieldDescriptor(ood); + + if (fieldDescriptor == null) { + return context.nil; + } else { + // Cache it so we don't need to do multiple checks next time + oneofCases.put(ood, fieldDescriptor); + return runtime.newSymbol(fieldDescriptor.getName()); + } } else { - return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK); + return runtime.newSymbol(fieldDescriptor.getName()); } - } + } - private boolean isWrappable(FieldDescriptor fieldDescriptor) { - if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE) return false; + // If we find a field return its value + FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName); - return isWrapper(fieldDescriptor.getMessageType()); - } + if (fieldDescriptor != null) { + return getFieldInternal(context, fieldDescriptor); + } - private static boolean isWrapper(Descriptor messageDescriptor) { - switch(messageDescriptor.getFullName()) { - case "google.protobuf.DoubleValue": - case "google.protobuf.FloatValue": - case "google.protobuf.Int64Value": - case "google.protobuf.UInt64Value": - case "google.protobuf.Int32Value": - case "google.protobuf.UInt32Value": - case "google.protobuf.BoolValue": - case "google.protobuf.StringValue": - case "google.protobuf.BytesValue": - return true; - default: - return false; + if (methodName.startsWith(CLEAR_PREFIX)) { + methodName = methodName.substring(6); + oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName)); + if (!oneofDescriptor.isNil()) { + fieldDescriptor = oneofCases.get(((RubyOneofDescriptor) oneofDescriptor).getDescriptor()); + if (fieldDescriptor == null) { + // Clearing an already cleared oneof; return here to avoid NoMethodError. + return context.nil; + } + } + + if (fieldDescriptor == null) { + fieldDescriptor = descriptor.findFieldByName(methodName); + } + + if (fieldDescriptor != null) { + return clearFieldInternal(context, fieldDescriptor); + } + + } else if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) { + methodName = + methodName.substring( + 4, methodName.length() - 1); // Trim "has_" and "?" off the field name + oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName)); + if (!oneofDescriptor.isNil()) { + RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor; + return oneofCases.containsKey(rubyOneofDescriptor.getDescriptor()) + ? runtime.getTrue() + : runtime.getFalse(); + } + + fieldDescriptor = descriptor.findFieldByName(methodName); + + if (fieldDescriptor != null + && (!proto3 + || fieldDescriptor.getContainingOneof() == null + || fieldDescriptor.getContainingOneof().isSynthetic()) + && fieldDescriptor.hasPresence()) { + return fields.containsKey(fieldDescriptor) ? runtime.getTrue() : runtime.getFalse(); + } + + } else if (methodName.endsWith(AS_VALUE_SUFFIX)) { + methodName = methodName.substring(0, methodName.length() - 9); + fieldDescriptor = descriptor.findFieldByName(methodName); + + if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { + IRubyObject value = getFieldInternal(context, fieldDescriptor); + + if (!value.isNil() && value instanceof RubyMessage) { + return ((RubyMessage) value).index(context, runtime.newString("value")); + } + + return value; + } + + } else if (methodName.endsWith(CONST_SUFFIX)) { + methodName = methodName.substring(0, methodName.length() - 6); + fieldDescriptor = descriptor.findFieldByName(methodName); + if (fieldDescriptor != null && fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) { + IRubyObject enumValue = getFieldInternal(context, fieldDescriptor); + + if (!enumValue.isNil()) { + EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType(); + if (enumValue instanceof RubyRepeatedField) { + RubyArray values = (RubyArray) ((RubyRepeatedField) enumValue).toArray(context); + RubyArray retValues = runtime.newArray(values.getLength()); + for (int i = 0; i < values.getLength(); i++) { + String val = values.eltInternal(i).toString(); + retValues.store( + (long) i, runtime.newFixnum(enumDescriptor.findValueByName(val).getNumber())); + } + return retValues; + } + + return runtime.newFixnum( + enumDescriptor.findValueByName(enumValue.asJavaString()).getNumber()); + } + } + } + + } else if (args.length == 2 && methodName.endsWith(Utils.EQUAL_SIGN)) { + + methodName = methodName.substring(0, methodName.length() - 1); // Trim equals sign + FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName); + if (fieldDescriptor != null) { + return setFieldInternal(context, fieldDescriptor, args[1]); + } + + IRubyObject oneofDescriptor = + rubyDescriptor.lookupOneof(context, RubyString.newString(context.runtime, methodName)); + if (!oneofDescriptor.isNil()) { + throw runtime.newRuntimeError("Oneof accessors are read-only."); + } + + if (methodName.endsWith(AS_VALUE_SUFFIX)) { + methodName = methodName.substring(0, methodName.length() - 9); + + fieldDescriptor = descriptor.findFieldByName(methodName); + + if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { + if (args[1].isNil()) { + return setFieldInternal(context, fieldDescriptor, args[1]); + } + + RubyClass typeClass = + (RubyClass) + ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)) + .msgclass(context); + RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK); + msg.indexSet(context, runtime.newString("value"), args[1]); + return setFieldInternal(context, fieldDescriptor, msg); + } } } - private void validateMessageType(ThreadContext context, FieldDescriptor fieldDescriptor, String methodName) { - if (descriptor != fieldDescriptor.getContainingType()) { - throw Utils.createTypeError(context, methodName + " method called on wrong message type"); - } + return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK); + } + + /** + * 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); + for (FieldDescriptor fieldDescriptor : this.descriptor.getFields()) { + if (fieldDescriptor.isRepeated()) { + dup.fields.put(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor)); + } else if (fields.containsKey(fieldDescriptor)) { + dup.setFieldInternal(context, fieldDescriptor, fields.get(fieldDescriptor)); + } else if (this.builder.hasField(fieldDescriptor)) { + dup.fields.put( + fieldDescriptor, + wrapField(context, fieldDescriptor, this.builder.getField(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, options = {}) => bytes + * + * Encodes the given message object to its serialized form in protocol buffers + * wire format. + * @param options [Hash] options for the encoder + * recursion_limit: set to maximum encoding depth for message (default is 64) + */ + @JRubyMethod(required = 1, optional = 1, meta = true) + public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject[] args) { + if (recv != args[0].getMetaClass()) { + throw context.runtime.newArgumentError( + "Tried to encode a " + args[0].getMetaClass() + " message with " + recv); + } + RubyMessage message = (RubyMessage) args[0]; + int recursionLimitInt = SINK_MAXIMUM_NESTING; + + if (args.length > 1) { + RubyHash options = (RubyHash) args[1]; + IRubyObject recursionLimit = options.fastARef(context.runtime.newSymbol("recursion_limit")); + + if (recursionLimit != null) { + recursionLimitInt = ((RubyNumeric) recursionLimit).getIntValue(); + } + } + return context.runtime.newString( + new ByteList(message.build(context, 0, recursionLimitInt).toByteArray())); + } + + /* + * call-seq: + * MessageClass.decode(data, options = {}) => message + * + * Decodes the given data (as a string containing bytes in protocol buffers wire + * format) under the interpretation given by this message class's definition + * and returns a message object with the corresponding field values. + * @param options [Hash] options for the decoder + * recursion_limit: set to maximum decoding depth for message (default is 100) + */ + @JRubyMethod(required = 1, optional = 1, meta = true) + public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyObject[] args) { + IRubyObject data = args[0]; + byte[] bin = data.convertToString().getBytes(); + CodedInputStream input = CodedInputStream.newInstance(bin); + RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK); + + if (args.length == 2) { + if (!(args[1] instanceof RubyHash)) { + throw context.runtime.newArgumentError("Expected hash arguments."); + } + + IRubyObject recursionLimit = + ((RubyHash) args[1]).fastARef(context.runtime.newSymbol("recursion_limit")); + if (recursionLimit != null) { + input.setRecursionLimit(((RubyNumeric) recursionLimit).getIntValue()); + } } - private static RubyClass parseErrorClass; + try { + ret.builder.mergeFrom(input); + } catch (Exception e) { + throw RaiseException.from( + context.runtime, + (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), + e.getMessage()); + } - private static final String AS_VALUE_SUFFIX = "_as_value"; - private static final String CLEAR_PREFIX = "clear_"; - private static final String CONST_SUFFIX = "_const"; - private static final String HAS_PREFIX = "has_"; - private static final String QUESTION_MARK = "?"; - private static final int SINK_MAXIMUM_NESTING = 64; + if (!ret.proto3) { + // Need to reset unknown values in repeated enum fields + ret.builder + .getUnknownFields() + .asMap() + .forEach( + (i, values) -> { + FieldDescriptor fd = ret.builder.getDescriptorForType().findFieldByNumber(i); + if (fd != null && fd.isRepeated() && fd.getType() == FieldDescriptor.Type.ENUM) { + EnumDescriptor ed = fd.getEnumType(); + values + .getVarintList() + .forEach( + value -> { + ret.builder.addRepeatedField( + fd, ed.findValueByNumberCreatingIfUnknown(value.intValue())); + }); + } + }); + } - private Descriptor descriptor; - private DynamicMessage.Builder builder; - private Map fields; - private Map oneofCases; - private RubyClass cRepeatedField; - private RubyClass cMap; - private boolean ignoreUnknownFieldsOnInit = false; - private boolean proto3; + return ret; + } + + /* + * call-seq: + * MessageClass.encode_json(msg, options = {}) => json_string + * + * Encodes the given message object into its serialized JSON representation. + * @param options [Hash] options for the decoder + * preserve_proto_fieldnames: set true to use original fieldnames (default is to camelCase) + * emit_defaults: set true to emit 0/false values (default is to omit them) + */ + @JRubyMethod(name = "encode_json", required = 1, optional = 1, meta = true) + public static IRubyObject encodeJson( + ThreadContext context, IRubyObject recv, IRubyObject[] args) { + Ruby runtime = context.runtime; + RubyMessage message = (RubyMessage) args[0]; + JsonFormat.Printer printer = JsonFormat.printer().omittingInsignificantWhitespace(); + String result; + + if (args.length > 1) { + RubyHash options; + if (args[1] instanceof RubyHash) { + options = (RubyHash) args[1]; + } else if (args[1].respondsTo("to_h")) { + options = (RubyHash) args[1].callMethod(context, "to_h"); + } else { + throw runtime.newArgumentError("Expected hash arguments."); + } + + IRubyObject emitDefaults = options.fastARef(runtime.newSymbol("emit_defaults")); + IRubyObject preserveNames = options.fastARef(runtime.newSymbol("preserve_proto_fieldnames")); + + if (emitDefaults != null && emitDefaults.isTrue()) { + printer = printer.includingDefaultValueFields(); + } + + if (preserveNames != null && preserveNames.isTrue()) { + printer = printer.preservingProtoFieldNames(); + } + } + printer = + printer.usingTypeRegistry( + JsonFormat.TypeRegistry.newBuilder().add(message.descriptor).build()); + + try { + result = printer.print(message.build(context, 0, SINK_MAXIMUM_NESTING)); + } catch (InvalidProtocolBufferException e) { + throw runtime.newRuntimeError(e.getMessage()); + } catch (IllegalArgumentException e) { + throw createParseError(context, e.getMessage()); + } + + return runtime.newString(result); + } + + /* + * call-seq: + * MessageClass.decode_json(data, options = {}) => message + * + * Decodes the given data (as a string containing bytes in protocol buffers wire + * format) under the interpretation given by this message class's definition + * and returns a message object with the corresponding field values. + * + * @param options [Hash] options for the decoder + * ignore_unknown_fields: set true to ignore unknown fields (default is to + * raise an error) + */ + @JRubyMethod(name = "decode_json", required = 1, optional = 1, meta = true) + public static IRubyObject decodeJson( + ThreadContext context, IRubyObject recv, IRubyObject[] args) { + Ruby runtime = context.runtime; + boolean ignoreUnknownFields = false; + IRubyObject data = args[0]; + JsonFormat.Parser parser = JsonFormat.parser(); + + if (args.length == 2) { + if (!(args[1] instanceof RubyHash)) { + throw runtime.newArgumentError("Expected hash arguments."); + } + + IRubyObject ignoreSetting = + ((RubyHash) args[1]).fastARef(runtime.newSymbol("ignore_unknown_fields")); + if (ignoreSetting != null && ignoreSetting.isTrue()) { + parser = parser.ignoringUnknownFields(); + } + } + + if (!(data instanceof RubyString)) { + throw runtime.newArgumentError("Expected string for JSON data."); + } + + RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK); + parser = + parser.usingTypeRegistry(JsonFormat.TypeRegistry.newBuilder().add(ret.descriptor).build()); + + try { + parser.merge(data.asJavaString(), ret.builder); + } catch (InvalidProtocolBufferException e) { + throw createParseError(context, e.getMessage().replace("Cannot find", "No such")); + } + + if (isWrapper(ret.descriptor)) { + throw runtime.newRuntimeError( + "Parsing a wrapper type from JSON at the top level does not work."); + } + + return ret; + } + + @JRubyMethod(name = "to_h") + public IRubyObject toHash(ThreadContext context) { + Ruby runtime = context.runtime; + RubyHash ret = RubyHash.newHash(runtime); + for (FieldDescriptor fdef : this.descriptor.getFields()) { + IRubyObject value = getFieldInternal(context, fdef, proto3); + + if (!value.isNil()) { + if (fdef.isRepeated() && !fdef.isMapField()) { + if (!proto3 && ((RubyRepeatedField) value).size() == 0) + continue; // Don't output empty repeated fields for proto2 + if (fdef.getType() != FieldDescriptor.Type.MESSAGE) { + value = Helpers.invoke(context, value, "to_a"); + } else { + RubyArray ary = value.convertToArray(); + for (int i = 0; i < ary.size(); i++) { + IRubyObject submsg = Helpers.invoke(context, ary.eltInternal(i), "to_h"); + ary.eltInternalSet(i, submsg); + } + + value = ary.to_ary(); + } + } else if (value.respondsTo("to_h")) { + value = Helpers.invoke(context, value, "to_h"); + } else if (value.respondsTo("to_a")) { + value = Helpers.invoke(context, value, "to_a"); + } + } + if (proto3 || !value.isNil()) { + ret.fastASet(runtime.newSymbol(fdef.getName()), value); + } + } + return ret; + } + + protected DynamicMessage build(ThreadContext context, int depth, int recursionLimit) { + if (depth >= recursionLimit) { + throw context.runtime.newRuntimeError("Recursion limit exceeded during encoding."); + } + + RubySymbol typeBytesSymbol = RubySymbol.newSymbol(context.runtime, "TYPE_BYTES"); + + // Handle the typical case where the fields.keySet contain the fieldDescriptors + for (FieldDescriptor fieldDescriptor : fields.keySet()) { + IRubyObject value = fields.get(fieldDescriptor); + + if (value instanceof RubyMap) { + builder.clearField(fieldDescriptor); + RubyDescriptor mapDescriptor = + (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); + for (DynamicMessage kv : + ((RubyMap) value).build(context, mapDescriptor, depth, recursionLimit)) { + builder.addRepeatedField(fieldDescriptor, kv); + } + + } else if (value instanceof RubyRepeatedField) { + RubyRepeatedField repeatedField = (RubyRepeatedField) value; + + builder.clearField(fieldDescriptor); + for (int i = 0; i < repeatedField.size(); i++) { + Object item = + convert( + context, + fieldDescriptor, + repeatedField.get(i), + depth, + recursionLimit, + /*isDefaultValueForBytes*/ false); + builder.addRepeatedField(fieldDescriptor, item); + } + + } else if (!value.isNil()) { + /** + * Detect the special case where default_value strings are provided for byte fields. If so, + * disable normal string encoding behavior within convert. For a more detailed explanation + * of other possible workarounds, see the comments above {@code + * com.google.protobuf.Internal#stringDefaultValue() stringDefaultValue}. + */ + boolean isDefaultStringForBytes = false; + if (DEFAULT_VALUE.equals(fieldDescriptor.getFullName())) { + FieldDescriptor enumFieldDescriptorForType = + this.builder.getDescriptorForType().findFieldByName(TYPE); + if (typeBytesSymbol.equals(fields.get(enumFieldDescriptorForType))) { + isDefaultStringForBytes = true; + } + } + builder.setField( + fieldDescriptor, + convert( + context, fieldDescriptor, value, depth, recursionLimit, isDefaultStringForBytes)); + } + } + + // Handle cases where {@code fields} doesn't contain the value until after getFieldInternal + // is called - typical of a deserialized message. Skip non-maps and descriptors that already + // have an entry in {@code fields}. + for (FieldDescriptor fieldDescriptor : descriptor.getFields()) { + if (!fieldDescriptor.isMapField()) { + continue; + } + IRubyObject value = fields.get(fieldDescriptor); + if (value != null) { + continue; + } + value = getFieldInternal(context, fieldDescriptor); + if (value instanceof RubyMap) { + builder.clearField(fieldDescriptor); + RubyDescriptor mapDescriptor = + (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); + for (DynamicMessage kv : + ((RubyMap) value).build(context, mapDescriptor, depth, recursionLimit)) { + builder.addRepeatedField(fieldDescriptor, kv); + } + } + } + return builder.build(); + } + + // Internal use only, called by Google::Protobuf.deep_copy + protected IRubyObject deepCopy(ThreadContext context) { + RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK); + for (FieldDescriptor fdef : descriptor.getFields()) { + if (fdef.isRepeated()) { + copy.fields.put(fdef, this.getRepeatedField(context, fdef).deepCopy(context)); + } else if (fields.containsKey(fdef)) { + copy.setFieldInternal(context, fdef, fields.get(fdef)); + } else if (builder.hasField(fdef)) { + copy.fields.put(fdef, wrapField(context, fdef, builder.getField(fdef))); + } + } + return copy; + } + + protected IRubyObject clearField(ThreadContext context, FieldDescriptor fieldDescriptor) { + validateMessageType(context, fieldDescriptor, "clear"); + return clearFieldInternal(context, fieldDescriptor); + } + + protected void discardUnknownFields(ThreadContext context) { + discardUnknownFields(context, builder); + } + + protected IRubyObject getField(ThreadContext context, FieldDescriptor fieldDescriptor) { + validateMessageType(context, fieldDescriptor, "get"); + return getFieldInternal(context, fieldDescriptor); + } + + protected IRubyObject hasField(ThreadContext context, FieldDescriptor fieldDescriptor) { + validateMessageType(context, fieldDescriptor, "has?"); + if (!fieldDescriptor.hasPresence()) { + throw context.runtime.newArgumentError("does not track presence"); + } + return fields.containsKey(fieldDescriptor) + ? context.runtime.getTrue() + : context.runtime.getFalse(); + } + + protected IRubyObject setField( + ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) { + validateMessageType(context, fieldDescriptor, "set"); + return setFieldInternal(context, fieldDescriptor, value); + } + + private RubyRepeatedField getRepeatedField( + ThreadContext context, FieldDescriptor fieldDescriptor) { + if (fields.containsKey(fieldDescriptor)) { + return (RubyRepeatedField) fields.get(fieldDescriptor); + } + int count = this.builder.getRepeatedFieldCount(fieldDescriptor); + RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor); + for (int i = 0; i < count; i++) { + ret.push( + context, + new IRubyObject[] { + wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i)) + }); + } + fields.put(fieldDescriptor, ret); + return ret; + } + + private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) { + this.builder.mergeFrom(dynamicMessage); + return this; + } + + private IRubyObject clearFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) { + OneofDescriptor ood = fieldDescriptor.getContainingOneof(); + if (ood != null) oneofCases.remove(ood); + fields.remove(fieldDescriptor); + builder.clearField(fieldDescriptor); + return context.nil; + } + + private void discardUnknownFields(ThreadContext context, Message.Builder messageBuilder) { + messageBuilder.setUnknownFields(UnknownFieldSet.getDefaultInstance()); + messageBuilder + .getAllFields() + .forEach( + (fd, value) -> { + if (fd.getType() == FieldDescriptor.Type.MESSAGE) { + if (fd.isRepeated()) { + messageBuilder.clearField(fd); + ((List) value) + .forEach( + (val) -> { + Message.Builder submessageBuilder = ((DynamicMessage) val).toBuilder(); + discardUnknownFields(context, submessageBuilder); + messageBuilder.addRepeatedField(fd, submessageBuilder.build()); + }); + } else { + Message.Builder submessageBuilder = ((DynamicMessage) value).toBuilder(); + discardUnknownFields(context, submessageBuilder); + messageBuilder.setField(fd, submessageBuilder.build()); + } + } + }); + } + + private FieldDescriptor findField(ThreadContext context, IRubyObject fieldName) { + return findField(context, fieldName, false); + } + + private FieldDescriptor findField( + ThreadContext context, IRubyObject fieldName, boolean ignoreUnknownField) { + String nameStr = fieldName.asJavaString(); + FieldDescriptor ret = this.descriptor.findFieldByName(nameStr); + if (ret == null && !ignoreUnknownField) { + throw context.runtime.newArgumentError("field " + fieldName.asJavaString() + " is not found"); + } + return ret; + } + + // convert a ruby object to protobuf type, skip type check since it is checked on the way in + private Object convert( + ThreadContext context, + FieldDescriptor fieldDescriptor, + IRubyObject value, + int depth, + int recursionLimit, + boolean isDefaultStringForBytes) { + Object val = null; + switch (fieldDescriptor.getType()) { + case INT32: + case SFIXED32: + case SINT32: + val = RubyNumeric.num2int(value); + break; + case INT64: + case SFIXED64: + case SINT64: + val = RubyNumeric.num2long(value); + break; + case FIXED32: + case UINT32: + val = Utils.num2uint(value); + break; + case FIXED64: + case UINT64: + val = Utils.num2ulong(context.runtime, value); + break; + case FLOAT: + val = (float) RubyNumeric.num2dbl(value); + break; + case DOUBLE: + val = (double) RubyNumeric.num2dbl(value); + break; + case BOOL: + val = value.isTrue(); + break; + case BYTES: + val = ByteString.copyFrom(((RubyString) value).getBytes()); + break; + case STRING: + if (isDefaultStringForBytes) { + val = ((RubyString) value).getByteList().toString(); + } else { + val = value.asJavaString(); + } + break; + case MESSAGE: + val = ((RubyMessage) value).build(context, depth + 1, recursionLimit); + break; + case ENUM: + EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType(); + if (Utils.isRubyNum(value)) { + val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); + } else { + val = enumDescriptor.findValueByName(value.asJavaString()); + } + break; + default: + break; + } + + return val; + } + + private static RaiseException createParseError(ThreadContext context, String message) { + if (parseErrorClass == null) { + parseErrorClass = + (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"); + } + return RaiseException.from(context.runtime, parseErrorClass, message); + } + + private IRubyObject wrapField( + ThreadContext context, FieldDescriptor fieldDescriptor, Object value) { + return wrapField(context, fieldDescriptor, value, false); + } + + private IRubyObject wrapField( + ThreadContext context, FieldDescriptor fieldDescriptor, Object value, boolean encodeBytes) { + if (value == null) { + return context.runtime.getNil(); + } + Ruby runtime = context.runtime; + + switch (fieldDescriptor.getType()) { + case INT32: + case INT64: + case FIXED32: + case SINT32: + case FIXED64: + case SINT64: + case SFIXED64: + case SFIXED32: + case UINT32: + case UINT64: + case FLOAT: + case DOUBLE: + case BOOL: + case BYTES: + case STRING: + return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value, encodeBytes); + 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: + EnumValueDescriptor enumValueDescriptor = (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, FieldDescriptor fieldDescriptor) { + IRubyObject typeClass = context.runtime.getNilClass(); + IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor); + FieldDescriptor.Type type = fieldDescriptor.getType(); + + if (type == FieldDescriptor.Type.MESSAGE) { + typeClass = ((RubyDescriptor) descriptor).msgclass(context); + + } else if (type == FieldDescriptor.Type.ENUM) { + typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context); + } + + RubyRepeatedField field = + new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass); + field.setName(fieldDescriptor.getName()); + + return field; + } + + private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) { + return getFieldInternal(context, fieldDescriptor, true); + } + + private IRubyObject getFieldInternal( + ThreadContext context, FieldDescriptor fieldDescriptor, boolean returnDefaults) { + OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof(); + if (oneofDescriptor != null) { + if (oneofCases.get(oneofDescriptor) == fieldDescriptor) { + IRubyObject value = fields.get(fieldDescriptor); + if (value == null) { + FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor); + if (oneofCase != null) { + Object builderValue = builder.getField(oneofCase); + if (builderValue != null) { + boolean encodeBytes = + oneofCase.hasDefaultValue() && builderValue.equals(oneofCase.getDefaultValue()); + value = wrapField(context, oneofCase, builderValue, encodeBytes); + } + } + if (value == null) { + return context.nil; + } else { + return value; + } + } else { + return value; + } + } else { + FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor); + if (oneofCase != fieldDescriptor) { + if (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE || !returnDefaults) { + return context.nil; + } else { + return wrapField(context, fieldDescriptor, fieldDescriptor.getDefaultValue(), true); + } + } + if (returnDefaults || builder.hasField(fieldDescriptor)) { + Object rawValue = builder.getField(oneofCase); + boolean encodeBytes = + oneofCase.hasDefaultValue() && rawValue.equals(oneofCase.getDefaultValue()); + IRubyObject value = wrapField(context, oneofCase, rawValue, encodeBytes); + fields.put(fieldDescriptor, value); + return value; + } else { + return context.nil; + } + } + } + + if (fieldDescriptor.isMapField()) { + RubyMap map = (RubyMap) fields.get(fieldDescriptor); + if (map == null) { + map = newMapForField(context, fieldDescriptor); + int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor); + FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1); + 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)); + } + fields.put(fieldDescriptor, map); + } + return map; + } + + if (fieldDescriptor.isRepeated()) { + return getRepeatedField(context, fieldDescriptor); + } + + if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE + || builder.hasField(fieldDescriptor) + || fields.containsKey(fieldDescriptor)) { + if (fields.containsKey(fieldDescriptor)) { + return fields.get(fieldDescriptor); + } else if (returnDefaults || builder.hasField(fieldDescriptor)) { + Object rawValue = builder.getField(fieldDescriptor); + boolean encodeBytes = + fieldDescriptor.hasDefaultValue() && rawValue.equals(fieldDescriptor.getDefaultValue()); + IRubyObject value = wrapField(context, fieldDescriptor, rawValue, encodeBytes); + if (builder.hasField(fieldDescriptor)) { + fields.put(fieldDescriptor, value); + } + return value; + } + } + return context.nil; + } + + private IRubyObject setFieldInternal( + ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) { + testFrozen("can't modify frozen " + getMetaClass()); + + if (fieldDescriptor.isMapField()) { + if (!(value instanceof RubyMap)) { + throw Utils.createTypeError(context, "Expected Map instance"); + } + RubyMap thisMap = (RubyMap) getFieldInternal(context, fieldDescriptor); + thisMap.mergeIntoSelf(context, value); + + } else if (fieldDescriptor.isRepeated()) { + if (value instanceof RubyRepeatedField) { + fields.put(fieldDescriptor, value); + } else { + throw Utils.createTypeError(context, "Expected repeated field array"); + } + + } else { + boolean addValue = true; + FieldDescriptor.Type fieldType = fieldDescriptor.getType(); + OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof(); + + // Determine the typeclass, if any + IRubyObject typeClass = context.runtime.getObject(); + if (fieldType == FieldDescriptor.Type.MESSAGE) { + typeClass = + ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context); + if (value.isNil()) { + addValue = false; + } + } else if (fieldType == FieldDescriptor.Type.ENUM) { + typeClass = + ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor)) + .enummodule(context); + value = enumToSymbol(context, fieldDescriptor.getEnumType(), value); + } + + if (oneofDescriptor != null) { + FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor); + + // Remove the existing field if we are setting a different field in the Oneof + if (oneofCase != null && oneofCase != fieldDescriptor) { + fields.remove(oneofCase); + } + + // Keep track of what Oneofs are set + if (value.isNil()) { + oneofCases.remove(oneofDescriptor); + if (!oneofDescriptor.isSynthetic()) { + addValue = false; + } + } else { + oneofCases.put(oneofDescriptor, fieldDescriptor); + } + } + + if (addValue) { + value = + Utils.checkType( + context, fieldType, fieldDescriptor.getName(), value, (RubyModule) typeClass); + fields.put(fieldDescriptor, value); + } else { + fields.remove(fieldDescriptor); + } + } + return context.nil; + } + + private IRubyObject getDescriptorForField( + ThreadContext context, FieldDescriptor fieldDescriptor) { + RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); + RubyFieldDescriptor fd = + (RubyFieldDescriptor) + thisRbDescriptor.lookup(context, context.runtime.newString(fieldDescriptor.getName())); + return fd.getSubtype(context); + } + + private IRubyObject enumToSymbol( + ThreadContext context, EnumDescriptor enumDescriptor, IRubyObject value) { + if (value instanceof RubySymbol) { + return (RubySymbol) value; + } else if (Utils.isRubyNum(value)) { + EnumValueDescriptor enumValue = + enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); + if (enumValue.getIndex() != -1) { + return context.runtime.newSymbol(enumValue.getName()); + } else { + return value; + } + } else if (value instanceof RubyString) { + return ((RubyString) value).intern(); + } + + return context.runtime.newSymbol("UNKNOWN"); + } + + private RubyRepeatedField rubyToRepeatedField( + ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) { + RubyArray arr = value.convertToArray(); + RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor); + IRubyObject[] values = new IRubyObject[arr.size()]; + FieldDescriptor.Type fieldType = fieldDescriptor.getType(); + + RubyModule typeClass = null; + if (fieldType == FieldDescriptor.Type.MESSAGE) { + RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); + typeClass = (RubyModule) descriptor.msgclass(context); + } else if (fieldType == FieldDescriptor.Type.ENUM) { + RubyEnumDescriptor enumDescriptor = + (RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor); + typeClass = (RubyModule) enumDescriptor.enummodule(context); + } + + for (int i = 0; i < arr.size(); i++) { + IRubyObject item = arr.eltInternal(i); + if (item.isNil()) { + throw Utils.createTypeError(context, "nil message not allowed here."); + } + if (item instanceof RubyHash && typeClass != null) { + values[i] = ((RubyClass) typeClass).newInstance(context, item, Block.NULL_BLOCK); + } else { + if (fieldType == FieldDescriptor.Type.ENUM) { + item = enumToSymbol(context, fieldDescriptor.getEnumType(), item); + } + + values[i] = item; + } + } + repeatedField.push(context, values); + + return repeatedField; + } + + private RubyMap newMapForField(ThreadContext context, FieldDescriptor fieldDescriptor) { + RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); + FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1); + 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() == 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 if (valueField.getType() == FieldDescriptor.Type.ENUM) { + RubyFieldDescriptor rubyFieldDescriptor = + (RubyFieldDescriptor) mapDescriptor.lookup(context, context.runtime.newString("value")); + RubyEnumDescriptor rubyEnumDescriptor = + (RubyEnumDescriptor) rubyFieldDescriptor.getSubtype(context); + return (RubyMap) + cMap.newInstance( + context, + keyType, + valueType, + rubyEnumDescriptor.enummodule(context), + Block.NULL_BLOCK); + + } else { + return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK); + } + } + + private boolean isWrappable(FieldDescriptor fieldDescriptor) { + if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE) return false; + + return isWrapper(fieldDescriptor.getMessageType()); + } + + private static boolean isWrapper(Descriptor messageDescriptor) { + switch (messageDescriptor.getFullName()) { + case "google.protobuf.DoubleValue": + case "google.protobuf.FloatValue": + case "google.protobuf.Int64Value": + case "google.protobuf.UInt64Value": + case "google.protobuf.Int32Value": + case "google.protobuf.UInt32Value": + case "google.protobuf.BoolValue": + case "google.protobuf.StringValue": + case "google.protobuf.BytesValue": + return true; + default: + return false; + } + } + + private void validateMessageType( + ThreadContext context, FieldDescriptor fieldDescriptor, String methodName) { + if (descriptor != fieldDescriptor.getContainingType()) { + throw Utils.createTypeError(context, methodName + " method called on wrong message type"); + } + } + + private static RubyClass parseErrorClass; + + private static final String AS_VALUE_SUFFIX = "_as_value"; + private static final String CLEAR_PREFIX = "clear_"; + private static final String CONST_SUFFIX = "_const"; + private static final String HAS_PREFIX = "has_"; + private static final String QUESTION_MARK = "?"; + private static final int SINK_MAXIMUM_NESTING = 64; + + private Descriptor descriptor; + private DynamicMessage.Builder builder; + private Map fields; + private Map oneofCases; + private RubyClass cRepeatedField; + private RubyClass cMap; + private boolean ignoreUnknownFieldsOnInit = false; + private boolean proto3; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java index 6f2ebdb45..5ade98b7f 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java @@ -2,6 +2,10 @@ package com.google.protobuf.jruby; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; @@ -13,74 +17,76 @@ import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - @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) { + 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()); + } + }); + cRubyOneofDescriptor.defineAnnotatedMethods(RubyOneofDescriptor.class); + cRubyOneofDescriptor.includeModule(runtime.getEnumerable()); + } + + public RubyOneofDescriptor(Ruby ruby, RubyClass rubyClass) { + super(ruby, rubyClass); + fields = new ArrayList(); + } + + /* + * call-seq: + * OneofDescriptor.name => name + * + * Returns the name of this oneof. + */ + @JRubyMethod(name = "name") + public IRubyObject getName(ThreadContext context) { + return name; + } + + /* + * 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.nil; + } - public RubyOneofDescriptor(Ruby ruby, RubyClass rubyClass) { - super(ruby, rubyClass); - fields = new ArrayList(); + protected Collection getFields() { + return fields; + } + + protected OneofDescriptor getDescriptor() { + return descriptor; + } + + protected void setDescriptor( + ThreadContext context, + OneofDescriptor descriptor, + Map fieldCache) { + this.descriptor = descriptor; + this.name = context.runtime.newString(descriptor.getName()); + + for (FieldDescriptor fd : descriptor.getFields()) { + fields.add(fieldCache.get(fd)); } + } - /* - * call-seq: - * OneofDescriptor.name => name - * - * Returns the name of this oneof. - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - return name; - } - - /* - * 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.nil; - } - - protected Collection getFields() { - return fields; - } - - protected OneofDescriptor getDescriptor() { - return descriptor; - } - - protected void setDescriptor(ThreadContext context, OneofDescriptor descriptor, Map fieldCache) { - this.descriptor = descriptor; - this.name = context.runtime.newString(descriptor.getName()); - - for (FieldDescriptor fd : descriptor.getFields()) { - fields.add(fieldCache.get(fd)); - } - } - - private IRubyObject name; - private List fields; - private OneofDescriptor descriptor; + private IRubyObject name; + private List fields; + private OneofDescriptor descriptor; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java index 582c675b2..8d132be34 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java @@ -42,41 +42,42 @@ 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); - RubyModule mInternal = mProtobuf.defineModuleUnder("Internal"); - } + public static void createProtobuf(Ruby runtime) { + RubyModule mGoogle = runtime.getModule("Google"); + RubyModule mProtobuf = mGoogle.defineModuleUnder("Protobuf"); + mProtobuf.defineAnnotatedMethods(RubyProtobuf.class); + RubyModule mInternal = mProtobuf.defineModuleUnder("Internal"); + } - /* - * 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); - } + /* + * 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); } + } - /* - * call-seq: - * Google::Protobuf.discard_unknown(msg) - * - * Discard unknown fields in the given message object and recursively discard - * unknown fields in submessages. - */ - @JRubyMethod(name = "discard_unknown", meta = true) - public static IRubyObject discardUnknown(ThreadContext context, IRubyObject self, IRubyObject message) { - ((RubyMessage) message).discardUnknownFields(context); - return context.nil; - } + /* + * call-seq: + * Google::Protobuf.discard_unknown(msg) + * + * Discard unknown fields in the given message object and recursively discard + * unknown fields in submessages. + */ + @JRubyMethod(name = "discard_unknown", meta = true) + public static IRubyObject discardUnknown( + ThreadContext context, IRubyObject self, IRubyObject message) { + ((RubyMessage) message).discardUnknownFields(context); + return context.nil; + } } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java index 995171fc7..883d480c6 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java @@ -40,384 +40,391 @@ import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; -import java.util.Arrays; @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 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, 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 == FieldDescriptor.Type.MESSAGE || fieldType == 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) { + int arrIndex = normalizeArrayIndex(index); + value = Utils.checkType(context, fieldType, name, value, (RubyModule) typeClass); + IRubyObject defaultValue = defaultValue(context); + for (int i = this.storage.size(); i < arrIndex; i++) { + this.storage.set(i, defaultValue); + } + this.storage.set(arrIndex, value); + return context.runtime.getNil(); + } + + /* + * call-seq: + * RepeatedField.[](index) => value + * + * Accesses the element at the given index. Returns nil on out-of-bounds + */ + @JRubyMethod( + required = 1, + optional = 1, + name = {"at", "[]"}) + public IRubyObject index(ThreadContext context, IRubyObject[] args) { + if (args.length == 1) { + IRubyObject arg = args[0]; + if (Utils.isRubyNum(arg)) { + /* standard case */ + int arrIndex = normalizeArrayIndex(arg); + if (arrIndex < 0 || arrIndex >= this.storage.size()) { + return context.runtime.getNil(); + } + return this.storage.eltInternal(arrIndex); + } else if (arg instanceof RubyRange) { + RubyRange range = ((RubyRange) arg); + + int beg = RubyNumeric.num2int(range.first(context)); + int len = RubyNumeric.num2int(range.size(context)); + + if (len == 0) return context.runtime.newEmptyArray(); + + return this.storage.subseq(beg, len); + } + } + /* assume 2 arguments */ + int beg = RubyNumeric.num2int(args[0]); + int len = RubyNumeric.num2int(args[1]); + if (beg < 0) { + beg += this.storage.size(); + } + if (beg >= this.storage.size()) { + return context.runtime.getNil(); + } + return this.storage.subseq(beg, len); + } + + /* + * call-seq: + * RepeatedField.push(value) + * + * Adds a new element to the repeated field. + */ + @JRubyMethod( + name = {"push", "<<"}, + required = 1, + rest = true) + public IRubyObject push(ThreadContext context, IRubyObject[] args) { + for (int i = 0; i < args.length; i++) { + IRubyObject val = args[i]; + if (fieldType != FieldDescriptor.Type.MESSAGE || !val.isNil()) { + val = Utils.checkType(context, fieldType, name, val, (RubyModule) typeClass); + } + storage.add(val); } - public RubyRepeatedField(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - } + return this; + } - public RubyRepeatedField(Ruby runtime, RubyClass klazz, FieldDescriptor.Type fieldType, IRubyObject typeClass) { - this(runtime, klazz); - this.fieldType = fieldType; - this.storage = runtime.newArray(); - this.typeClass = typeClass; - } + /* + * private Ruby method used by RepeatedField.pop + */ + @JRubyMethod(visibility = org.jruby.runtime.Visibility.PRIVATE) + public IRubyObject pop_one(ThreadContext context) { + IRubyObject ret = this.storage.last(); + this.storage.remove(ret); + return ret; + } - @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 == FieldDescriptor.Type.MESSAGE - || fieldType == 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.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 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) { - int arrIndex = normalizeArrayIndex(index); - value = Utils.checkType(context, fieldType, name, value, (RubyModule) typeClass); - IRubyObject defaultValue = defaultValue(context); - for (int i = this.storage.size(); i < arrIndex; i++) { - this.storage.set(i, defaultValue); - } - this.storage.set(arrIndex, value); + /* + * call-seq: + * RepeatedField.clear + * + * Clears (removes all elements from) this repeated field. + */ + @JRubyMethod + public IRubyObject clear(ThreadContext context) { + this.storage.clear(); + return this; + } + + /* + * call-seq: + * RepeatedField.length + * + * Returns the length of this repeated field. + */ + @JRubyMethod(name = {"length", "size"}) + 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.concat(other) => self + * + * concats the passed in array to self. Returns a Ruby array. + */ + @JRubyMethod + public IRubyObject concat(ThreadContext context, IRubyObject list) { + if (list instanceof RubyArray) { + checkArrayElementType(context, (RubyArray) list); + this.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."); + this.storage.addAll((RubyArray) repeatedField.toArray(context)); + } + return this; + } + + /* + * 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 = this.storage.hashCode(); + 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 this; + } + + @JRubyMethod(name = {"to_ary", "to_a"}) + public IRubyObject toArray(ThreadContext context) { + 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); + dup.push(context, storage.toJavaArray()); + return dup; + } + + @JRubyMethod + public IRubyObject inspect() { + return storage.inspect(); + } + + // 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 == FieldDescriptor.Type.MESSAGE) { + copy.storage.add(((RubyMessage) value).deepCopy(context)); + } else { + copy.storage.add(value); + } + } + return copy; + } + + protected void setName(String name) { + this.name = name; + } + + 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; + case ENUM: + IRubyObject defaultEnumLoc = context.runtime.newFixnum(0); + return RubyEnum.lookup(context, typeClass, defaultEnumLoc); + default: return context.runtime.getNil(); } + return Utils.wrapPrimaryValue(context, fieldType, value); + } - /* - * call-seq: - * RepeatedField.[](index) => value - * - * Accesses the element at the given index. Returns nil on out-of-bounds - */ - @JRubyMethod(required=1, optional=1, name = {"at", "[]"}) - public IRubyObject index(ThreadContext context, IRubyObject[] args) { - if (args.length == 1){ - IRubyObject arg = args[0]; - if (Utils.isRubyNum(arg)) { - /* standard case */ - int arrIndex = normalizeArrayIndex(arg); - if (arrIndex < 0 || arrIndex >= this.storage.size()) { - return context.runtime.getNil(); - } - return this.storage.eltInternal(arrIndex); - } else if (arg instanceof RubyRange) { - RubyRange range = ((RubyRange) arg); - - int beg = RubyNumeric.num2int(range.first(context)); - int len = RubyNumeric.num2int(range.size(context)); - - if (len == 0) return context.runtime.newEmptyArray(); - - return this.storage.subseq(beg, len); - } - } - /* assume 2 arguments */ - int beg = RubyNumeric.num2int(args[0]); - int len = RubyNumeric.num2int(args[1]); - if (beg < 0) { - beg += this.storage.size(); - } - if (beg >= this.storage.size()) { - return context.runtime.getNil(); - } - return this.storage.subseq(beg, len); + private void checkArrayElementType(ThreadContext context, RubyArray arr) { + for (int i = 0; i < arr.getLength(); i++) { + Utils.checkType(context, fieldType, name, arr.eltInternal(i), (RubyModule) typeClass); } + } - /* - * call-seq: - * RepeatedField.push(value) - * - * Adds a new element to the repeated field. - */ - @JRubyMethod(name = {"push", "<<"}, required = 1, rest = true) - public IRubyObject push(ThreadContext context, IRubyObject[] args) { - for (int i = 0; i < args.length; i++) { - IRubyObject val = args[i]; - if (fieldType != FieldDescriptor.Type.MESSAGE || !val.isNil()) { - val = Utils.checkType(context, fieldType, name, val, (RubyModule) typeClass); - } - storage.add(val); - } - - return this; + private int normalizeArrayIndex(IRubyObject index) { + int arrIndex = RubyNumeric.num2int(index); + int arrSize = this.storage.size(); + if (arrIndex < 0 && arrSize > 0) { + arrIndex = arrSize + arrIndex; } + return arrIndex; + } - /* - * private Ruby method used by RepeatedField.pop - */ - @JRubyMethod(visibility = org.jruby.runtime.Visibility.PRIVATE) - public IRubyObject pop_one(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 this; - } - - /* - * call-seq: - * RepeatedField.clear - * - * Clears (removes all elements from) this repeated field. - */ - @JRubyMethod - public IRubyObject clear(ThreadContext context) { - this.storage.clear(); - return this; - } - - /* - * call-seq: - * RepeatedField.length - * - * Returns the length of this repeated field. - */ - @JRubyMethod(name = {"length", "size"}) - 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.concat(other) => self - * - * concats the passed in array to self. Returns a Ruby array. - */ - @JRubyMethod - public IRubyObject concat(ThreadContext context, IRubyObject list) { - if (list instanceof RubyArray) { - checkArrayElementType(context, (RubyArray) list); - this.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."); - this.storage.addAll((RubyArray) repeatedField.toArray(context)); - } - return this; - } - - /* - * 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 = this.storage.hashCode(); - 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 this; - } - - - @JRubyMethod(name = {"to_ary", "to_a"}) - public IRubyObject toArray(ThreadContext context) { - 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); - dup.push(context, storage.toJavaArray()); - return dup; - } - - @JRubyMethod - public IRubyObject inspect() { - return storage.inspect(); - } - - // 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 == FieldDescriptor.Type.MESSAGE) { - copy.storage.add(((RubyMessage) value).deepCopy(context)); - } else { - copy.storage.add(value); - } - } - return copy; - } - - protected void setName(String name) { - this.name = name; - } - - 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; - case ENUM: - IRubyObject defaultEnumLoc = context.runtime.newFixnum(0); - return RubyEnum.lookup(context, typeClass, defaultEnumLoc); - 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, name, arr.eltInternal(i), (RubyModule) typeClass); - } - } - - private int normalizeArrayIndex(IRubyObject index) { - int arrIndex = RubyNumeric.num2int(index); - int arrSize = this.storage.size(); - if (arrIndex < 0 && arrSize > 0) { - arrIndex = arrSize + arrIndex; - } - return arrIndex; - } - - private FieldDescriptor.Type fieldType; - private IRubyObject typeClass; - private RubyArray storage; - private String name; + private FieldDescriptor.Type fieldType; + private IRubyObject typeClass; + private RubyArray storage; + private String name; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java b/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java index b3f23c5d3..3c56cf86a 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java @@ -37,74 +37,53 @@ package com.google.protobuf.jruby; public final class SentinelOuterClass { private SentinelOuterClass() {} - public static void registerAllExtensions( - com.google.protobuf.ExtensionRegistry registry) { - } - public interface SentinelOrBuilder extends + + 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 { - /** - * optional int32 default_int32 = 1; - */ + /** optional int32 default_int32 = 1; */ int getDefaultInt32(); - /** - * optional int64 default_int64 = 2; - */ + /** optional int64 default_int64 = 2; */ long getDefaultInt64(); - /** - * optional uint32 default_unit32 = 3; - */ + /** optional uint32 default_unit32 = 3; */ int getDefaultUnit32(); - /** - * optional uint64 default_uint64 = 4; - */ + /** optional uint64 default_uint64 = 4; */ long getDefaultUint64(); - /** - * optional string default_string = 5; - */ + /** optional string default_string = 5; */ java.lang.String getDefaultString(); - /** - * optional string default_string = 5; - */ - com.google.protobuf.ByteString - getDefaultStringBytes(); + /** optional string default_string = 5; */ + com.google.protobuf.ByteString getDefaultStringBytes(); - /** - * optional bool default_bool = 6; - */ + /** optional bool default_bool = 6; */ boolean getDefaultBool(); - /** - * optional float default_float = 7; - */ + /** optional float default_float = 7; */ float getDefaultFloat(); - /** - * optional double default_double = 8; - */ + /** optional double default_double = 8; */ double getDefaultDouble(); - /** - * optional bytes default_bytes = 9; - */ + /** optional bytes default_bytes = 9; */ com.google.protobuf.ByteString getDefaultBytes(); } - /** - * Protobuf type {@code com.google.protobuf.jruby.Sentinel} - */ - public static final class Sentinel extends - com.google.protobuf.GeneratedMessage implements + /** 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; @@ -118,40 +97,42 @@ public final class SentinelOuterClass { } @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + 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; + + 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 + 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); + com.google.protobuf.jruby.SentinelOuterClass.Sentinel.class, + com.google.protobuf.jruby.SentinelOuterClass.Sentinel.Builder.class); } public static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - 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(); - } - }; + 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 getParserForType() { @@ -160,52 +141,41 @@ public final class SentinelOuterClass { public static final int DEFAULT_INT32_FIELD_NUMBER = 1; private int defaultInt32_; - /** - * optional int32 default_int32 = 1; - */ + /** optional int32 default_int32 = 1; */ public int getDefaultInt32() { return defaultInt32_; } public static final int DEFAULT_INT64_FIELD_NUMBER = 2; private long defaultInt64_; - /** - * optional int64 default_int64 = 2; - */ + /** optional int64 default_int64 = 2; */ public long getDefaultInt64() { return defaultInt64_; } public static final int DEFAULT_UNIT32_FIELD_NUMBER = 3; private int defaultUnit32_; - /** - * optional uint32 default_unit32 = 3; - */ + /** optional uint32 default_unit32 = 3; */ public int getDefaultUnit32() { return defaultUnit32_; } public static final int DEFAULT_UINT64_FIELD_NUMBER = 4; private long defaultUint64_; - /** - * optional uint64 default_uint64 = 4; - */ + /** optional uint64 default_uint64 = 4; */ public long getDefaultUint64() { return defaultUint64_; } public static final int DEFAULT_STRING_FIELD_NUMBER = 5; private java.lang.Object defaultString_; - /** - * optional string default_string = 5; - */ + /** optional string default_string = 5; */ 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; + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { defaultString_ = s; @@ -213,16 +183,12 @@ public final class SentinelOuterClass { return s; } } - /** - * optional string default_string = 5; - */ - public com.google.protobuf.ByteString - getDefaultStringBytes() { + /** optional string default_string = 5; */ + 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); + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); defaultString_ = b; return b; } else { @@ -232,36 +198,28 @@ public final class SentinelOuterClass { public static final int DEFAULT_BOOL_FIELD_NUMBER = 6; private boolean defaultBool_; - /** - * optional bool default_bool = 6; - */ + /** optional bool default_bool = 6; */ public boolean getDefaultBool() { return defaultBool_; } public static final int DEFAULT_FLOAT_FIELD_NUMBER = 7; private float defaultFloat_; - /** - * optional float default_float = 7; - */ + /** optional float default_float = 7; */ public float getDefaultFloat() { return defaultFloat_; } public static final int DEFAULT_DOUBLE_FIELD_NUMBER = 8; private double defaultDouble_; - /** - * optional double default_double = 8; - */ + /** optional double default_double = 8; */ public double getDefaultDouble() { return defaultDouble_; } public static final int DEFAULT_BYTES_FIELD_NUMBER = 9; private com.google.protobuf.ByteString defaultBytes_; - /** - * optional bytes default_bytes = 9; - */ + /** optional bytes default_bytes = 9; */ public com.google.protobuf.ByteString getDefaultBytes() { return defaultBytes_; } @@ -271,47 +229,52 @@ public final class SentinelOuterClass { 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) + 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 { + + 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) + 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 { + + 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) + 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 { + 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) @@ -319,36 +282,45 @@ public final class SentinelOuterClass { 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) { + 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); } + + public Builder toBuilder() { + return newBuilder(this); + } @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { + 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 implements + /** Protobuf type {@code com.google.protobuf.jruby.Sentinel} */ + public static final class Builder extends com.google.protobuf.GeneratedMessage.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; + 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 + 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); + com.google.protobuf.jruby.SentinelOuterClass.Sentinel.class, + com.google.protobuf.jruby.SentinelOuterClass.Sentinel.Builder.class); } // Construct using com.google.protobuf.jruby.SentinelOuterClass.Sentinel.newBuilder() @@ -356,15 +328,15 @@ public final class SentinelOuterClass { maybeForceBuilderInitialization(); } - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { + private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } + private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {} } + public Builder clear() { super.clear(); defaultInt32_ = 0; @@ -388,9 +360,9 @@ public final class SentinelOuterClass { 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.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() { @@ -406,7 +378,8 @@ public final class SentinelOuterClass { } public com.google.protobuf.jruby.SentinelOuterClass.Sentinel buildPartial() { - com.google.protobuf.jruby.SentinelOuterClass.Sentinel result = new com.google.protobuf.jruby.SentinelOuterClass.Sentinel(this); + com.google.protobuf.jruby.SentinelOuterClass.Sentinel result = + new com.google.protobuf.jruby.SentinelOuterClass.Sentinel(this); result.defaultInt32_ = defaultInt32_; result.defaultInt64_ = defaultInt64_; result.defaultUnit32_ = defaultUnit32_; @@ -420,26 +393,19 @@ public final class SentinelOuterClass { return result; } - - private int defaultInt32_ ; - /** - * optional int32 default_int32 = 1; - */ + private int defaultInt32_; + /** optional int32 default_int32 = 1; */ public int getDefaultInt32() { return defaultInt32_; } - /** - * optional int32 default_int32 = 1; - */ + /** optional int32 default_int32 = 1; */ public Builder setDefaultInt32(int value) { defaultInt32_ = value; onChanged(); return this; } - /** - * optional int32 default_int32 = 1; - */ + /** optional int32 default_int32 = 1; */ public Builder clearDefaultInt32() { defaultInt32_ = 0; @@ -447,25 +413,19 @@ public final class SentinelOuterClass { return this; } - private long defaultInt64_ ; - /** - * optional int64 default_int64 = 2; - */ + private long defaultInt64_; + /** optional int64 default_int64 = 2; */ public long getDefaultInt64() { return defaultInt64_; } - /** - * optional int64 default_int64 = 2; - */ + /** optional int64 default_int64 = 2; */ public Builder setDefaultInt64(long value) { defaultInt64_ = value; onChanged(); return this; } - /** - * optional int64 default_int64 = 2; - */ + /** optional int64 default_int64 = 2; */ public Builder clearDefaultInt64() { defaultInt64_ = 0L; @@ -473,25 +433,19 @@ public final class SentinelOuterClass { return this; } - private int defaultUnit32_ ; - /** - * optional uint32 default_unit32 = 3; - */ + private int defaultUnit32_; + /** optional uint32 default_unit32 = 3; */ public int getDefaultUnit32() { return defaultUnit32_; } - /** - * optional uint32 default_unit32 = 3; - */ + /** optional uint32 default_unit32 = 3; */ public Builder setDefaultUnit32(int value) { defaultUnit32_ = value; onChanged(); return this; } - /** - * optional uint32 default_unit32 = 3; - */ + /** optional uint32 default_unit32 = 3; */ public Builder clearDefaultUnit32() { defaultUnit32_ = 0; @@ -499,25 +453,19 @@ public final class SentinelOuterClass { return this; } - private long defaultUint64_ ; - /** - * optional uint64 default_uint64 = 4; - */ + private long defaultUint64_; + /** optional uint64 default_uint64 = 4; */ public long getDefaultUint64() { return defaultUint64_; } - /** - * optional uint64 default_uint64 = 4; - */ + /** optional uint64 default_uint64 = 4; */ public Builder setDefaultUint64(long value) { defaultUint64_ = value; onChanged(); return this; } - /** - * optional uint64 default_uint64 = 4; - */ + /** optional uint64 default_uint64 = 4; */ public Builder clearDefaultUint64() { defaultUint64_ = 0L; @@ -526,14 +474,11 @@ public final class SentinelOuterClass { } private java.lang.Object defaultString_ = ""; - /** - * optional string default_string = 5; - */ + /** optional string default_string = 5; */ 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; + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { defaultString_ = s; @@ -543,77 +488,59 @@ public final class SentinelOuterClass { return (java.lang.String) ref; } } - /** - * optional string default_string = 5; - */ - public com.google.protobuf.ByteString - getDefaultStringBytes() { + /** optional string default_string = 5; */ + 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); + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); defaultString_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } - /** - * optional string default_string = 5; - */ - public Builder setDefaultString( - java.lang.String value) { + /** optional string default_string = 5; */ + public Builder setDefaultString(java.lang.String value) { if (value == null) { - throw new NullPointerException(); - } + throw new NullPointerException(); + } defaultString_ = value; onChanged(); return this; } - /** - * optional string default_string = 5; - */ + /** optional string default_string = 5; */ public Builder clearDefaultString() { defaultString_ = getDefaultInstance().getDefaultString(); onChanged(); return this; } - /** - * optional string default_string = 5; - */ - public Builder setDefaultStringBytes( - com.google.protobuf.ByteString value) { + /** optional string default_string = 5; */ + public Builder setDefaultStringBytes(com.google.protobuf.ByteString value) { if (value == null) { - throw new NullPointerException(); - } + throw new NullPointerException(); + } defaultString_ = value; onChanged(); return this; } - private boolean defaultBool_ ; - /** - * optional bool default_bool = 6; - */ + private boolean defaultBool_; + /** optional bool default_bool = 6; */ public boolean getDefaultBool() { return defaultBool_; } - /** - * optional bool default_bool = 6; - */ + /** optional bool default_bool = 6; */ public Builder setDefaultBool(boolean value) { defaultBool_ = value; onChanged(); return this; } - /** - * optional bool default_bool = 6; - */ + /** optional bool default_bool = 6; */ public Builder clearDefaultBool() { defaultBool_ = false; @@ -621,25 +548,19 @@ public final class SentinelOuterClass { return this; } - private float defaultFloat_ ; - /** - * optional float default_float = 7; - */ + private float defaultFloat_; + /** optional float default_float = 7; */ public float getDefaultFloat() { return defaultFloat_; } - /** - * optional float default_float = 7; - */ + /** optional float default_float = 7; */ public Builder setDefaultFloat(float value) { defaultFloat_ = value; onChanged(); return this; } - /** - * optional float default_float = 7; - */ + /** optional float default_float = 7; */ public Builder clearDefaultFloat() { defaultFloat_ = 0F; @@ -647,25 +568,19 @@ public final class SentinelOuterClass { return this; } - private double defaultDouble_ ; - /** - * optional double default_double = 8; - */ + private double defaultDouble_; + /** optional double default_double = 8; */ public double getDefaultDouble() { return defaultDouble_; } - /** - * optional double default_double = 8; - */ + /** optional double default_double = 8; */ public Builder setDefaultDouble(double value) { defaultDouble_ = value; onChanged(); return this; } - /** - * optional double default_double = 8; - */ + /** optional double default_double = 8; */ public Builder clearDefaultDouble() { defaultDouble_ = 0D; @@ -674,33 +589,28 @@ public final class SentinelOuterClass { } private com.google.protobuf.ByteString defaultBytes_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes default_bytes = 9; - */ + /** optional bytes default_bytes = 9; */ public com.google.protobuf.ByteString getDefaultBytes() { return defaultBytes_; } - /** - * optional bytes default_bytes = 9; - */ + /** optional bytes default_bytes = 9; */ public Builder setDefaultBytes(com.google.protobuf.ByteString value) { if (value == null) { - throw new NullPointerException(); - } + throw new NullPointerException(); + } defaultBytes_ = value; onChanged(); return this; } - /** - * optional bytes default_bytes = 9; - */ + /** optional bytes default_bytes = 9; */ public Builder clearDefaultBytes() { defaultBytes_ = getDefaultInstance().getDefaultBytes(); onChanged(); return this; } + public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { return this; @@ -711,12 +621,13 @@ public final class SentinelOuterClass { 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 { + private static final com.google.protobuf.jruby.SentinelOuterClass.Sentinel defaultInstance; + + static { defaultInstance = new com.google.protobuf.jruby.SentinelOuterClass.Sentinel(); } @@ -727,49 +638,55 @@ public final class SentinelOuterClass { 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_descriptor; + private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable; - public static com.google.protobuf.Descriptors.FileDescriptor - getDescriptor() { + public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { return descriptor; } - private static com.google.protobuf.Descriptors.FileDescriptor - 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" + "\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() { + 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); + 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", }); + 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 index cd2758962..65de683b0 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/Utils.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/Utils.java @@ -35,6 +35,7 @@ package com.google.protobuf.jruby; import com.google.protobuf.ByteString; import com.google.protobuf.DescriptorProtos.FieldDescriptorProto; import com.google.protobuf.Descriptors.FieldDescriptor; +import java.math.BigInteger; import org.jcodings.specific.ASCIIEncoding; import org.jruby.*; import org.jruby.exceptions.RaiseException; @@ -44,302 +45,363 @@ import org.jruby.runtime.Helpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; -import java.math.BigInteger; - public class Utils { - public static FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) { - return FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase()); - } + public static FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) { + return FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase()); + } - public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptor.Type type) { - return fieldTypeToRuby(context, type.name()); - } + public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptor.Type type) { + return fieldTypeToRuby(context, type.name()); + } - public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptorProto.Type type) { - return fieldTypeToRuby(context, type.name()); - } + public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptorProto.Type type) { + return fieldTypeToRuby(context, type.name()); + } - private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) { + private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) { - return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase()); - } + return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase()); + } - public static IRubyObject checkType(ThreadContext context, FieldDescriptor.Type fieldType, - String fieldName, IRubyObject value, RubyModule typeClass) { - Ruby runtime = context.runtime; + public static IRubyObject checkType( + ThreadContext context, + FieldDescriptor.Type fieldType, + String fieldName, + IRubyObject value, + RubyModule typeClass) { + Ruby runtime = context.runtime; - switch(fieldType) { - case SFIXED32: - case SFIXED64: - case FIXED64: - case SINT64: - case SINT32: - case FIXED32: - case INT32: - case INT64: - case UINT32: - case UINT64: - if (!isRubyNum(value)) - throw createExpectedTypeError(context, "number", "integral", fieldName, value); + switch (fieldType) { + case SFIXED32: + case SFIXED64: + case FIXED64: + case SINT64: + case SINT32: + case FIXED32: + case INT32: + case INT64: + case UINT32: + case UINT64: + if (!isRubyNum(value)) + throw createExpectedTypeError(context, "number", "integral", fieldName, value); - if (value instanceof RubyFloat) { - double doubleVal = RubyNumeric.num2dbl(value); - if (Math.floor(doubleVal) != doubleVal) { - throw runtime.newRangeError("Non-integral floating point value assigned to integer field '" + fieldName + "' (given " + value.getMetaClass() + ")."); - } - } - if (fieldType == FieldDescriptor.Type.UINT32 || fieldType == FieldDescriptor.Type.UINT64 || - fieldType == FieldDescriptor.Type.FIXED32 || fieldType == FieldDescriptor.Type.FIXED64) { - if (((RubyNumeric) value).isNegative()) { - throw runtime.newRangeError("Assigning negative value to unsigned integer field '" + fieldName + "' (given " + value.getMetaClass() + ")."); - } - } - - switch(fieldType) { - case INT32: - RubyNumeric.num2int(value); - break; - case UINT32: - case FIXED32: - num2uint(value); - break; - case UINT64: - case FIXED64: - num2ulong(context.runtime, value); - break; - default: - RubyNumeric.num2long(value); - break; - } - break; - case FLOAT: - if (!isRubyNum(value)) - throw createExpectedTypeError(context, "number", "float", fieldName, value); - break; - case DOUBLE: - if (!isRubyNum(value)) - throw createExpectedTypeError(context, "number", "double", fieldName, value); - break; - case BOOL: - if (!(value instanceof RubyBoolean)) - throw createInvalidTypeError(context, "boolean", fieldName, value); - break; - case BYTES: - value = validateAndEncodeString(context, "bytes", fieldName, value, "Encoding::ASCII_8BIT"); - break; - case STRING: - value = validateAndEncodeString(context, "string", fieldName, symToString(value), "Encoding::UTF_8"); - break; - case MESSAGE: - if (value.getMetaClass() != typeClass) { - // See if we can convert the value before flagging it as invalid - String className = typeClass.getName(); - - if (className.equals("Google::Protobuf::Timestamp") && value instanceof RubyTime) { - RubyTime rt = (RubyTime) value; - RubyHash timestampArgs = - Helpers.constructHash(runtime, - runtime.newString("nanos"), rt.nsec(), false, - runtime.newString("seconds"), rt.to_i(), false); - return ((RubyClass) typeClass).newInstance(context, timestampArgs, Block.NULL_BLOCK); - - } else if (className.equals("Google::Protobuf::Duration") && value instanceof RubyNumeric) { - IRubyObject seconds; - if (value instanceof RubyFloat) { - seconds = ((RubyFloat) value).truncate(context); - } else if (value instanceof RubyRational) { - seconds = ((RubyRational) value).to_i(context); - } else if (value instanceof RubyBigDecimal) { - seconds = ((RubyBigDecimal) value).to_int(context); - } else { - seconds = ((RubyInteger) value).to_i(); - } - - IRubyObject nanos = ((RubyNumeric) value).remainder(context, RubyFixnum.one(runtime)); - if (nanos instanceof RubyFloat) { - nanos = ((RubyFloat) nanos).op_mul(context, 1000000000); - } else if (nanos instanceof RubyRational) { - nanos = ((RubyRational) nanos).op_mul(context, runtime.newFixnum(1000000000)); - } else if (nanos instanceof RubyBigDecimal) { - nanos = ((RubyBigDecimal) nanos).op_mul(context, runtime.newFixnum(1000000000)); - } else { - nanos = ((RubyInteger) nanos).op_mul(context, 1000000000); - } - - RubyHash durationArgs = - Helpers.constructHash(runtime, - runtime.newString("nanos"), ((RubyNumeric) nanos).round(context), false, - runtime.newString("seconds"), seconds, false); - return ((RubyClass) typeClass).newInstance(context, durationArgs, Block.NULL_BLOCK); - } - - // Not able to convert so flag as invalid - throw createTypeError(context, "Invalid type " + value.getMetaClass() + " to assign to submessage field '" + fieldName + "'."); - } - - break; - case ENUM: - boolean isValid = ((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR)).isValidValue(context, value); - if (!isValid) { - throw runtime.newRangeError("Unknown symbol value for enum field '" + fieldName + "'."); - } - break; - default: - break; - } - return value; - } - - public static IRubyObject wrapPrimaryValue(ThreadContext context, FieldDescriptor.Type fieldType, Object value) { - return wrapPrimaryValue(context, fieldType, value, false); - } - - public static IRubyObject wrapPrimaryValue(ThreadContext context, FieldDescriptor.Type fieldType, Object value, boolean encodeBytes) { - Ruby runtime = context.runtime; - switch (fieldType) { - case INT32: - case SFIXED32: - case SINT32: - return runtime.newFixnum((Integer) value); - case SFIXED64: - case SINT64: - case INT64: - return runtime.newFixnum((Long) value); - case FIXED32: - case UINT32: - return runtime.newFixnum(((Integer) value) & (-1l >>> 32)); - case FIXED64: - 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: { - IRubyObject wrapped = encodeBytes ? - RubyString.newString(runtime, ((ByteString) value).toStringUtf8(), ASCIIEncoding.INSTANCE) : - RubyString.newString(runtime, ((ByteString) value).toByteArray()); - wrapped.setFrozen(true); - return wrapped; - } - case STRING: { - IRubyObject wrapped = runtime.newString(value.toString()); - wrapped.setFrozen(true); - return wrapped; - } - 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); + double doubleVal = RubyNumeric.num2dbl(value); + if (Math.floor(doubleVal) != doubleVal) { + throw runtime.newRangeError( + "Non-integral floating point value assigned to integer field '" + + fieldName + + "' (given " + + value.getMetaClass() + + ")."); + } } - } - - /* - * Helper to make it easier to support symbols being passed instead of strings - */ - public static IRubyObject symToString(IRubyObject sym) { - if (sym instanceof RubySymbol) { - return ((RubySymbol) sym).id2name(); + if (fieldType == FieldDescriptor.Type.UINT32 + || fieldType == FieldDescriptor.Type.UINT64 + || fieldType == FieldDescriptor.Type.FIXED32 + || fieldType == FieldDescriptor.Type.FIXED64) { + if (((RubyNumeric) value).isNegative()) { + throw runtime.newRangeError( + "Assigning negative value to unsigned integer field '" + + fieldName + + "' (given " + + value.getMetaClass() + + ")."); + } } - return sym; - } - public static void checkNameAvailability(ThreadContext context, String name) { - if (context.runtime.getObject().getConstantAt(name) != null) - throw context.runtime.newNameError(name + " is already defined", name); - } - - public static boolean isMapEntry(FieldDescriptor fieldDescriptor) { - return fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE && - fieldDescriptor.isRepeated() && - fieldDescriptor.getMessageType().getOptions().getMapEntry(); - } - - public static RaiseException createTypeError(ThreadContext context, String message) { - if (cTypeError == null) { - cTypeError = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::TypeError"); + switch (fieldType) { + case INT32: + RubyNumeric.num2int(value); + break; + case UINT32: + case FIXED32: + num2uint(value); + break; + case UINT64: + case FIXED64: + num2ulong(context.runtime, value); + break; + default: + RubyNumeric.num2long(value); + break; } - return RaiseException.from(context.runtime, cTypeError, message); - } + break; + case FLOAT: + if (!isRubyNum(value)) + throw createExpectedTypeError(context, "number", "float", fieldName, value); + break; + case DOUBLE: + if (!isRubyNum(value)) + throw createExpectedTypeError(context, "number", "double", fieldName, value); + break; + case BOOL: + if (!(value instanceof RubyBoolean)) + throw createInvalidTypeError(context, "boolean", fieldName, value); + break; + case BYTES: + value = validateAndEncodeString(context, "bytes", fieldName, value, "Encoding::ASCII_8BIT"); + break; + case STRING: + value = + validateAndEncodeString( + context, "string", fieldName, symToString(value), "Encoding::UTF_8"); + break; + case MESSAGE: + if (value.getMetaClass() != typeClass) { + // See if we can convert the value before flagging it as invalid + String className = typeClass.getName(); - public static RaiseException createExpectedTypeError(ThreadContext context, String type, String fieldType, String fieldName, IRubyObject value) { - return createTypeError(context, String.format(EXPECTED_TYPE_ERROR_FORMAT, type, fieldType, fieldName, value.getMetaClass())); - } + if (className.equals("Google::Protobuf::Timestamp") && value instanceof RubyTime) { + RubyTime rt = (RubyTime) value; + RubyHash timestampArgs = + Helpers.constructHash( + runtime, + runtime.newString("nanos"), + rt.nsec(), + false, + runtime.newString("seconds"), + rt.to_i(), + false); + return ((RubyClass) typeClass).newInstance(context, timestampArgs, Block.NULL_BLOCK); - public static RaiseException createInvalidTypeError(ThreadContext context, String fieldType, String fieldName, IRubyObject value) { - return createTypeError(context, String.format(INVALID_TYPE_ERROR_FORMAT, fieldType, fieldName, value.getMetaClass())); - } - - protected static boolean isRubyNum(Object value) { - return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum; - } - - protected static void validateTypeClass(ThreadContext context, 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 == FieldDescriptor.Type.MESSAGE) { - if (! (descriptor instanceof RubyDescriptor)) { - throw runtime.newArgumentError("Descriptor has an incorrect type"); + } else if (className.equals("Google::Protobuf::Duration") + && value instanceof RubyNumeric) { + IRubyObject seconds; + if (value instanceof RubyFloat) { + seconds = ((RubyFloat) value).truncate(context); + } else if (value instanceof RubyRational) { + seconds = ((RubyRational) value).to_i(context); + } else if (value instanceof RubyBigDecimal) { + seconds = ((RubyBigDecimal) value).to_int(context); + } else { + seconds = ((RubyInteger) value).to_i(); } - } else if (type == FieldDescriptor.Type.ENUM) { - if (! (descriptor instanceof RubyEnumDescriptor)) { - throw runtime.newArgumentError("Descriptor has an incorrect type"); + + IRubyObject nanos = ((RubyNumeric) value).remainder(context, RubyFixnum.one(runtime)); + if (nanos instanceof RubyFloat) { + nanos = ((RubyFloat) nanos).op_mul(context, 1000000000); + } else if (nanos instanceof RubyRational) { + nanos = ((RubyRational) nanos).op_mul(context, runtime.newFixnum(1000000000)); + } else if (nanos instanceof RubyBigDecimal) { + nanos = ((RubyBigDecimal) nanos).op_mul(context, runtime.newFixnum(1000000000)); + } else { + nanos = ((RubyInteger) nanos).op_mul(context, 1000000000); } + + RubyHash durationArgs = + Helpers.constructHash( + runtime, + runtime.newString("nanos"), + ((RubyNumeric) nanos).round(context), + false, + runtime.newString("seconds"), + seconds, + false); + return ((RubyClass) typeClass).newInstance(context, durationArgs, Block.NULL_BLOCK); + } + + // Not able to convert so flag as invalid + throw createTypeError( + context, + "Invalid type " + + value.getMetaClass() + + " to assign to submessage field '" + + fieldName + + "'."); } + + break; + case ENUM: + boolean isValid = + ((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR)) + .isValidValue(context, value); + if (!isValid) { + throw runtime.newRangeError("Unknown symbol value for enum field '" + fieldName + "'."); + } + break; + default: + break; } + return value; + } - private static IRubyObject validateAndEncodeString(ThreadContext context, String fieldType, String fieldName, IRubyObject value, String encoding) { - if (!(value instanceof RubyString)) - throw createInvalidTypeError(context, fieldType, fieldName, value); + public static IRubyObject wrapPrimaryValue( + ThreadContext context, FieldDescriptor.Type fieldType, Object value) { + return wrapPrimaryValue(context, fieldType, value, false); + } - value = ((RubyString) value).encode(context, context.runtime.evalScriptlet(encoding)); - value.setFrozen(true); - return value; + public static IRubyObject wrapPrimaryValue( + ThreadContext context, FieldDescriptor.Type fieldType, Object value, boolean encodeBytes) { + Ruby runtime = context.runtime; + switch (fieldType) { + case INT32: + case SFIXED32: + case SINT32: + return runtime.newFixnum((Integer) value); + case SFIXED64: + case SINT64: + case INT64: + return runtime.newFixnum((Long) value); + case FIXED32: + case UINT32: + return runtime.newFixnum(((Integer) value) & (-1l >>> 32)); + case FIXED64: + 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: + { + IRubyObject wrapped = + encodeBytes + ? RubyString.newString( + runtime, ((ByteString) value).toStringUtf8(), ASCIIEncoding.INSTANCE) + : RubyString.newString(runtime, ((ByteString) value).toByteArray()); + wrapped.setFrozen(true); + return wrapped; + } + case STRING: + { + IRubyObject wrapped = runtime.newString(value.toString()); + wrapped.setFrozen(true); + return wrapped; + } + default: + return runtime.getNil(); } + } - public static final String DESCRIPTOR_INSTANCE_VAR = "@descriptor"; + 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 final String EQUAL_SIGN = "="; + 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); + } + } - private static final BigInteger UINT64_COMPLEMENTARY = new BigInteger("18446744073709551616"); //Math.pow(2, 64) + /* + * Helper to make it easier to support symbols being passed instead of strings + */ + public static IRubyObject symToString(IRubyObject sym) { + if (sym instanceof RubySymbol) { + return ((RubySymbol) sym).id2name(); + } + return sym; + } - private static final String EXPECTED_TYPE_ERROR_FORMAT = "Expected %s type for %s field '%s' (given %s)."; - private static final String INVALID_TYPE_ERROR_FORMAT = "Invalid argument for %s field '%s' (given %s)."; + public static void checkNameAvailability(ThreadContext context, String name) { + if (context.runtime.getObject().getConstantAt(name) != null) + throw context.runtime.newNameError(name + " is already defined", name); + } - private static final long UINT_MAX = 0xffffffffl; + public static boolean isMapEntry(FieldDescriptor fieldDescriptor) { + return fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE + && fieldDescriptor.isRepeated() + && fieldDescriptor.getMessageType().getOptions().getMapEntry(); + } - private static RubyClass cTypeError; + public static RaiseException createTypeError(ThreadContext context, String message) { + if (cTypeError == null) { + cTypeError = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::TypeError"); + } + return RaiseException.from(context.runtime, cTypeError, message); + } + + public static RaiseException createExpectedTypeError( + ThreadContext context, String type, String fieldType, String fieldName, IRubyObject value) { + return createTypeError( + context, + String.format( + EXPECTED_TYPE_ERROR_FORMAT, type, fieldType, fieldName, value.getMetaClass())); + } + + public static RaiseException createInvalidTypeError( + ThreadContext context, String fieldType, String fieldName, IRubyObject value) { + return createTypeError( + context, + String.format(INVALID_TYPE_ERROR_FORMAT, fieldType, fieldName, value.getMetaClass())); + } + + protected static boolean isRubyNum(Object value) { + return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum; + } + + protected static void validateTypeClass( + ThreadContext context, 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 == FieldDescriptor.Type.MESSAGE) { + if (!(descriptor instanceof RubyDescriptor)) { + throw runtime.newArgumentError("Descriptor has an incorrect type"); + } + } else if (type == FieldDescriptor.Type.ENUM) { + if (!(descriptor instanceof RubyEnumDescriptor)) { + throw runtime.newArgumentError("Descriptor has an incorrect type"); + } + } + } + + private static IRubyObject validateAndEncodeString( + ThreadContext context, + String fieldType, + String fieldName, + IRubyObject value, + String encoding) { + if (!(value instanceof RubyString)) + throw createInvalidTypeError(context, fieldType, fieldName, value); + + value = ((RubyString) value).encode(context, context.runtime.evalScriptlet(encoding)); + value.setFrozen(true); + return value; + } + + public static final String DESCRIPTOR_INSTANCE_VAR = "@descriptor"; + + public static final String EQUAL_SIGN = "="; + + private static final BigInteger UINT64_COMPLEMENTARY = + new BigInteger("18446744073709551616"); // Math.pow(2, 64) + + private static final String EXPECTED_TYPE_ERROR_FORMAT = + "Expected %s type for %s field '%s' (given %s)."; + private static final String INVALID_TYPE_ERROR_FORMAT = + "Invalid argument for %s field '%s' (given %s)."; + + private static final long UINT_MAX = 0xffffffffl; + + private static RubyClass cTypeError; } diff --git a/ruby/src/main/java/google/ProtobufJavaService.java b/ruby/src/main/java/google/ProtobufJavaService.java index 713891e1c..00d60a149 100644 --- a/ruby/src/main/java/google/ProtobufJavaService.java +++ b/ruby/src/main/java/google/ProtobufJavaService.java @@ -33,30 +33,29 @@ package google; import com.google.protobuf.jruby.*; +import java.io.IOException; import org.jruby.Ruby; import org.jruby.runtime.load.BasicLibraryService; -import java.io.IOException; - public class ProtobufJavaService implements BasicLibraryService { - @Override - public boolean basicLoad(Ruby ruby) throws IOException { - ruby.defineModule("Google"); + @Override + public boolean basicLoad(Ruby ruby) throws IOException { + ruby.defineModule("Google"); - /* - * The order these happen in is important because we - * save a static reference to some classes and they - * need to exist before we try to save a reference to them - */ - RubyProtobuf.createProtobuf(ruby); - RubyFileDescriptor.createRubyFileDescriptor(ruby); - RubyEnumDescriptor.createRubyEnumDescriptor(ruby); - RubyRepeatedField.createRubyRepeatedField(ruby); - RubyFieldDescriptor.createRubyFieldDescriptor(ruby); - RubyMap.createRubyMap(ruby); - RubyOneofDescriptor.createRubyOneofDescriptor(ruby); - RubyDescriptor.createRubyDescriptor(ruby); - RubyDescriptorPool.createRubyDescriptorPool(ruby); - return true; - } + /* + * The order these happen in is important because we + * save a static reference to some classes and they + * need to exist before we try to save a reference to them + */ + RubyProtobuf.createProtobuf(ruby); + RubyFileDescriptor.createRubyFileDescriptor(ruby); + RubyEnumDescriptor.createRubyEnumDescriptor(ruby); + RubyRepeatedField.createRubyRepeatedField(ruby); + RubyFieldDescriptor.createRubyFieldDescriptor(ruby); + RubyMap.createRubyMap(ruby); + RubyOneofDescriptor.createRubyOneofDescriptor(ruby); + RubyDescriptor.createRubyDescriptor(ruby); + RubyDescriptorPool.createRubyDescriptorPool(ruby); + return true; + } }