Skip to content

Instantly share code, notes, and snippets.

@isaiah
Last active October 13, 2015 02:27
Show Gist options
  • Save isaiah/4124471 to your computer and use it in GitHub Desktop.
Save isaiah/4124471 to your computer and use it in GitHub Desktop.
How to write jruby extension

How to define modules

To define a ruby module, the java class doesn't have to inherit from RubyObject and no object allocator is required.

@JRubyModule(name = "Protobuf")
public class RubyProtobuf {

    public static void createProtobuf(Ruby runtime) {
        RubyModule google = runtime.getOrCreateModule("Google");
        RubyModule protobuf = google.defineModuleUnder("Protobuf");
        protobuf.defineAnnotatedMethods(RubyProtobuf.class);
        // To define constants that are integers
        protobuf.defineAnnotatedConstants(RubyProtobuf.class);
        // Define constant
        protobuf.defineConstant("ACCEPTED", runtime.newFixnum(1));
    }
    
    @JRubyConstant
    public final static int OK = 0;
}

To define method in module, the method has to be declared static, the first two parameters are ThreadContext context and IRubyObject recv.

// JRubyMethod decides how the ruby world see the method, it has the following options:
// required, optional, rest, meta
@JRubyMethod(name = "count")
public static IRubyObject count(ThreadContext context, IRubyObject self, final Block block) {

Add meta=true to @JRubyMethod(meta=true) if the method is a module method.

Problem

Sometimes you found that jruby always complain about cannot find class you defined in your precious java class, that jruby failed to load the Service that defines all the classes in ruby space.

How it work

Let's talk about the way jruby load native extension, take puma as an example, Assume you have 'puma/http11.jar' in your load path, with:

require 'puma/http11'

JRuby will look for an implementation of BasicLibraryService whose qualitifed name is puma.Http11Service from that jar, as a init point.

how to define classes

A common practice is move the responsibility to your class for define class and module it located in. for example to define Puma::Http11Parser:

Http11Parser.java

class Http11 extends RubyObject {
    public static void createHttp11(Ruby runtime) {
        RubyModule mPuma = runtime.defineModule("Puma");
        RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),ALLOCATOR);
        cHttpParser.defineAnnotatedMethods(Http11.class);
    }

    private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new Http11(runtime, klass);
        }
    };
}

puma.Http11Service.java

package puma;
import Http11;

public class PumaHttp11Service implements BasicLibraryService { 
    public boolean basicLoad(final Ruby runtime) throws IOException {
        Http11.createHttp11(runtime);
        return true;
    }
}

Thing to keep in mind

initialize methods is called with IRubyObject[] args as always.

"static method" aka "class method" or "module method" should be declared as static

When calling into ruby, there is a GIL holding, which sometimes might seem surprise.

How tos

Convert java type to ruby:

runtime.newBoolean(bool);
runtime.newString(String);
// short cuts
runtime.getTrue();
...

From ruby to java:

IRubyObject rubyObj;
rubyObj.asJavaString(); // to String
rubyObj.isTrue(); // to boolean
RubyNumeric.num2int(rubyObj) // to int, long, double

Wrap a Java Throwable in a IRubyObject, use:

new NativeException(runtime, runtime.getNativeException(), throwable);
throw runtime.newRuntimeError(throwable.getMessage());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment