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