Paludis meets Java, part III

Once we’ve converted native types we face the task of converting arbitrary classes and types. Another thing we’ll need is extract the C++ type of a Java object (jobject in JNI).

For that task, we will follow the convention of calling the native pointers in Java classes _ptr. With that, we can define the following templates:

template <typename T_>
inline T_ * get_native_ptr(JNIEnv * env, const char * const class_name, jobject obj)
{
    jclass cls(env->FindClass(class_name));
    jfieldID ptr_field(env->GetFieldID(cls, "_ptr", "J"));
    jlong ptr(env->GetLongField(obj, ptr_field));
    env->DeleteLocalRef(cls);
    return from_java_ptr<T_>(ptr);
}

template <typename T_>
inline tr1::shared_ptr<T_> get_native_sptr(JNIEnv * env, const char * const class_name, jobject obj)
{
    jclass cls(env->FindClass(class_name));
    jfieldID ptr_field(env->GetFieldID(cls, "_ptr", "J"));
    jlong ptr(env->GetLongField(obj, ptr_field));
    env->DeleteLocalRef(cls);
    return from_java_ptr_sptr<T_>(ptr);
}

This should really be doing more error checking, but it is good enough for ilustrating how nice are things when using proper tools (both languages and libraries).

Converting arbitrary types also uses some template magic:

template <typename T_>
struct NativeToJavaTypeMapper
{
    jobject operator() (JNIEnv *, const T_ &);
};

Now we need specializations for each type we want to convert, for instance, converting a paludis’ FSEntry into a java.io.File looks like the following:

