Fix ruby gem to work with JRuby

pull/8138/head
Rob Widmer 4 years ago committed by Adam Cozzette
parent 26c0fbc15b
commit 8313e577e5
  1. 12
      ruby/pom.xml
  2. 122
      ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java
  3. 154
      ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java
  4. 150
      ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java
  5. 16
      ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java
  6. 24
      ruby/src/main/java/com/google/protobuf/jruby/RubyEnumBuilderContext.java
  7. 108
      ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java
  8. 226
      ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java
  9. 337
      ruby/src/main/java/com/google/protobuf/jruby/RubyFileBuilderContext.java
  10. 106
      ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java
  11. 90
      ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java
  12. 861
      ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
  13. 202
      ruby/src/main/java/com/google/protobuf/jruby/RubyMessageBuilderContext.java
  14. 51
      ruby/src/main/java/com/google/protobuf/jruby/RubyOneofBuilderContext.java
  15. 76
      ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java
  16. 14
      ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java
  17. 64
      ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java
  18. 262
      ruby/src/main/java/com/google/protobuf/jruby/Utils.java
  19. 17
      ruby/src/main/java/google/ProtobufJavaService.java
  20. 8
      ruby/tests/basic.rb
  21. 1
      ruby/tests/encode_decode_test.rb

@ -43,6 +43,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<finalName>${jar.finalName}</finalName>
<outputDirectory>${ruby.sources}</outputDirectory>
@ -64,9 +65,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
@ -80,13 +82,13 @@
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
<version>1.7.13</version>
<version>9.2.11.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.0.0</version>
<artifactId>protobuf-java-util</artifactId>
<version>3.13.0</version>
</dependency>
</dependencies>
</project>

@ -41,8 +41,8 @@ import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "Builder")
public class RubyBuilder extends RubyObject {
public static void createRubyBuilder(Ruby runtime) {
RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cBuilder = protobuf.defineClassUnder("Builder", runtime.getObject(), new ObjectAllocator() {
RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
RubyClass cBuilder = internal.defineClassUnder("Builder", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyBuilder(runtime, klazz);
@ -53,10 +53,7 @@ public class RubyBuilder extends RubyObject {
public RubyBuilder(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
this.cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor");
this.cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor");
this.cMessageBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::MessageBuilderContext");
this.cEnumBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumBuilderContext");
this.cFileBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::FileBuilderContext");
}
/*
@ -68,9 +65,8 @@ public class RubyBuilder extends RubyObject {
* (co)recursive type references.
*/
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
Ruby runtime = context.runtime;
this.pendingList = runtime.newArray();
public IRubyObject initialize(ThreadContext context, IRubyObject descriptorPool) {
this.descriptorPool = (RubyDescriptorPool) descriptorPool;
return this;
}
@ -78,90 +74,74 @@ public class RubyBuilder extends RubyObject {
* call-seq:
* Builder.add_message(name, &block)
*
* Creates a new, empty descriptor with the given name, and invokes the block in
* the context of a MessageBuilderContext on that descriptor. The block can then
* call, e.g., MessageBuilderContext#optional and MessageBuilderContext#repeated
* methods to define the message fields.
* Old and deprecated way to create a new descriptor.
* See FileBuilderContext.add_message for the recommended way.
*
* This is the recommended, idiomatic way to build message definitions.
* Exists for backwards compatibility to allow building descriptor pool for
* files generated by protoc which don't add messages within "add_file" block.
* Descriptors created this way get assigned to a default empty FileDescriptor.
*/
@JRubyMethod(name = "add_message")
public IRubyObject addMessage(ThreadContext context, IRubyObject name, Block block) {
RubyDescriptor msgdef = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK);
IRubyObject ctx = cMessageBuilderContext.newInstance(context, msgdef, this, Block.NULL_BLOCK);
msgdef.setName(context, name);
if (block.isGiven()) {
if (block.arity() == Arity.ONE_ARGUMENT) {
block.yield(context, ctx);
} else {
Binding binding = block.getBinding();
binding.setSelf(ctx);
block.yieldSpecific(context);
}
}
this.pendingList.add(msgdef);
return context.runtime.getNil();
ensureDefaultFileBuilder(context);
defaultFileBuilder.addMessage(context, name, block);
return context.nil;
}
/*
* call-seq:
* Builder.add_enum(name, &block)
*
* Creates a new, empty enum descriptor with the given name, and invokes the block in
* the context of an EnumBuilderContext on that descriptor. The block can then
* call EnumBuilderContext#add_value to define the enum values.
* Old and deprecated way to create a new enum descriptor.
* See FileBuilderContext.add_enum for the recommended way.
*
* This is the recommended, idiomatic way to build enum definitions.
* Exists for backwards compatibility to allow building descriptor pool for
* files generated by protoc which don't add enums within "add_file" block.
* Enum descriptors created this way get assigned to a default empty
* FileDescriptor.
*/
@JRubyMethod(name = "add_enum")
public IRubyObject addEnum(ThreadContext context, IRubyObject name, Block block) {
RubyEnumDescriptor enumDef = (RubyEnumDescriptor) cEnumDescriptor.newInstance(context, Block.NULL_BLOCK);
IRubyObject ctx = cEnumBuilderContext.newInstance(context, enumDef, Block.NULL_BLOCK);
enumDef.setName(context, name);
if (block.isGiven()) {
if (block.arity() == Arity.ONE_ARGUMENT) {
block.yield(context, ctx);
} else {
Binding binding = block.getBinding();
binding.setSelf(ctx);
block.yieldSpecific(context);
}
}
this.pendingList.add(enumDef);
return context.runtime.getNil();
ensureDefaultFileBuilder(context);
defaultFileBuilder.addEnum(context, name, block);
return context.nil;
}
/*
* call-seq:
* Builder.finalize_to_pool(pool)
* Builder.add_file(name, options = nil, &block)
*
* Adds all accumulated message and enum descriptors created in this builder
* context to the given pool. The operation occurs atomically, and all
* descriptors can refer to each other (including in cycles). This is the only
* way to build (co)recursive message definitions.
* Creates a new, file descriptor with the given name and options and invokes
* the block in the context of a FileBuilderContext on that descriptor. The
* block can then call FileBuilderContext#add_message or
* FileBuilderContext#add_enum to define new messages or enums, respectively.
*
* This method is usually called automatically by DescriptorPool#build after it
* invokes the given user block in the context of the builder. The user should
* not normally need to call this manually because a Builder is not normally
* created manually.
* This is the recommended, idiomatic way to build file descriptors.
*/
@JRubyMethod(name = "finalize_to_pool")
public IRubyObject finalizeToPool(ThreadContext context, IRubyObject rbPool) {
RubyDescriptorPool pool = (RubyDescriptorPool) rbPool;
for (int i = 0; i < this.pendingList.size(); i++) {
IRubyObject defRb = this.pendingList.entry(i);
if (defRb instanceof RubyDescriptor) {
pool.addToSymtab(context, (RubyDescriptor) defRb);
} else {
pool.addToSymtab(context, (RubyEnumDescriptor) defRb);
}
@JRubyMethod(name = "add_file")
public IRubyObject addFile(ThreadContext context, IRubyObject name, IRubyObject options, Block block) {
RubyFileBuilderContext ctx = (RubyFileBuilderContext) cFileBuilderContext.newInstance(context, descriptorPool, name, options, Block.NULL_BLOCK);
ctx.instance_eval(context, block);
ctx.build(context);
return context.nil;
}
/*
* Used to trigger the build when using the deprecated syntax
*/
protected void build(ThreadContext context) {
if (defaultFileBuilder != null) {
defaultFileBuilder.build(context);
}
}
private void ensureDefaultFileBuilder(ThreadContext context) {
if (defaultFileBuilder == null) {
this.defaultFileBuilder = (RubyFileBuilderContext) cFileBuilderContext.newInstance(context, descriptorPool, context.runtime.newString("ruby_default_file.proto"), Block.NULL_BLOCK);
}
this.pendingList = context.runtime.newArray();
return context.runtime.getNil();
}
protected RubyArray pendingList;
private RubyClass cDescriptor, cEnumDescriptor, cMessageBuilderContext, cEnumBuilderContext;
private RubyClass cFileBuilderContext;
private RubyDescriptorPool descriptorPool;
private RubyFileBuilderContext defaultFileBuilder;
}

@ -32,12 +32,14 @@
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
@ -58,29 +60,14 @@ public class RubyDescriptor extends RubyObject {
});
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.new => descriptor
*
* Creates a new, empty, message type descriptor. At a minimum, its name must be
* set before it is added to a pool. It cannot be used to create messages until
* it is added to a pool, after which it becomes immutable (as part of a
* finalization process).
*/
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
this.builder = DescriptorProtos.DescriptorProto.newBuilder();
this.fieldDefMap = new HashMap<String, RubyFieldDescriptor>();
this.oneofDefs = new HashMap<IRubyObject, RubyOneofDescriptor>();
return this;
}
/*
* call-seq:
* Descriptor.name => name
@ -90,38 +77,7 @@ public class RubyDescriptor extends RubyObject {
*/
@JRubyMethod(name = "name")
public IRubyObject getName(ThreadContext context) {
return this.name;
}
/*
* call-seq:
* Descriptor.name = name
*
* Assigns a name to this message type. The descriptor must not have been added
* to a pool yet.
*/
@JRubyMethod(name = "name=")
public IRubyObject setName(ThreadContext context, IRubyObject name) {
this.name = name;
this.builder.setName(Utils.escapeIdentifier(this.name.asJavaString()));
return context.runtime.getNil();
}
/*
* call-seq:
* Descriptor.add_field(field) => nil
*
* Adds the given FieldDescriptor to this message type. The descriptor must not
* have been added to a pool yet. Raises an exception if a field with the same
* name or number already exists. Sub-type references (e.g. for fields of type
* message) are not resolved at this point.
*/
@JRubyMethod(name = "add_field")
public IRubyObject addField(ThreadContext context, IRubyObject obj) {
RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) obj;
this.fieldDefMap.put(fieldDef.getName(context).asJavaString(), fieldDef);
this.builder.addField(fieldDef.build());
return context.runtime.getNil();
return name;
}
/*
@ -133,7 +89,7 @@ public class RubyDescriptor extends RubyObject {
*/
@JRubyMethod
public IRubyObject lookup(ThreadContext context, IRubyObject fieldName) {
return this.fieldDefMap.get(fieldName.asJavaString());
return Helpers.nullToNil(fieldDescriptors.get(fieldName), context.nil);
}
/*
@ -145,10 +101,7 @@ public class RubyDescriptor extends RubyObject {
*/
@JRubyMethod
public IRubyObject msgclass(ThreadContext context) {
if (this.klazz == null) {
this.klazz = buildClassFromDescriptor(context);
}
return this.klazz;
return klazz;
}
/*
@ -159,33 +112,22 @@ public class RubyDescriptor extends RubyObject {
*/
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
for (Map.Entry<String, RubyFieldDescriptor> entry : fieldDefMap.entrySet()) {
for (Map.Entry<IRubyObject, RubyFieldDescriptor> entry : fieldDescriptors.entrySet()) {
block.yield(context, entry.getValue());
}
return context.runtime.getNil();
return context.nil;
}
/*
* call-seq:
* Descriptor.add_oneof(oneof) => nil
* Descriptor.file_descriptor
*
* Adds the given OneofDescriptor to this message type. This descriptor must not
* have been added to a pool yet. Raises an exception if a oneof with the same
* name already exists, or if any of the oneof's fields' names or numbers
* conflict with an existing field in this message type. All fields in the oneof
* are added to the message descriptor. Sub-type references (e.g. for fields of
* type message) are not resolved at this point.
* Returns the FileDescriptor object this message belongs to.
*/
@JRubyMethod(name = "add_oneof")
public IRubyObject addOneof(ThreadContext context, IRubyObject obj) {
RubyOneofDescriptor def = (RubyOneofDescriptor) obj;
builder.addOneofDecl(def.build(builder.getOneofDeclCount()));
for (RubyFieldDescriptor fieldDescriptor : def.getFields()) {
addField(context, fieldDescriptor);
}
oneofDefs.put(def.getName(context), def);
return context.runtime.getNil();
}
@JRubyMethod(name = "file_descriptor")
public IRubyObject getFileDescriptor(ThreadContext context) {
return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor);
}
/*
* call-seq:
@ -196,10 +138,10 @@ public class RubyDescriptor extends RubyObject {
*/
@JRubyMethod(name = "each_oneof")
public IRubyObject eachOneof(ThreadContext context, Block block) {
for (RubyOneofDescriptor oneofDescriptor : oneofDefs.values()) {
for (RubyOneofDescriptor oneofDescriptor : oneofDescriptors.values()) {
block.yieldSpecific(context, oneofDescriptor);
}
return context.runtime.getNil();
return context.nil;
}
/*
@ -211,29 +153,44 @@ public class RubyDescriptor extends RubyObject {
*/
@JRubyMethod(name = "lookup_oneof")
public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) {
if (name instanceof RubySymbol) {
name = ((RubySymbol) name).id2name();
}
return oneofDefs.containsKey(name) ? oneofDefs.get(name) : context.runtime.getNil();
return Helpers.nullToNil(oneofDescriptors.get(Utils.symToString(name)), context.nil);
}
public void setDescriptor(Descriptors.Descriptor descriptor) {
this.descriptor = descriptor;
protected FieldDescriptor getField(String name) {
return descriptor.findFieldByName(name);
}
public Descriptors.Descriptor getDescriptor() {
return this.descriptor;
}
protected void setDescriptor(ThreadContext context, Descriptor descriptor, RubyDescriptorPool pool) {
Ruby runtime = context.runtime;
Map<FieldDescriptor, RubyFieldDescriptor> cache = new HashMap();
this.descriptor = descriptor;
// Populate the field caches
fieldDescriptors = new HashMap<IRubyObject, RubyFieldDescriptor>();
oneofDescriptors = new HashMap<IRubyObject, RubyOneofDescriptor>();
public DescriptorProtos.DescriptorProto.Builder getBuilder() {
return builder;
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);
}
public void setMapEntry(boolean isMapEntry) {
this.builder.setOptions(DescriptorProtos.MessageOptions.newBuilder().setMapEntry(isMapEntry));
protected void setName(IRubyObject name) {
this.name = name;
}
private RubyModule buildClassFromDescriptor(ThreadContext context) {
private RubyClass buildClassFromDescriptor(ThreadContext context) {
Ruby runtime = context.runtime;
ObjectAllocator allocator = new ObjectAllocator() {
@ -255,15 +212,12 @@ public class RubyDescriptor extends RubyObject {
return klass;
}
protected RubyFieldDescriptor lookup(String fieldName) {
return fieldDefMap.get(Utils.unescapeIdentifier(fieldName));
}
private static RubyClass cFieldDescriptor;
private static RubyClass cOneofDescriptor;
private Descriptor descriptor;
private IRubyObject name;
private RubyModule klazz;
private DescriptorProtos.DescriptorProto.Builder builder;
private Descriptors.Descriptor descriptor;
private Map<String, RubyFieldDescriptor> fieldDefMap;
private Map<IRubyObject, RubyOneofDescriptor> oneofDefs;
private Map<IRubyObject, RubyFieldDescriptor> fieldDescriptors;
private Map<IRubyObject, RubyOneofDescriptor> oneofDescriptors;
private RubyClass klazz;
}

@ -32,15 +32,20 @@
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.DescriptorValidationException;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.IRubyObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@JRubyClass(name = "DescriptorPool")
@ -56,42 +61,38 @@ public class RubyDescriptorPool extends RubyObject {
cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class);
descriptorPool = (RubyDescriptorPool) cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK);
cBuilder = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::Builder");
cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor");
cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor");
}
public RubyDescriptorPool(Ruby ruby, RubyClass klazz) {
super(ruby, klazz);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
public RubyDescriptorPool(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
this.fileDescriptors = new ArrayList<>();
this.symtab = new HashMap<IRubyObject, IRubyObject>();
this.cBuilder = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Builder");
this.builder = DescriptorProtos.FileDescriptorProto.newBuilder();
return this;
}
@JRubyMethod
public IRubyObject build(ThreadContext context, Block block) {
RubyBuilder ctx = (RubyBuilder) cBuilder.newInstance(context, Block.NULL_BLOCK);
if (block.arity() == Arity.ONE_ARGUMENT) {
block.yield(context, ctx);
} else {
Binding binding = block.getBinding();
binding.setSelf(ctx);
block.yieldSpecific(context);
}
ctx.finalizeToPool(context, this);
buildFileDescriptor(context);
return context.runtime.getNil();
RubyBuilder ctx = (RubyBuilder) cBuilder.newInstance(context, this, Block.NULL_BLOCK);
ctx.instance_eval(context, block);
ctx.build(context); // 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) {
IRubyObject descriptor = this.symtab.get(name);
if (descriptor == null) {
return context.runtime.getNil();
}
return descriptor;
return Helpers.nullToNil(symtab.get(name), context.nil);
}
/*
@ -108,62 +109,59 @@ public class RubyDescriptorPool extends RubyObject {
return descriptorPool;
}
protected void addToSymtab(ThreadContext context, RubyDescriptor def) {
symtab.put(def.getName(context), def);
this.builder.addMessageType(def.getBuilder());
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());
}
String packageName = fd.getPackage();
if (!packageName.isEmpty()) {
packageName = packageName + ".";
}
// 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);
// Mark this as a loaded file
fileDescriptors.add(fd);
}
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);
}
protected void addToSymtab(ThreadContext context, RubyEnumDescriptor def) {
symtab.put(def.getName(context), def);
this.builder.addEnumType(def.getBuilder());
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 void buildFileDescriptor(ThreadContext context) {
Ruby runtime = context.runtime;
try {
this.builder.setSyntax("proto3");
final Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(
this.builder.build(), new Descriptors.FileDescriptor[]{});
for (Descriptors.EnumDescriptor enumDescriptor : fileDescriptor.getEnumTypes()) {
String enumName = Utils.unescapeIdentifier(enumDescriptor.getName());
if (enumDescriptor.findValueByNumber(0) == null) {
throw runtime.newTypeError("Enum definition " + enumName
+ " does not contain a value for '0'");
}
((RubyEnumDescriptor) symtab.get(runtime.newString(enumName)))
.setDescriptor(enumDescriptor);
}
for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
RubyDescriptor rubyDescriptor = ((RubyDescriptor)
symtab.get(runtime.newString(Utils.unescapeIdentifier(descriptor.getName()))));
for (Descriptors.FieldDescriptor fieldDescriptor : descriptor.getFields()) {
if (fieldDescriptor.isRequired()) {
throw runtime.newTypeError("Required fields are unsupported in proto3");
}
RubyFieldDescriptor rubyFieldDescriptor = rubyDescriptor.lookup(fieldDescriptor.getName());
rubyFieldDescriptor.setFieldDef(fieldDescriptor);
if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
RubyDescriptor subType = (RubyDescriptor) lookup(context,
runtime.newString(Utils.unescapeIdentifier(fieldDescriptor.getMessageType().getName())));
rubyFieldDescriptor.setSubType(subType);
}
if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.ENUM) {
RubyEnumDescriptor subType = (RubyEnumDescriptor) lookup(context,
runtime.newString(Utils.unescapeIdentifier(fieldDescriptor.getEnumType().getName())));
rubyFieldDescriptor.setSubType(subType);
}
}
rubyDescriptor.setDescriptor(descriptor);
}
} catch (Descriptors.DescriptorValidationException e) {
throw runtime.newRuntimeError(e.getMessage());
}
private FileDescriptor[] existingFileDescriptors() {
return fileDescriptors.toArray(new FileDescriptor[fileDescriptors.size()]);
}
private static RubyClass cBuilder;
private static RubyClass cDescriptor;
private static RubyClass cEnumDescriptor;
private static RubyDescriptorPool descriptorPool;
private RubyClass cBuilder;
private List<FileDescriptor> fileDescriptors;
private Map<IRubyObject, IRubyObject> symtab;
private DescriptorProtos.FileDescriptorProto.Builder builder;
}

@ -32,9 +32,7 @@
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
@ -49,11 +47,8 @@ public class RubyEnum {
*/
@JRubyMethod(meta = true)
public static IRubyObject lookup(ThreadContext context, IRubyObject recv, IRubyObject number) {
RubyEnumDescriptor rubyEnumDescriptorescriptor = (RubyEnumDescriptor) getDescriptor(context, recv);
Descriptors.EnumDescriptor descriptor = rubyEnumDescriptorescriptor.getDescriptor();
Descriptors.EnumValueDescriptor value = descriptor.findValueByNumber(RubyNumeric.num2int(number));
if (value == null) return context.runtime.getNil();
return context.runtime.newSymbol(value.getName());
RubyEnumDescriptor rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv);
return rubyEnumDescriptor.numberToName(context, number);
}
/*
@ -65,11 +60,8 @@ public class RubyEnum {
*/
@JRubyMethod(meta = true)
public static IRubyObject resolve(ThreadContext context, IRubyObject recv, IRubyObject name) {
RubyEnumDescriptor rubyEnumDescriptorescriptor = (RubyEnumDescriptor) getDescriptor(context, recv);
Descriptors.EnumDescriptor descriptor = rubyEnumDescriptorescriptor.getDescriptor();
Descriptors.EnumValueDescriptor value = descriptor.findValueByName(name.asJavaString());
if (value == null) return context.runtime.getNil();
return context.runtime.newFixnum(value.getNumber());
RubyEnumDescriptor rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv);
return rubyEnumDescriptor.nameToNumber(context, name);
}
/*

@ -32,9 +32,11 @@
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
@ -45,14 +47,14 @@ import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "EnumBuilderContext")
public class RubyEnumBuilderContext extends RubyObject {
public static void createRubyEnumBuilderContext(Ruby runtime) {
RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cMessageBuilderContext = protobuf.defineClassUnder("EnumBuilderContext", runtime.getObject(), new ObjectAllocator() {
RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
RubyClass cEnumBuilderContext = internal.defineClassUnder("EnumBuilderContext", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyEnumBuilderContext(runtime, klazz);
}
});
cMessageBuilderContext.defineAnnotatedMethods(RubyEnumBuilderContext.class);
cEnumBuilderContext.defineAnnotatedMethods(RubyEnumBuilderContext.class);
}
public RubyEnumBuilderContext(Ruby ruby, RubyClass klazz) {
@ -60,8 +62,11 @@ public class RubyEnumBuilderContext extends RubyObject {
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject enumDescriptor) {
this.enumDescriptor = (RubyEnumDescriptor) enumDescriptor;
public IRubyObject initialize(ThreadContext context, IRubyObject fileBuilderContext, IRubyObject name) {
this.fileBuilderContext = (RubyFileBuilderContext) fileBuilderContext;
this.builder = this.fileBuilderContext.getNewEnumBuilder();
this.builder.setName(name.asJavaString());
return this;
}
@ -74,9 +79,12 @@ public class RubyEnumBuilderContext extends RubyObject {
*/
@JRubyMethod
public IRubyObject value(ThreadContext context, IRubyObject name, IRubyObject number) {
this.enumDescriptor.addValue(context, name, number);
return context.runtime.getNil();
this.builder.addValueBuilder()
.setName(name.asJavaString())
.setNumber(RubyNumeric.num2int(number));
return context.nil;
}
private RubyEnumDescriptor enumDescriptor;
private EnumDescriptorProto.Builder builder;
private RubyFileBuilderContext fileBuilderContext;
}

@ -32,8 +32,9 @@
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
@ -64,20 +65,6 @@ public class RubyEnumDescriptor extends RubyObject {
super(runtime, klazz);
}
/*
* call-seq:
* EnumDescriptor.new => enum_descriptor
*
* Creates a new, empty, enum descriptor. Must be added to a pool before the
* enum type can be used. The enum type may only be modified prior to adding to
* a pool.
*/
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
this.builder = DescriptorProtos.EnumDescriptorProto.newBuilder();
return this;
}
/*
* call-seq:
* EnumDescriptor.name => name
@ -89,37 +76,6 @@ public class RubyEnumDescriptor extends RubyObject {
return this.name;
}
/*
* call-seq:
* EnumDescriptor.name = name
*
* Sets the name of this enum type. Cannot be called if the enum type has
* already been added to a pool.
*/
@JRubyMethod(name = "name=")
public IRubyObject setName(ThreadContext context, IRubyObject name) {
this.name = name;
this.builder.setName(Utils.escapeIdentifier(name.asJavaString()));
return context.runtime.getNil();
}
/*
* call-seq:
* EnumDescriptor.add_value(key, value)
*
* Adds a new key => value mapping to this enum type. Key must be given as a
* Ruby symbol. Cannot be called if the enum type has already been added to a
* pool. Will raise an exception if the key or value is already in use.
*/
@JRubyMethod(name = "add_value")
public IRubyObject addValue(ThreadContext context, IRubyObject name, IRubyObject number) {
DescriptorProtos.EnumValueDescriptorProto.Builder valueBuilder = DescriptorProtos.EnumValueDescriptorProto.newBuilder();
valueBuilder.setName(name.asJavaString());
valueBuilder.setNumber(RubyNumeric.num2int(number));
this.builder.addValue(valueBuilder);
return context.runtime.getNil();
}
/*
* call-seq:
* EnumDescriptor.each(&block)
@ -130,11 +86,11 @@ public class RubyEnumDescriptor extends RubyObject {
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
for (Descriptors.EnumValueDescriptor enumValueDescriptor : descriptor.getValues()) {
for (EnumValueDescriptor enumValueDescriptor : descriptor.getValues()) {
block.yield(context, runtime.newArray(runtime.newSymbol(enumValueDescriptor.getName()),
runtime.newFixnum(enumValueDescriptor.getNumber())));
}
return runtime.getNil();
return context.nil;
}
/*
@ -146,30 +102,56 @@ public class RubyEnumDescriptor extends RubyObject {
*/
@JRubyMethod
public IRubyObject enummodule(ThreadContext context) {
if (this.klazz == null) {
this.klazz = buildModuleFromDescriptor(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 this.klazz;
return enumValue != null;
}
public void setDescriptor(Descriptors.EnumDescriptor descriptor) {
this.descriptor = descriptor;
protected IRubyObject nameToNumber(ThreadContext context, IRubyObject name) {
EnumValueDescriptor value = descriptor.findValueByName(name.asJavaString());
return value == null ? context.nil : context.runtime.newFixnum(value.getNumber());
}
public Descriptors.EnumDescriptor getDescriptor() {
return this.descriptor;
protected IRubyObject numberToName(ThreadContext context, IRubyObject number) {
EnumValueDescriptor value = descriptor.findValueByNumber(RubyNumeric.num2int(number));
return value == null ? context.nil : context.runtime.newSymbol(value.getName());
}
public DescriptorProtos.EnumDescriptorProto.Builder getBuilder() {
return this.builder;
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;
Utils.checkNameAvailability(context, name.asJavaString());
RubyModule enumModule = RubyModule.newModule(runtime);
for (Descriptors.EnumValueDescriptor value : descriptor.getValues()) {
for (EnumValueDescriptor value : descriptor.getValues()) {
enumModule.defineConstant(value.getName(), runtime.newFixnum(value.getNumber()));
}
@ -178,8 +160,8 @@ public class RubyEnumDescriptor extends RubyObject {
return enumModule;
}
private EnumDescriptor descriptor;
private EnumDescriptorProto.Builder builder;
private IRubyObject name;
private RubyModule klazz;
private Descriptors.EnumDescriptor descriptor;
private DescriptorProtos.EnumDescriptorProto.Builder builder;
private RubyModule module;
}

@ -32,8 +32,8 @@
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
@ -43,7 +43,7 @@ import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "FieldDescriptor")
public class RubyFieldDescriptor extends RubyObject {
public static void createRubyFileDescriptor(Ruby runtime) {
public static void createRubyFieldDescriptor(Ruby runtime) {
RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cFieldDescriptor = mProtobuf.defineClassUnder("FieldDescriptor", runtime.getObject(), new ObjectAllocator() {
@Override
@ -60,42 +60,30 @@ public class RubyFieldDescriptor extends RubyObject {
/*
* call-seq:
* FieldDescriptor.new => field
* FieldDescriptor.default => default
*
* Returns a new field descriptor. Its name, type, etc. must be set before it is
* added to a message type.
* Returns this field's default, as a Ruby object, or nil if not yet set.
*/
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
builder = DescriptorProtos.FieldDescriptorProto.newBuilder();
return this;
}
// VALUE FieldDescriptor_default(VALUE _self) {
// DEFINE_SELF(FieldDescriptor, self, _self);
// return layout_get_default(self->fielddef);
// }
/*
* call-seq:
* FieldDescriptor.label
* FieldDescriptor.label => label
*
* Returns this field's label (i.e., plurality), as a Ruby symbol.
*
* Return the label of this field.
* Valid field labels are:
* :optional, :repeated
*/
@JRubyMethod(name = "label")
public IRubyObject getLabel(ThreadContext context) {
return this.label;
}
/*
* call-seq:
* FieldDescriptor.label = label
*
* Sets the label on this field. Cannot be called if field is part of a message
* type already in a pool.
*/
@JRubyMethod(name = "label=")
public IRubyObject setLabel(ThreadContext context, IRubyObject value) {
String labelName = value.asJavaString();
this.label = context.runtime.newSymbol(labelName.toLowerCase());
this.builder.setLabel(
DescriptorProtos.FieldDescriptorProto.Label.valueOf("LABEL_" + labelName.toUpperCase()));
return context.runtime.getNil();
if (label == null) {
calculateLabel(context);
}
return label;
}
/*
@ -111,23 +99,19 @@ public class RubyFieldDescriptor extends RubyObject {
/*
* call-seq:
* FieldDescriptor.name = name
* FieldDescriptor.subtype => message_or_enum_descriptor
*
* Sets the name of this field. Cannot be called once the containing message
* type, if any, is added to a pool.
* 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 = "name=")
public IRubyObject setName(ThreadContext context, IRubyObject value) {
String nameStr = value.asJavaString();
this.name = context.runtime.newString(nameStr);
this.builder.setName(Utils.escapeIdentifier(nameStr));
return context.runtime.getNil();
}
@JRubyMethod(name = "subtype")
public IRubyObject getSubType(ThreadContext context) {
return subType;
public IRubyObject getSubtype(ThreadContext context) {
if (subtype == null) {
calculateSubtype(context);
}
return subtype;
}
/*
@ -142,48 +126,42 @@ public class RubyFieldDescriptor extends RubyObject {
*/
@JRubyMethod(name = "type")
public IRubyObject getType(ThreadContext context) {
return Utils.fieldTypeToRuby(context, this.builder.getType());
}
/*
* call-seq:
* FieldDescriptor.type = type
*
* Sets this field's type. Cannot be called if field is part of a message type
* already in a pool.
*/
@JRubyMethod(name = "type=")
public IRubyObject setType(ThreadContext context, IRubyObject value) {
this.builder.setType(DescriptorProtos.FieldDescriptorProto.Type.valueOf("TYPE_" + value.asJavaString().toUpperCase()));
return context.runtime.getNil();
return Utils.fieldTypeToRuby(context, descriptor.getType());
}
/*
* call-seq:
* FieldDescriptor.number => number
*
* Returns this field's number, as a Ruby Integer, or nil if not yet set.
*
* Returns the tag number for this field.
*/
@JRubyMethod(name = "number")
public IRubyObject getnumber(ThreadContext context) {
public IRubyObject getNumber(ThreadContext context) {
return this.number;
}
/*
* call-seq:
* FieldDescriptor.number = number
* FieldDescriptor.submsg_name => submsg_name
*
* Sets the tag number for this field. Cannot be called if field is part of a
* message type already in a pool.
* 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.
*/
@JRubyMethod(name = "number=")
public IRubyObject setNumber(ThreadContext context, IRubyObject value) {
this.number = value;
this.builder.setNumber(RubyNumeric.num2int(value));
return context.runtime.getNil();
}
// 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
@ -194,10 +172,21 @@ public class RubyFieldDescriptor extends RubyObject {
* 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();
// @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);
}
/*
@ -208,14 +197,22 @@ public class RubyFieldDescriptor extends RubyObject {
* exception if message is of the wrong type.
*/
@JRubyMethod(name = "get")
public IRubyObject getValue(ThreadContext context, IRubyObject msgRb) {
RubyMessage message = (RubyMessage) msgRb;
if (message.getDescriptor() != fieldDef.getContainingType()) {
throw context.runtime.newTypeError("set method called on wrong message type");
}
return message.getField(context, fieldDef);
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)
@ -225,53 +222,46 @@ public class RubyFieldDescriptor extends RubyObject {
* ordinary type-checks for field setting.
*/
@JRubyMethod(name = "set")
public IRubyObject setValue(ThreadContext context, IRubyObject msgRb, IRubyObject value) {
RubyMessage message = (RubyMessage) msgRb;
if (message.getDescriptor() != fieldDef.getContainingType()) {
throw context.runtime.newTypeError("set method called on wrong message type");
}
message.setField(context, fieldDef, value);
return context.runtime.getNil();
}
protected void setSubType(IRubyObject rubyDescriptor) {
this.subType = rubyDescriptor;
}
protected void setFieldDef(Descriptors.FieldDescriptor fieldDescriptor) {
this.fieldDef = fieldDescriptor;
public IRubyObject setValue(ThreadContext context, IRubyObject message, IRubyObject value) {
((RubyMessage) message).setField(context, descriptor, value);
return context.nil;
}
protected void setOneofName(IRubyObject name) {
oneofName = name;
protected void setDescriptor(ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool) {
this.descriptor = descriptor;
this.name = context.runtime.newString(descriptor.getName());
this.pool = pool;
}
protected void setOneofIndex(int index) {
hasOneofIndex = true;
oneofIndex = index;
}
protected IRubyObject getOneofName() {
return oneofName;
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;
}
}
protected Descriptors.FieldDescriptor getFieldDef() {
return fieldDef;
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;
}
}
protected DescriptorProtos.FieldDescriptorProto build() {
if (hasOneofIndex)
builder.setOneofIndex(oneofIndex);
return this.builder.build();
}
private static final String DOT = ".";
private DescriptorProtos.FieldDescriptorProto.Builder builder;
private FieldDescriptor descriptor;
private IRubyObject name;
private IRubyObject label;
private IRubyObject number;
private IRubyObject subType;
private IRubyObject oneofName;
private Descriptors.FieldDescriptor fieldDef;
private int oneofIndex;
private boolean hasOneofIndex = false;
private IRubyObject subtype;
private RubyDescriptorPool pool;
}

@ -0,0 +1,337 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProtoOrBuilder;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.Descriptors.FieldDescriptor;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyHash;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import java.util.HashMap;
import java.util.List;
@JRubyClass(name = "FileBuilderContext")
public class RubyFileBuilderContext extends RubyObject {
public static void createRubyFileBuilderContext(Ruby runtime) {
RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
RubyClass cFileBuilderContext = internal.defineClassUnder("FileBuilderContext", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyFileBuilderContext(runtime, klazz);
}
});
cFileBuilderContext.defineAnnotatedMethods(RubyFileBuilderContext.class);
cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor");
cEnumBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::EnumBuilderContext");
cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor");
cMessageBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::MessageBuilderContext");
}
public RubyFileBuilderContext(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
}
/*
* call-seq:
* FileBuilderContext.new(descriptor_pool, name, options = nil) => context
*
* Create a new file builder context for the given file descriptor and
* builder context. This class is intended to serve as a DSL context to be used
* with #instance_eval.
*/
@JRubyMethod(required = 2, optional = 1)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
this.descriptorPool = (RubyDescriptorPool) args[0];
this.builder = FileDescriptorProto.newBuilder();
this.builder.setName(args[1].asJavaString());
this.builder.setSyntax("proto3");
if (args.length > 2) {
RubyHash options = (RubyHash) args[2];
IRubyObject syntax = options.fastARef(context.runtime.newSymbol("syntax"));
if (syntax != null) {
String syntaxStr = syntax.asJavaString();
this.builder.setSyntax(syntaxStr);
this.proto3 = syntaxStr.equals("proto3");
}
}
return this;
}
/*
* call-seq:
* FileBuilderContext.add_enum(name, &block)
*
* Creates a new, empty enum descriptor with the given name, and invokes the
* block in the context of an EnumBuilderContext on that descriptor. The block
* can then call EnumBuilderContext#add_value to define the enum values.
*
* This is the recommended, idiomatic way to build enum definitions.
*/
@JRubyMethod(name = "add_enum")
public IRubyObject addEnum(ThreadContext context, IRubyObject name, Block block) {
RubyObject ctx = (RubyObject) cEnumBuilderContext.newInstance(context, this, name, Block.NULL_BLOCK);
ctx.instance_eval(context, block);
return context.nil;
}
/*
* call-seq:
* FileBuilderContext.add_message(name, &block)
*
* Creates a new, empty descriptor with the given name, and invokes the block in
* the context of a MessageBuilderContext on that descriptor. The block can then
* call, e.g., MessageBuilderContext#optional and MessageBuilderContext#repeated
* methods to define the message fields.
*
* This is the recommended, idiomatic way to build message definitions.
*/
@JRubyMethod(name = "add_message")
public IRubyObject addMessage(ThreadContext context, IRubyObject name, Block block) {
RubyObject ctx = (RubyObject) cMessageBuilderContext.newInstance(context, this, name, Block.NULL_BLOCK);
ctx.instance_eval(context, block);
return context.nil;
}
protected void build(ThreadContext context) {
Ruby runtime = context.runtime;
List<DescriptorProto.Builder> messageBuilderList = builder.getMessageTypeBuilderList();
List<EnumDescriptorProto.Builder> enumBuilderList = builder.getEnumTypeBuilderList();
// Get the package name from defined names
String packageName = getPackageName(messageBuilderList, enumBuilderList);
if (!packageName.isEmpty()) {
builder.setPackage(packageName);
}
// Make an index of the message builders so we can easily nest them
HashMap<String, DescriptorProto.Builder> messageBuilderMap = new HashMap();
for (DescriptorProto.Builder messageBuilder : messageBuilderList) {
messageBuilderMap.put(messageBuilder.getName(), messageBuilder);
}
// Make an index of the enum builders so we can easily nest them
HashMap<String, EnumDescriptorProto.Builder> enumBuilderMap = new HashMap();
for (EnumDescriptorProto.Builder enumBuilder : enumBuilderList) {
enumBuilderMap.put("." + enumBuilder.getName(), enumBuilder);
}
// Rename and properly nest messages and create associated ruby objects
int packageNameLength = packageName.length();
int currentMessageIndex = 0;
int currentEnumIndex = 0;
// Need to get a static list because we are potentially deleting some of them from the collection
DescriptorProto.Builder[] messageBuilders = new DescriptorProto.Builder[messageBuilderList.size()];
messageBuilderList.toArray(messageBuilders);
EnumDescriptorProto.Builder[] enumBuilders = new EnumDescriptorProto.Builder[enumBuilderList.size()];
enumBuilderList.toArray(enumBuilders);
for (EnumDescriptorProto.Builder enumBuilder : enumBuilders) {
String name = enumBuilder.getName();
int lastDot = name.lastIndexOf('.');
if (lastDot > packageNameLength) {
String parentName = name.substring(0, lastDot);
String shortName = name.substring(lastDot + 1);
enumBuilder.setName(shortName);
messageBuilderMap.get(parentName).addEnumType(enumBuilder);
builder.removeEnumType(currentEnumIndex);
} else {
if (packageNameLength > 0) {
// Remove the package name
String shortName = name.substring(packageNameLength + 1);
enumBuilder.setName(shortName);
}
currentEnumIndex++;
}
// Ensure we have a default value if using proto3 syntax
if (proto3) {
boolean foundDefault = false;
for (EnumValueDescriptorProtoOrBuilder enumValue : enumBuilder.getValueOrBuilderList()) {
if (enumValue.getNumber() == 0) {
foundDefault = true;
break;
}
}
if (!foundDefault) {
throw Utils.createTypeError(context, "Enum definition " + enumBuilder.getName() + " does not contain a value for '0'");
}
}
}
for (DescriptorProto.Builder messageBuilder : messageBuilders) {
String name = messageBuilder.getName();
int lastDot = name.lastIndexOf('.');
if (lastDot > packageNameLength) {
String parentName = name.substring(0, lastDot);
String shortName = name.substring(lastDot + 1);
messageBuilder.setName(shortName);
messageBuilderMap.get(parentName).addNestedType(messageBuilder);
builder.removeMessageType(currentMessageIndex);
} else {
if (packageNameLength > 0) {
// Remove the package name
String shortName = name.substring(packageNameLength + 1);
messageBuilder.setName(shortName);
}
currentMessageIndex++;
}
// Rewrite any enum defaults needed
for(FieldDescriptorProto.Builder field : messageBuilder.getFieldBuilderList()) {
String typeName = field.getTypeName();
if (typeName == null || !field.hasDefaultValue()) continue;
EnumDescriptorProto.Builder enumBuilder = enumBuilderMap.get(typeName);
if (enumBuilder == null) continue;
int defaultValue = Integer.parseInt(field.getDefaultValue());
for (EnumValueDescriptorProtoOrBuilder enumValue : enumBuilder.getValueOrBuilderList()) {
if (enumValue.getNumber() == defaultValue) {
field.setDefaultValue(enumValue.getName());
break;
}
}
}
}
descriptorPool.registerFileDescriptor(context, builder);
}
protected EnumDescriptorProto.Builder getNewEnumBuilder() {
return builder.addEnumTypeBuilder();
}
protected DescriptorProto.Builder getNewMessageBuilder() {
return builder.addMessageTypeBuilder();
}
protected boolean isProto3() {
return proto3;
}
private String getPackageName(List<DescriptorProto.Builder> messages, List<EnumDescriptorProto.Builder> enums) {
String shortest = null;
String longest = null;
/*
* The >= in the longest string comparisons below makes it so we replace
* the name in case all the names are the same length. This makes it so
* that the shortest and longest aren't the same name to prevent
* finding a "package" that isn't correct
*/
for (DescriptorProto.Builder message : messages) {
String name = message.getName();
int nameLength = name.length();
if (shortest == null) {
shortest = name;
longest = name;
} else if (nameLength < shortest.length()) {
shortest = name;
} else if (nameLength >= longest.length()) {
longest = name;
}
}
for (EnumDescriptorProto.Builder item : enums) {
String name = item.getName();
int nameLength = name.length();
if (shortest == null) {
shortest = name;
longest = name;
} else if (nameLength < shortest.length()) {
shortest = name;
} else if (nameLength >= longest.length()) {
longest = name;
}
}
if (shortest == null) {
return "";
}
int lastCommonDot = 0;
for (int i = 0; i < shortest.length(); i++) {
char nextChar = shortest.charAt(i);
if (nextChar != longest.charAt(i)) break;
if (nextChar == '.') lastCommonDot = i;
}
return shortest.substring(0, lastCommonDot);
}
private static RubyClass cDescriptor;
private static RubyClass cEnumBuilderContext;
private static RubyClass cEnumDescriptor;
private static RubyClass cMessageBuilderContext;
private FileDescriptorProto.Builder builder;
private RubyDescriptorPool descriptorPool;
private boolean proto3 = true;
}

@ -0,0 +1,106 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor.Syntax.*;
import com.google.protobuf.Descriptors.GenericDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "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) {
return new RubyFileDescriptor(runtime, klazz);
}
});
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;
}
}
private static RubyClass cFileDescriptor;
private FileDescriptor fileDescriptor;
}

@ -32,19 +32,19 @@
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.MapEntry;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
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.ArrayList;
@ -100,7 +100,6 @@ public class RubyMap extends RubyObject {
* references to underlying objects will be shared if the value type is a
* message type.
*/
@JRubyMethod(required = 2, optional = 2)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
this.table = new HashMap<IRubyObject, IRubyObject>();
@ -108,13 +107,15 @@ public class RubyMap extends RubyObject {
this.valueType = Utils.rubyToFieldType(args[1]);
switch(keyType) {
case STRING:
case BYTES:
this.keyTypeIsString = true;
break;
case INT32:
case INT64:
case UINT32:
case UINT64:
case BOOL:
case STRING:
case BYTES:
// These are OK.
break;
default:
@ -130,8 +131,6 @@ public class RubyMap extends RubyObject {
this.valueTypeClass = context.runtime.getNilClass();
}
// Table value type is always UINT64: this ensures enough space to store the
// native_slot value.
if (args.length > initValueArg) {
mergeIntoSelf(context, args[initValueArg]);
}
@ -148,10 +147,19 @@ public class RubyMap extends RubyObject {
*/
@JRubyMethod(name = "[]=")
public IRubyObject indexSet(ThreadContext context, IRubyObject key, IRubyObject value) {
key = Utils.checkType(context, keyType, key, (RubyModule) valueTypeClass);
value = Utils.checkType(context, valueType, value, (RubyModule) valueTypeClass);
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 context.runtime.newTypeError("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 == Descriptors.FieldDescriptor.Type.ENUM &&
if (valueType == FieldDescriptor.Type.ENUM &&
Utils.isRubyNum(value) &&
! (symbol = RubyEnum.lookup(context, valueTypeClass, value)).isNil()) {
value = symbol;
@ -169,9 +177,8 @@ public class RubyMap extends RubyObject {
*/
@JRubyMethod(name = "[]")
public IRubyObject index(ThreadContext context, IRubyObject key) {
if (table.containsKey(key))
return this.table.get(key);
return context.runtime.getNil();
key = Utils.symToString(key);
return Helpers.nullToNil(table.get(key), context.nil);
}
/*
@ -190,7 +197,7 @@ public class RubyMap extends RubyObject {
@JRubyMethod(name = "==")
public IRubyObject eq(ThreadContext context, IRubyObject _other) {
if (_other instanceof RubyHash)
return toHash(context).op_equal(context, _other);
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())
@ -214,7 +221,7 @@ public class RubyMap extends RubyObject {
*/
@JRubyMethod
public IRubyObject inspect() {
return toHash(getRuntime().getCurrentContext()).inspect();
return singleLevelHash(getRuntime().getCurrentContext()).inspect();
}
/*
@ -231,7 +238,7 @@ public class RubyMap extends RubyObject {
digest.update((byte) key.hashCode());
digest.update((byte) table.get(key).hashCode());
}
return context.runtime.newString(new ByteList(digest.digest()));
return context.runtime.newFixnum(ByteBuffer.wrap(digest.digest()).getLong());
} catch (NoSuchAlgorithmException ignore) {
return context.runtime.newFixnum(System.identityHashCode(table));
}
@ -267,8 +274,9 @@ public class RubyMap extends RubyObject {
*/
@JRubyMethod
public IRubyObject clear(ThreadContext context) {
checkFrozen();
table.clear();
return context.runtime.getNil();
return context.nil;
}
/*
@ -284,7 +292,7 @@ public class RubyMap extends RubyObject {
for (IRubyObject key : table.keySet()) {
block.yieldSpecific(context, key, table.get(key));
}
return context.runtime.getNil();
return context.nil;
}
/*
@ -296,6 +304,7 @@ public class RubyMap extends RubyObject {
*/
@JRubyMethod
public IRubyObject delete(ThreadContext context, IRubyObject key) {
checkFrozen();
return table.remove(key);
}
@ -340,7 +349,20 @@ public class RubyMap extends RubyObject {
@JRubyMethod(name = "to_h")
public RubyHash toHash(ThreadContext context) {
return RubyHash.newHash(context.runtime, table, context.runtime.getNil());
Map<IRubyObject, IRubyObject> 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);
}
});
return RubyHash.newHash(context.runtime, mapForHash, context.nil);
}
// Used by Google::Protobuf.deep_copy but not exposed directly.
@ -361,16 +383,16 @@ public class RubyMap extends RubyObject {
return newMap;
}
protected List<DynamicMessage> build(ThreadContext context, RubyDescriptor descriptor) {
protected List<DynamicMessage> build(ThreadContext context, RubyDescriptor descriptor, int depth) {
List<DynamicMessage> list = new ArrayList<DynamicMessage>();
RubyClass rubyClass = (RubyClass) descriptor.msgclass(context);
Descriptors.FieldDescriptor keyField = descriptor.lookup("key").getFieldDef();
Descriptors.FieldDescriptor valueField = descriptor.lookup("value").getFieldDef();
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));
list.add(mapMessage.build(context, depth + 1));
}
return list;
}
@ -380,13 +402,16 @@ public class RubyMap extends RubyObject {
((RubyHash) hashmap).visitAll(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);
}
});
} else if (hashmap instanceof RubyMap) {
RubyMap other = (RubyMap) hashmap;
if (!typeCompatible(other)) {
throw context.runtime.newTypeError("Attempt to merge Map with mismatching types");
throw Utils.createTypeError(context, "Attempt to merge Map with mismatching types");
}
} else {
throw context.runtime.newTypeError("Unknown type merging into Map");
@ -417,7 +442,15 @@ public class RubyMap extends RubyObject {
return newMap;
}
private boolean needTypeclass(Descriptors.FieldDescriptor.Type type) {
/*
* 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:
@ -427,8 +460,9 @@ public class RubyMap extends RubyObject {
}
}
private Descriptors.FieldDescriptor.Type keyType;
private Descriptors.FieldDescriptor.Type valueType;
private FieldDescriptor.Type keyType;
private FieldDescriptor.Type valueType;
private IRubyObject valueTypeClass;
private Map<IRubyObject, IRubyObject> table;
private boolean keyTypeIsString = false;
}

@ -32,7 +32,9 @@
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.OneofDescriptorProto;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
@ -45,52 +47,58 @@ import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "MessageBuilderContext")
public class RubyMessageBuilderContext extends RubyObject {
public static void createRubyMessageBuilderContext(Ruby runtime) {
RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cMessageBuilderContext = protobuf.defineClassUnder("MessageBuilderContext", runtime.getObject(), new ObjectAllocator() {
RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
RubyClass cMessageBuilderContext = internal.defineClassUnder("MessageBuilderContext", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyMessageBuilderContext(runtime, klazz);
}
});
cMessageBuilderContext.defineAnnotatedMethods(RubyMessageBuilderContext.class);
cFieldDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
cOneofBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::OneofBuilderContext");
}
public RubyMessageBuilderContext(Ruby ruby, RubyClass klazz) {
super(ruby, klazz);
public RubyMessageBuilderContext(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject descriptor, IRubyObject rubyBuilder) {
this.cFieldDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
this.cDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Descriptor");
this.cOneofDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::OneofDescriptor");
this.cOneofBuilderContext = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::OneofBuilderContext");
this.descriptor = (RubyDescriptor) descriptor;
this.builder = (RubyBuilder) rubyBuilder;
public IRubyObject initialize(ThreadContext context, IRubyObject fileBuilderContext, IRubyObject name) {
this.fileBuilderContext = (RubyFileBuilderContext) fileBuilderContext;
this.builder = this.fileBuilderContext.getNewMessageBuilder();
this.builder.setName(name.asJavaString());
return this;
}
/*
* call-seq:
* MessageBuilderContext.optional(name, type, number, type_class = nil)
* MessageBuilderContext.optional(name, type, number, type_class = nil,
* options = nil)
*
* Defines a new optional field on this message type with the given type, tag
* number, and type class (for message and enum fields). The type must be a Ruby
* symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
* string, if present (as accepted by FieldDescriptor#submsg_name=).
*/
@JRubyMethod(required = 3, optional = 1)
@JRubyMethod(required = 3, optional = 2)
public IRubyObject optional(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.runtime;
IRubyObject typeClass = runtime.getNil();
if (args.length > 3) typeClass = args[3];
msgdefAddField(context, "optional", args[0], args[1], args[2], typeClass);
return context.runtime.getNil();
addField(context, OPTIONAL, args, false);
return context.nil;
}
@JRubyMethod(required = 3, optional = 2)
public IRubyObject proto3_optional(ThreadContext context, IRubyObject[] args) {
addField(context, OPTIONAL, args, true);
return context.nil;
}
/*
* call-seq:
* MessageBuilderContext.required(name, type, number, type_class = nil)
* MessageBuilderContext.required(name, type, number, type_class = nil,
* options = nil)
*
* Defines a new required field on this message type with the given type, tag
* number, and type class (for message and enum fields). The type must be a Ruby
@ -101,12 +109,11 @@ public class RubyMessageBuilderContext extends RubyObject {
* completeness. Any attempt to add a message type with required fields to a
* pool will currently result in an error.
*/
@JRubyMethod(required = 3, optional = 1)
@JRubyMethod(required = 3, optional = 2)
public IRubyObject required(ThreadContext context, IRubyObject[] args) {
IRubyObject typeClass = context.runtime.getNil();
if (args.length > 3) typeClass = args[3];
msgdefAddField(context, "required", args[0], args[1], args[2], typeClass);
return context.runtime.getNil();
if (fileBuilderContext.isProto3()) throw Utils.createTypeError(context, "Required fields are unsupported in proto3");
addField(context, "required", args, false);
return context.nil;
}
/*
@ -120,10 +127,8 @@ public class RubyMessageBuilderContext extends RubyObject {
*/
@JRubyMethod(required = 3, optional = 1)
public IRubyObject repeated(ThreadContext context, IRubyObject[] args) {
IRubyObject typeClass = context.runtime.getNil();
if (args.length > 3) typeClass = args[3];
msgdefAddField(context, "repeated", args[0], args[1], args[2], typeClass);
return context.runtime.getNil();
addField(context, "repeated", args, false);
return context.nil;
}
/*
@ -141,77 +146,110 @@ public class RubyMessageBuilderContext extends RubyObject {
@JRubyMethod(required = 4, optional = 1)
public IRubyObject map(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.runtime;
if (!fileBuilderContext.isProto3()) throw runtime.newArgumentError("Cannot add a native map field using proto2 syntax.");
RubySymbol messageSym = runtime.newSymbol("message");
IRubyObject name = args[0];
IRubyObject keyType = args[1];
IRubyObject valueType = args[2];
IRubyObject number = args[3];
IRubyObject typeClass = args.length > 4 ? args[4] : context.runtime.getNil();
IRubyObject typeClass = args.length > 4 ? args[4] : context.nil;
// Validate the key type. We can't accept enums, messages, or floats/doubles
// as map keys. (We exclude these explicitly, and the field-descriptor setter
// below then ensures that the type is one of the remaining valid options.)
if (keyType.equals(RubySymbol.newSymbol(runtime, "float")) ||
keyType.equals(RubySymbol.newSymbol(runtime, "double")) ||
keyType.equals(RubySymbol.newSymbol(runtime, "enum")) ||
keyType.equals(RubySymbol.newSymbol(runtime, "message")))
if (keyType.equals(runtime.newSymbol("float")) ||
keyType.equals(runtime.newSymbol("double")) ||
keyType.equals(runtime.newSymbol("enum")) ||
keyType.equals(messageSym))
throw runtime.newArgumentError("Cannot add a map field with a float, double, enum, or message type.");
// Create a new message descriptor for the map entry message, and create a
// repeated submessage field here with that type.
RubyDescriptor mapentryDesc = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK);
IRubyObject mapentryDescName = RubySymbol.newSymbol(runtime, name).id2name(context);
mapentryDesc.setName(context, mapentryDescName);
mapentryDesc.setMapEntry(true);
//optional <type> key = 1;
RubyFieldDescriptor keyField = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
keyField.setName(context, runtime.newString("key"));
keyField.setLabel(context, RubySymbol.newSymbol(runtime, "optional"));
keyField.setNumber(context, runtime.newFixnum(1));
keyField.setType(context, keyType);
mapentryDesc.addField(context, keyField);
//optional <type> value = 2;
RubyFieldDescriptor valueField = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
valueField.setName(context, runtime.newString("value"));
valueField.setLabel(context, RubySymbol.newSymbol(runtime, "optional"));
valueField.setNumber(context, runtime.newFixnum(2));
valueField.setType(context, valueType);
if (! typeClass.isNil()) valueField.setSubmsgName(context, typeClass);
mapentryDesc.addField(context, valueField);
// Add the map-entry message type to the current builder, and use the type to
// create the map field itself.
this.builder.pendingList.add(mapentryDesc);
msgdefAddField(context, "repeated", name, runtime.newSymbol("message"), number, mapentryDescName);
return runtime.getNil();
DescriptorProto.Builder mapEntryBuilder = fileBuilderContext.getNewMessageBuilder();
mapEntryBuilder.setName(builder.getName() + "_MapEntry_" + name.asJavaString());
mapEntryBuilder.getOptionsBuilder().setMapEntry(true);
mapEntryBuilder.addField(
Utils.createFieldBuilder(
context,
OPTIONAL,
new IRubyObject[] {
runtime.newString("key"),
keyType,
runtime.newFixnum(1)
}
)
);
mapEntryBuilder.addField(
Utils.createFieldBuilder(
context,
OPTIONAL,
new IRubyObject[] {
runtime.newString("value"),
valueType,
runtime.newFixnum(2),
typeClass
}
)
);
IRubyObject[] addFieldArgs = {
name, messageSym, number, runtime.newString(mapEntryBuilder.getName())
};
repeated(context, addFieldArgs);
return context.nil;
}
/*
* call-seq:
* MessageBuilderContext.oneof(name, &block) => nil
*
* Creates a new OneofDescriptor with the given name, creates a
* OneofBuilderContext attached to that OneofDescriptor, evaluates the given
* block in the context of that OneofBuilderContext with #instance_eval, and
* then adds the oneof to the message.
*
* This is the recommended, idiomatic way to build oneof definitions.
*/
@JRubyMethod
public IRubyObject oneof(ThreadContext context, IRubyObject name, Block block) {
RubyOneofDescriptor oneofdef = (RubyOneofDescriptor)
cOneofDescriptor.newInstance(context, Block.NULL_BLOCK);
RubyOneofBuilderContext ctx = (RubyOneofBuilderContext)
cOneofBuilderContext.newInstance(context, oneofdef, Block.NULL_BLOCK);
oneofdef.setName(context, name);
Binding binding = block.getBinding();
binding.setSelf(ctx);
block.yieldSpecific(context);
descriptor.addOneof(context, oneofdef);
return context.runtime.getNil();
cOneofBuilderContext.newInstance(
context,
context.runtime.newFixnum(builder.getOneofDeclCount()),
this,
Block.NULL_BLOCK
);
builder.addOneofDeclBuilder().setName(name.asJavaString());
ctx.instance_eval(context, block);
return context.nil;
}
private void msgdefAddField(ThreadContext context, String label, IRubyObject name,
IRubyObject type, IRubyObject number, IRubyObject typeClass) {
descriptor.addField(context,
Utils.msgdefCreateField(context, label, name, type, number, typeClass, cFieldDescriptor));
protected void addFieldBuilder(FieldDescriptorProto.Builder fieldBuilder) {
builder.addField(fieldBuilder);
}
private RubyDescriptor descriptor;
private RubyBuilder builder;
private RubyClass cFieldDescriptor;
private RubyClass cOneofDescriptor;
private RubyClass cOneofBuilderContext;
private FieldDescriptorProto.Builder addField(ThreadContext context, String label, IRubyObject[] args, boolean proto3Optional) {
FieldDescriptorProto.Builder fieldBuilder =
Utils.createFieldBuilder(context, label, args);
fieldBuilder.setProto3Optional(proto3Optional);
builder.addField(fieldBuilder);
return fieldBuilder;
}
private static RubyClass cFieldDescriptor;
private static RubyClass cOneofBuilderContext;
private static final String OPTIONAL = "optional";
private DescriptorProto.Builder builder;
private RubyClass cDescriptor;
private RubyFileBuilderContext fileBuilderContext;
}

@ -32,9 +32,12 @@
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyHash;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
@ -45,8 +48,7 @@ import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "OneofBuilderContext")
public class RubyOneofBuilderContext extends RubyObject {
public static void createRubyOneofBuilderContext(Ruby runtime) {
RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
RubyModule internal = protobuf.defineModuleUnder("Internal");
RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
RubyClass cRubyOneofBuidlerContext = internal.defineClassUnder("OneofBuilderContext", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
@ -60,25 +62,42 @@ public class RubyOneofBuilderContext extends RubyObject {
super(ruby, rubyClass);
}
/*
* call-seq:
* OneofBuilderContext.new(oneof_index, message_builder) => context
*
* Create a new oneof builder context around the given oneof descriptor and
* builder context. This class is intended to serve as a DSL context to be used
* with #instance_eval.
*/
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject oneofdef) {
this.descriptor = (RubyOneofDescriptor) oneofdef;
this.cFieldDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
public IRubyObject initialize(ThreadContext context, IRubyObject index, IRubyObject messageBuilder) {
this.builder = (RubyMessageBuilderContext) messageBuilder;
this.index = RubyNumeric.num2int(index);
return this;
}
@JRubyMethod(required = 3, optional = 1)
/*
* call-seq:
* OneofBuilderContext.optional(name, type, number, type_class = nil,
* options = nil)
*
* Defines a new optional field in this oneof with the given type, tag number,
* and type class (for message and enum fields). The type must be a Ruby symbol
* (as accepted by FieldDescriptor#type=) and the type_class must be a string,
* if present (as accepted by FieldDescriptor#submsg_name=).
*/
@JRubyMethod(required = 3, optional = 2)
public IRubyObject optional(ThreadContext context, IRubyObject[] args) {
IRubyObject name = args[0];
IRubyObject type = args[1];
IRubyObject number = args[2];
IRubyObject typeClass = args.length > 3 ? args[3] : context.runtime.getNil();
RubyFieldDescriptor fieldDescriptor = Utils.msgdefCreateField(context, "optional",
name, type, number, typeClass, cFieldDescriptor);
descriptor.addField(context, fieldDescriptor);
return this;
FieldDescriptorProto.Builder fieldBuilder =
Utils.createFieldBuilder(context, "optional", args);
fieldBuilder.setOneofIndex(index);
builder.addFieldBuilder(fieldBuilder);
return context.nil;
}
private RubyOneofDescriptor descriptor;
private RubyClass cFieldDescriptor;
private RubyMessageBuilderContext builder;
private int index;
}

@ -1,7 +1,7 @@
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
@ -13,7 +13,10 @@ import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import java.util.*;
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 {
@ -32,13 +35,7 @@ public class RubyOneofDescriptor extends RubyObject {
public RubyOneofDescriptor(Ruby ruby, RubyClass rubyClass) {
super(ruby, rubyClass);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
builder = DescriptorProtos.OneofDescriptorProto.newBuilder();
fields = new ArrayList<RubyFieldDescriptor>();
return this;
}
/*
@ -52,42 +49,6 @@ public class RubyOneofDescriptor extends RubyObject {
return name;
}
/*
* call-seq:
* OneofDescriptor.name = name
*
* Sets a new name for this oneof. The oneof must not have been added to a
* message descriptor yet.
*/
@JRubyMethod(name = "name=")
public IRubyObject setName(ThreadContext context, IRubyObject name) {
this.name = context.runtime.newString(name.asJavaString());
this.builder.setName(name.asJavaString());
return context.runtime.getNil();
}
/*
* call-seq:
* OneofDescriptor.add_field(field) => nil
*
* Adds a field to this oneof. The field may have been added to this oneof in
* the past, or the message to which this oneof belongs (if any), but may not
* have already been added to any other oneof or message. Otherwise, an
* exception is raised.
*
* All fields added to the oneof via this method will be automatically added to
* the message to which this oneof belongs, if it belongs to one currently, or
* else will be added to any message to which the oneof is later added at the
* time that it is added.
*/
@JRubyMethod(name = "add_field")
public IRubyObject addField(ThreadContext context, IRubyObject obj) {
RubyFieldDescriptor fieldDescriptor = (RubyFieldDescriptor) obj;
fieldDescriptor.setOneofName(this.name);
fields.add(fieldDescriptor);
return context.runtime.getNil();
}
/*
* call-seq:
* OneofDescriptor.each(&block) => nil
@ -99,26 +60,27 @@ public class RubyOneofDescriptor extends RubyObject {
for (RubyFieldDescriptor field : fields) {
block.yieldSpecific(context, field);
}
return context.runtime.getNil();
}
public DescriptorProtos.OneofDescriptorProto build(int index) {
for (RubyFieldDescriptor field: fields) {
field.setOneofIndex(index);
}
return this.builder.build();
return context.nil;
}
protected Collection<RubyFieldDescriptor> getFields() {
return fields;
}
protected Descriptors.OneofDescriptor getOneofDescriptor() {
RubyFieldDescriptor fieldDescriptor = fields.get(0);
return fieldDescriptor.getFieldDef().getContainingOneof();
protected OneofDescriptor getDescriptor() {
return descriptor;
}
protected void setDescriptor(ThreadContext context, OneofDescriptor descriptor, Map<FieldDescriptor, RubyFieldDescriptor> 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 DescriptorProtos.OneofDescriptorProto.Builder builder;
private List<RubyFieldDescriptor> fields;
private OneofDescriptor descriptor;
}

@ -46,6 +46,7 @@ public class RubyProtobuf {
RubyModule mGoogle = runtime.getModule("Google");
RubyModule mProtobuf = mGoogle.defineModuleUnder("Protobuf");
mProtobuf.defineAnnotatedMethods(RubyProtobuf.class);
RubyModule mInternal = mProtobuf.defineModuleUnder("Internal");
}
/*
@ -65,4 +66,17 @@ public class RubyProtobuf {
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 discardUnkown(ThreadContext context, IRubyObject self, IRubyObject message) {
((RubyMessage) message).discardUnknownFields(context);
return context.nil;
}
}

@ -32,7 +32,7 @@
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.FieldDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
@ -61,7 +61,7 @@ public class RubyRepeatedField extends RubyObject {
super(runtime, klazz);
}
public RubyRepeatedField(Ruby runtime, RubyClass klazz, Descriptors.FieldDescriptor.Type fieldType, IRubyObject typeClass) {
public RubyRepeatedField(Ruby runtime, RubyClass klazz, FieldDescriptor.Type fieldType, IRubyObject typeClass) {
this(runtime, klazz);
this.fieldType = fieldType;
this.storage = runtime.newArray();
@ -77,8 +77,8 @@ public class RubyRepeatedField extends RubyObject {
throw runtime.newArgumentError("Expected Symbol for type name");
}
this.fieldType = Utils.rubyToFieldType(args[0]);
if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE
|| fieldType == Descriptors.FieldDescriptor.Type.ENUM) {
if (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];
@ -110,7 +110,7 @@ public class RubyRepeatedField extends RubyObject {
@JRubyMethod(name = "[]=")
public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) {
int arrIndex = normalizeArrayIndex(index);
value = Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
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);
@ -138,9 +138,12 @@ public class RubyRepeatedField extends RubyObject {
return this.storage.eltInternal(arrIndex);
} else if (arg instanceof RubyRange) {
RubyRange range = ((RubyRange) arg);
int beg = RubyNumeric.num2int(range.first(context));
int to = RubyNumeric.num2int(range.last(context));
int len = to - beg + 1;
int len = RubyNumeric.num2int(range.size(context));
if (len == 0) return context.runtime.newEmptyArray();
return this.storage.subseq(beg, len);
}
}
@ -162,14 +165,17 @@ public class RubyRepeatedField extends RubyObject {
*
* Adds a new element to the repeated field.
*/
@JRubyMethod(name = {"push", "<<"})
public IRubyObject push(ThreadContext context, IRubyObject value) {
if (!(fieldType == Descriptors.FieldDescriptor.Type.MESSAGE &&
value == context.runtime.getNil())) {
value = Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
@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);
}
this.storage.add(value);
return this.storage;
return this;
}
/*
@ -193,7 +199,7 @@ public class RubyRepeatedField extends RubyObject {
RubyArray arr = (RubyArray) list;
checkArrayElementType(context, arr);
this.storage = arr;
return this.storage;
return this;
}
/*
@ -205,7 +211,7 @@ public class RubyRepeatedField extends RubyObject {
@JRubyMethod
public IRubyObject clear(ThreadContext context) {
this.storage.clear();
return this.storage;
return this;
}
/*
@ -261,7 +267,7 @@ public class RubyRepeatedField extends RubyObject {
throw context.runtime.newArgumentError("Attempt to append RepeatedField with different element type.");
this.storage.addAll((RubyArray) repeatedField.toArray(context));
}
return this.storage;
return this;
}
/*
@ -301,7 +307,7 @@ public class RubyRepeatedField extends RubyObject {
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
this.storage.each(context, block);
return this.storage;
return this;
}
@ -320,12 +326,15 @@ public class RubyRepeatedField extends RubyObject {
@JRubyMethod
public IRubyObject dup(ThreadContext context) {
RubyRepeatedField dup = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass);
for (int i = 0; i < this.storage.size(); i++) {
dup.push(context, this.storage.eltInternal(i));
}
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);
@ -335,7 +344,7 @@ public class RubyRepeatedField extends RubyObject {
RubyRepeatedField copy = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass);
for (int i = 0; i < size(); i++) {
IRubyObject value = storage.eltInternal(i);
if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE) {
if (fieldType == FieldDescriptor.Type.MESSAGE) {
copy.storage.add(((RubyMessage) value).deepCopy(context));
} else {
copy.storage.add(value);
@ -344,6 +353,10 @@ public class RubyRepeatedField extends RubyObject {
return copy;
}
protected void setName(String name) {
this.name = name;
}
protected int size() {
return this.storage.size();
}
@ -390,7 +403,7 @@ public class RubyRepeatedField extends RubyObject {
private void checkArrayElementType(ThreadContext context, RubyArray arr) {
for (int i = 0; i < arr.getLength(); i++) {
Utils.checkType(context, fieldType, arr.eltInternal(i), (RubyModule) typeClass);
Utils.checkType(context, fieldType, name, arr.eltInternal(i), (RubyModule) typeClass);
}
}
@ -403,7 +416,8 @@ public class RubyRepeatedField extends RubyObject {
return arrIndex;
}
private RubyArray storage;
private Descriptors.FieldDescriptor.Type fieldType;
private FieldDescriptor.Type fieldType;
private IRubyObject typeClass;
private RubyArray storage;
private String name;
}

@ -33,29 +33,30 @@
package com.google.protobuf.jruby;
import com.google.protobuf.ByteString;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.Descriptors.FieldDescriptor;
import org.jcodings.Encoding;
import org.jcodings.specific.ASCIIEncoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.*;
import org.jruby.exceptions.RaiseException;
import org.jruby.ext.bigdecimal.RubyBigDecimal;
import org.jruby.runtime.Block;
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 Descriptors.FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) {
return Descriptors.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, Descriptors.FieldDescriptor.Type type) {
public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptor.Type type) {
return fieldTypeToRuby(context, type.name());
}
public static IRubyObject fieldTypeToRuby(ThreadContext context, DescriptorProtos.FieldDescriptorProto.Type type) {
public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptorProto.Type type) {
return fieldTypeToRuby(context, type.name());
}
@ -64,64 +65,115 @@ public class Utils {
return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase());
}
public static IRubyObject checkType(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType,
IRubyObject value, RubyModule typeClass) {
public static IRubyObject checkType(ThreadContext context, FieldDescriptor.Type fieldType,
String fieldName, IRubyObject value, RubyModule typeClass) {
Ruby runtime = context.runtime;
Object val;
switch(fieldType) {
case INT32:
case INT64:
case UINT32:
case UINT64:
if (!isRubyNum(value)) {
throw runtime.newTypeError("Expected number type for integral field.");
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) {
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 INT64:
RubyNumeric.num2long(value);
break;
case UINT32:
num2uint(value);
break;
default:
case UINT64:
num2ulong(context.runtime, value);
break;
default:
RubyNumeric.num2long(value);
break;
}
checkIntTypePrecision(context, fieldType, value);
break;
case FLOAT:
if (!isRubyNum(value))
throw runtime.newTypeError("Expected number type for float field.");
throw createExpectedTypeError(context, "number", "float", fieldName, value);
break;
case DOUBLE:
if (!isRubyNum(value))
throw runtime.newTypeError("Expected number type for double field.");
throw createExpectedTypeError(context, "number", "double", fieldName, value);
break;
case BOOL:
if (!(value instanceof RubyBoolean))
throw runtime.newTypeError("Invalid argument for boolean field.");
throw createInvalidTypeError(context, "boolean", fieldName, value);
break;
case BYTES:
value = validateAndEncodeString(context, "bytes", fieldName, value, "Encoding::ASCII_8BIT");
break;
case STRING:
value = validateStringEncoding(context, fieldType, value);
value = validateAndEncodeString(context, "string", fieldName, symToString(value), "Encoding::UTF_8");
break;
case MESSAGE:
if (value.getMetaClass() != typeClass) {
throw runtime.newTypeError(value, 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:
if (value instanceof RubySymbol) {
Descriptors.EnumDescriptor enumDescriptor =
((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR)).getDescriptor();
val = enumDescriptor.findValueByName(value.asJavaString());
if (val == null)
throw runtime.newRangeError("Enum value " + value + " is not found.");
} else if(!isRubyNum(value)) {
throw runtime.newTypeError("Expected number or symbol type for enum field.");
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:
@ -130,7 +182,7 @@ public class Utils {
return value;
}
public static IRubyObject wrapPrimaryValue(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, Object value) {
public static IRubyObject wrapPrimaryValue(ThreadContext context, FieldDescriptor.Type fieldType, Object value) {
Ruby runtime = context.runtime;
switch (fieldType) {
case INT32:
@ -150,7 +202,7 @@ public class Utils {
case BOOL:
return (Boolean) value ? runtime.getTrue() : runtime.getFalse();
case BYTES: {
IRubyObject wrapped = runtime.newString(((ByteString) value).toStringUtf8());
IRubyObject wrapped = RubyString.newString(runtime, ((ByteString) value).toStringUtf8(), ASCIIEncoding.INSTANCE);
wrapped.setFrozen(true);
return wrapped;
}
@ -187,21 +239,14 @@ public class Utils {
}
}
public static IRubyObject validateStringEncoding(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
if (!(value instanceof RubyString))
throw context.runtime.newTypeError("Invalid argument for string field.");
switch(type) {
case BYTES:
value = ((RubyString)value).encode(context, context.runtime.evalScriptlet("Encoding::ASCII_8BIT"));
break;
case STRING:
value = ((RubyString)value).encode(context, context.runtime.evalScriptlet("Encoding::UTF_8"));
break;
default:
break;
/*
* 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();
}
value.setFrozen(true);
return value;
return sym;
}
public static void checkNameAvailability(ThreadContext context, String name) {
@ -209,67 +254,84 @@ public class Utils {
throw context.runtime.newNameError(name + " is already defined", name);
}
/**
* Replace invalid "." in descriptor with __DOT__
* @param name
* @return
*/
public static String escapeIdentifier(String name) {
return name.replace(".", BADNAME_REPLACEMENT);
}
/**
* Replace __DOT__ in descriptor name with "."
* @param name
* @return
*/
public static String unescapeIdentifier(String name) {
return name.replace(BADNAME_REPLACEMENT, ".");
}
public static boolean isMapEntry(Descriptors.FieldDescriptor fieldDescriptor) {
return fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE &&
public static boolean isMapEntry(FieldDescriptor fieldDescriptor) {
return fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE &&
fieldDescriptor.isRepeated() &&
fieldDescriptor.getMessageType().getOptions().getMapEntry();
}
public static RubyFieldDescriptor msgdefCreateField(ThreadContext context, String label, IRubyObject name,
IRubyObject type, IRubyObject number, IRubyObject typeClass, RubyClass cFieldDescriptor) {
/*
* call-seq:
* Utils.createFieldBuilder(context, label, name, type, number, typeClass = nil, options = nil)
*
* Most places calling this are already dealing with an optional number of
* arguments so dealing with them here. This helper is a standard way to
* create a FieldDescriptor builder that handles some of the options that
* are used in different places.
*/
public static FieldDescriptorProto.Builder createFieldBuilder(ThreadContext context,
String label, IRubyObject[] args) {
Ruby runtime = context.runtime;
RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
fieldDef.setLabel(context, runtime.newString(label));
fieldDef.setName(context, name);
fieldDef.setType(context, type);
fieldDef.setNumber(context, number);
IRubyObject options = context.nil;
IRubyObject typeClass = context.nil;
if (args.length > 4) {
options = args[4];
typeClass = args[3];
} else if (args.length > 3) {
if (args[3] instanceof RubyHash) {
options = args[3];
} else {
typeClass = args[3];
}
}
FieldDescriptorProto.Builder builder = FieldDescriptorProto.newBuilder();
builder.setLabel(FieldDescriptorProto.Label.valueOf("LABEL_" + label.toUpperCase()))
.setName(args[0].asJavaString())
.setNumber(RubyNumeric.num2int(args[2]))
.setType(FieldDescriptorProto.Type.valueOf("TYPE_" + args[1].asJavaString().toUpperCase()));
if (!typeClass.isNil()) {
if (!(typeClass instanceof RubyString)) {
throw runtime.newArgumentError("expected string for type class");
}
fieldDef.setSubmsgName(context, typeClass);
builder.setTypeName("." + typeClass.asJavaString());
}
return fieldDef;
}
protected static void checkIntTypePrecision(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
if (value instanceof RubyFloat) {
double doubleVal = RubyNumeric.num2dbl(value);
if (Math.floor(doubleVal) != doubleVal) {
throw context.runtime.newRangeError("Non-integral floating point value assigned to integer field.");
if (options instanceof RubyHash) {
IRubyObject defaultValue = ((RubyHash) options).fastARef(runtime.newSymbol("default"));
if (defaultValue != null) {
builder.setDefaultValue(defaultValue.toString());
}
}
if (type == Descriptors.FieldDescriptor.Type.UINT32 || type == Descriptors.FieldDescriptor.Type.UINT64) {
if (RubyNumeric.num2dbl(value) < 0) {
throw context.runtime.newRangeError("Assigning negative value to unsigned integer field.");
}
return builder;
}
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, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
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");
@ -280,24 +342,36 @@ public class Utils {
throw runtime.newArgumentError("Type class has no descriptor. Please pass a " +
"class or enum as returned by the DescriptorPool.");
}
if (type == Descriptors.FieldDescriptor.Type.MESSAGE) {
if (type == FieldDescriptor.Type.MESSAGE) {
if (! (descriptor instanceof RubyDescriptor)) {
throw runtime.newArgumentError("Descriptor has an incorrect type");
}
} else if (type == Descriptors.FieldDescriptor.Type.ENUM) {
} else if (type == FieldDescriptor.Type.ENUM) {
if (! (descriptor instanceof RubyEnumDescriptor)) {
throw runtime.newArgumentError("Descriptor has an incorrect type");
}
}
}
public static String BADNAME_REPLACEMENT = "__DOT__";
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 = "=";
public static String DESCRIPTOR_INSTANCE_VAR = "@descriptor";
private static final BigInteger UINT64_COMPLEMENTARY = new BigInteger("18446744073709551616"); //Math.pow(2, 64)
public static String EQUAL_SIGN = "=";
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 BigInteger UINT64_COMPLEMENTARY = new BigInteger("18446744073709551616"); //Math.pow(2, 64)
private static final long UINT_MAX = 0xffffffffl;
private static long UINT_MAX = 0xffffffffl;
private static RubyClass cTypeError;
}

@ -42,19 +42,26 @@ public class ProtobufJavaService implements BasicLibraryService {
@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);
RubyDescriptor.createRubyDescriptor(ruby);
RubyBuilder.createRubyBuilder(ruby);
RubyFieldDescriptor.createRubyFileDescriptor(ruby);
RubyMessageBuilderContext.createRubyMessageBuilderContext(ruby);
RubyFileDescriptor.createRubyFileDescriptor(ruby);
RubyEnumDescriptor.createRubyEnumDescriptor(ruby);
RubyEnumBuilderContext.createRubyEnumBuilderContext(ruby);
RubyDescriptorPool.createRubyDescriptorPool(ruby);
RubyRepeatedField.createRubyRepeatedField(ruby);
RubyFieldDescriptor.createRubyFileDescriptor(ruby);
RubyFieldDescriptor.createRubyFieldDescriptor(ruby);
RubyMap.createRubyMap(ruby);
RubyOneofDescriptor.createRubyOneofDescriptor(ruby);
RubyOneofBuilderContext.createRubyOneofBuilderContext(ruby);
RubyMessageBuilderContext.createRubyMessageBuilderContext(ruby);
RubyDescriptor.createRubyDescriptor(ruby);
RubyFileBuilderContext.createRubyFileBuilderContext(ruby);
RubyDescriptorPool.createRubyDescriptorPool(ruby);
return true;
}
}

@ -241,8 +241,12 @@ module BasicTest
:map_string_msg => {"a" => TestMessage2.new(:foo => 1),
"b" => TestMessage2.new(:foo => 2)},
:map_string_enum => {"a" => :A, "b" => :B})
expected = "<BasicTest::MapMessage: map_string_int32: {\"b\"=>2, \"a\"=>1}, map_string_msg: {\"b\"=><BasicTest::TestMessage2: foo: 2>, \"a\"=><BasicTest::TestMessage2: foo: 1>}, map_string_enum: {\"b\"=>:B, \"a\"=>:A}>"
assert_equal expected, m.inspect
# JRuby doesn't keep consistent ordering so check for either version
expected_a = "<BasicTest::MapMessage: map_string_int32: {\"b\"=>2, \"a\"=>1}, map_string_msg: {\"b\"=><BasicTest::TestMessage2: foo: 2>, \"a\"=><BasicTest::TestMessage2: foo: 1>}, map_string_enum: {\"b\"=>:B, \"a\"=>:A}>"
expected_b = "<BasicTest::MapMessage: map_string_int32: {\"a\"=>1, \"b\"=>2}, map_string_msg: {\"a\"=><BasicTest::TestMessage2: foo: 1>, \"b\"=><BasicTest::TestMessage2: foo: 2>}, map_string_enum: {\"a\"=>:A, \"b\"=>:B}>"
inspect_result = m.inspect
assert expected_a == inspect_result || expected_b == inspect_result, "Incorrect inspect result: #{inspect_result}"
end
def test_map_corruption

@ -4,6 +4,7 @@
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'generated_code_pb'
require 'google/protobuf/well_known_types'
require 'test/unit'
def hex2bin(s)

Loading…
Cancel
Save