template<>
struct NativeToJavaTypeMapper<FSEntry>
{
    jobject operator() (JNIEnv * env, const FSEntry & f)
    {
        jclass cls(env->FindClass("java/io/File"));
        jmethodID constructor(env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V"));
        jobject ret(env->NewObject(cls, constructor, to_java_string(env, stringify(f))));
        env->DeleteLocalRef(cls);
        return ret;
    }
};

Neat and clean.

For containers, a java.util.LinkedList would be used for paludis’ Sequence; and java.util.TreeSet for paludis’ Set:

template <typename T_, typename It_>
jobject range_to_list(JNIEnv * env, It_ begin, It_ end)
{
    jclass list_class(env->FindClass("java/util/LinkedList"));
    jmethodID constructor(env->GetMethodID(list_class, "<init>", "()V"));
    jobject our_list(env->NewObject(list_class, constructor));
    jmethodID add_method(env->GetMethodID(list_class, "add", "(Ljava/lang/Object;)Z"));

    for (It_ i(begin) ; i != end ; ++i)
        env->CallBooleanMethod(our_list, add_method, NativeToJavaTypeMapper<T_>()(env, *i));

    env->DeleteLocalRef(list_class);

    return our_list;
}

template <typename T_, typename It_>
jobject range_to_set(JNIEnv * env, It_ begin, It_ end)
{
    jclass set_class(env->FindClass("java/util/TreeSet"));
    jmethodID constructor(env->GetMethodID(set_class, "<init>", "()V"));
    jobject our_set(env->NewObject(set_class, constructor));
    jmethodID add_method(env->GetMethodID(set_class, "add", "(Ljava/lang/Object;)Z"));

    for (It_ i(begin) ; i != end ; ++i)
        env->CallBooleanMethod(our_set, add_method, NativeToJavaTypeMapper<T_>()(env, *i));

    env->DeleteLocalRef(set_class);

    return our_set;
}

And now defining NativeToJavaTypeMapper specializations for containers is quite easy:

template<>
template <typename T_>
struct NativeToJavaTypeMapper<tr1::shared_ptr<const Sequence<T_> > >
{
    jobject operator() (JNIEnv * env, const tr1::shared_ptr<const Sequence<T_> > & s)
    {
        return range_to_list<T_>(env, s->begin(), s->end());
    }
};

template<>
template <typename T_>
struct NativeToJavaTypeMapper<tr1::shared_ptr<const Set<T_> > >
{
    jobject operator() (JNIEnv * env, const tr1::shared_ptr<const Set<T_> > & s)
    {
        return range_to_set<T_>(env, s->begin(), s->end());
    }
};

I’ve spent a fair amount of the time fighting with make and the build system. It looks mostly sane now, though.

Dealing with exceptions has been a bit tricky, however, I think I have a good system to deal with it now, even though Ciaran tagged it as icky and ugly 🙂. That’d be the topic of the next part of the series.

The documentation is currently at http://dev.gentoo.org/~ferdy/paludis-jni/ . All of that has been accomplished in:

[ $ ~/git/paludis/jni(jni) ] git diff --shortstat trunk..
 65 files changed, 4371 insertions(+), 0 deletions(-)

Which is not a lot of code for what’s exposed.

— ferdy

Paludis meets Java, part II

After showing how is the structure of a regular Paludis class using JNI, next thing is showing part of the magic behind paludis_java.hh.

What’s there is functions to convert Java types into Paludis (and C++ native) types and viceversa (this conversions exist only when needed, not for every single type). So, for instance, one of those conversions would be jboolean <--> bool and it looks like this:

inline jboolean to_java_boolean(bool b)
{
    return b ? JNI_TRUE : JNI_FALSE;
}

inline bool from_java_boolean(jboolean b)
{
    return b == JNI_TRUE;
}

That was easy, let me show you the dirty part. I call it dirty not because it is difficult or tricky to understand, but because it is a bit icky. It is the way we store the native Paludis pointers in Java classes:

inline jlong to_java_ptr(void * const ptr)
{
    return reinterpret_cast<jlong>(ptr);
}

template <typename T_>
inline T_ * from_java_ptr(jlong ptr)
{
    return reinterpret_cast<_ *>(ptr);
}

template <typename T_>
inline tr1::shared_ptr<T_> from_java_ptr_sptr(jlong ptr)
{
    return * reinterpret_cast<tr1::shared_ptr<T_> *>(ptr);
}

Although dirty, it is quite easy aswell. Something this bindings will be converting a lot is strings:

std::string
from_java_string(JNIEnv * const env, jstring s)
{
    const char * const c_s(env->GetStringUTFChars(s, 0));
    std::string result(c_s);
    env->ReleaseStringUTFChars(s, c_s);
    return result;
}

jstring
to_java_string(JNIEnv * const env, const std::string & s)
{
    return env->NewStringUTF(s.c_str());
}

And since C++ is such a nice language comparing and stringifying arbitrary types was just as easy (credits for this go to Mr. McCreesh):

template <typename T_>
jstring common_stringify(JNIEnv * const env, jlong ptr)
{
    return to_java_string(env, stringify(*from_java_ptr<T_>(ptr)));
}

template <typename T_>
jint common_compare(jlong lhs_ptr, jlong rhs_ptr)
{
    T_ * const lhs(from_java_ptr<T_>(lhs_ptr)), * const rhs(from_java_ptr<T_>(rhs_ptr));
    if (*lhs < *rhs)
        return -1;
    else if (*rhs < *lhs)
        return 1;
    else
        return 0;
}

This wasn’t quite difficult and it certainly makes working with JNI easier. However, there is still stuff to do (actually, to show, since it is implemented and working in my git repository) like exception handling, converting arbitrary Paludis types and typesafe containers.

During the weekend I’ve written bindings for almost every core Paludis class and the patch is not that big:

[ $ ~/git/paludis(jni) ] git diff --shortstat trunk..
 30 files changed, 1975 insertions(+), 0 deletions(-)

Now it is time to stop the bindings for a while and start writing documentation, examples and integrating the bindings into the Paludis codebase properly.

Next part of the series will be about how I am converting arbitrary types and containers into Java types and typesafe collections respectively.

— ferdy

Paludis meets Java, part I

Some days ago, the Wearer of the shiny hat started tinkering with JNI bindings for Paludis.

Even if we all think Java is perverse and should be avoided at all costs. Having used Java for the last four years and having studied part of the Java5 code I thought I could use everything I learnt and took the task of developing and maintaining the JNI bindings.

It has all been relatively easy. It’s good that Ciaran started them; because his experience with the Ruby bindings gave the key to developing the Java bindings cleanly.

What we are currently doing is storing a C++ pointer in Java classes and passing it around in native methods. Then we delete it when Java’s garbage collector decides it is good to call finalize.

So it mostly looks like this:

package paludis;
import paludis.Paludis;

public class Foo {
    private static Paludis _load_the_frickin_paludis_library = new Paludis();
    private long _ptr;

    private static native long _construct_string(String s);
    private static native void _destruct(long ptr);

    private static native boolean _is_foo(long ptr);

    protected void finalize() throws Throwable {
        _destruct(_ptr);
        super.finalize();
    }

    public Foo(String s) {
        _ptr = _construct_string(s);
    }

    public boolean isFoo() {
        return _is_foo(_ptr);
    }
}

Then the C++ part looks like this:

/* vim: set sw=4 sts=4 et foldmethod=syntax : */

#include "foo.hh"
#include "paludis_java.hh"
#include <jni.h>
#include <paludis/foo.hh>

using namespace paludis;
using namespace paludis::java;

JNIEXPORT jlong JNICALL
Java_paludis_Foo__1construct_1string(JNIEnv * env, jclass, jstring s)
{
    return to_java_ptr(new Foo(from_java_string(env, s)));
}

JNIEXPORT void JNICALL
Java_paludis_Foo__1destruct(JNIEnv *, jclass, jlong ptr)
{
    delete from_java_ptr<Foo>(ptr);
}

JNIEXPORT jboolean JNICALL
Java_paludis_Foo__1is_1foo(JNIEnv *, jclass, jlong ptr)
{
    return to_java_boolean(from_java_ptr<Foo>(ptr)->is_foo());
}

Here, all the magic is happening behind the scenes in the paludis_java.hh header which I’ll show in a future post of the Paludis meets Java series.

— ferdy

I wonder who is acting like a moron…

It is often surprising how some people can really lose control of a situation.

Take this example http://dev.gentoo.org/~ferdy/tmp/seemant-moron.log . It is actually sad having to deal with that kind of thing. I asked three times for an apology and (surprise surprise) I didn’t get one. One publicly in IRC, another one in a /query and a third one by mail.

Besides wondering who is the one acting like a moron, I really wonder why people have the feeling that Gentoo is oh so nice and friendly.

No love.

— ferdy

PS: Despite of his attitude, I still can’t be bothered with personal issues. So if he wanted me to have an issue with him, I’m thankful he failed.

Been to Morocco, loved it

I didn’t really want to go there because it meant lots of car (4100 Km in a week) and not enough time to enjoy and getting to know each place we went.

ferdy as a twareg

In the end, well, I went. And I’m quite happy I did it. It is a very interesting country where the first thing you learn is:

Hurry kills...
    and lazyness re-kills. — Anyone in Morocco

We crossed the Atlas twice. Some of the views up there are just amazing.

We also went to the dunes in Merzouga. If you ever consider going to Morocco, you can’t miss a couple of nights in ‘Atlas du Sable‘ with Ali’s crew, those guys make it very comfortable and enjoyable.

We also visited M’Hamid and that part of Morocco, that part was a bit harder emotionally. But it is still enlightening and a very interesting place to visit.

Last place was Marrakech. I loved it so much that I’ll have to go again because two nights is not enough for this city.

Only complains are: Too many hours of ‘car stuck because of sand’ and not enough time to get to know any place. but all in all it’s been a wonderful trip and a very nice experience. Their way of living and thinking is just too different from anything in Europe.

We took hundreds of photos, some of them are on my picasaweb album: http://picasaweb.google.com/fpereda/MoroccoTour07 .

If you ever have the oportunity to go to Morocco, go for it.

— ferdy