Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JNI: Create HashMap

How do I create a HashMap object in JNI?

like image 753
AOO Avatar asked Jan 30 '11 16:01

AOO


5 Answers

Here is code, you will need to modify to work

jclass mapClass = (*env)->FindClass(env, "java/util/HashMap");
if(mapClass == NULL)
{
    return NULL;
}


jsize map_len = 1;

jmethodID init = (*env)->GetMethodID(env, mapClass, "<init>", "(I)V");
jobject hashMap = (*env)->NewObject(env, mapClass, init, map_len);

jmethodID put = (*env)->GetMethodID(env, mapClass, "put",
            "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

while( ... )
{
    jint key = ...;
    size_t sz = t->count;
    jbyteArray dd = (*env)->NewByteArray(env, sz);
    for(i = 0; i < sz; i++)
    {
        (*env)->SetByteArrayRegion(env, dd, i, 1, *data++);
    }

    (*env)->CallObjectMethod(env, hashMap, put, key, dd);

    (*env)->DeleteLocalRef(env, key);
    (*env)->DeleteLocalRef(env, dd);
}

(*env)->DeleteLocalRef(env, hashMap);
(*env)->DeleteLocalRef(env, mapClass);
like image 76
xqterry Avatar answered Nov 13 '22 19:11

xqterry


Below is my contribution to this question, I have used ideas from other answers and other places. Below are two functions that convert std::map<std::string, std::string> to HashMap and back:

jobject StlStringStringMapToJavaHashMap(JNIEnv *env, 
                                        const std::map<std::string, std::string>& map);
void JavaHashMapToStlStringStringMap(JNIEnv *env, jobject hashMap, 
                                     std::map<std::string, std::string>& mapOut);

Test function:

void TestConversions(JNIEnv *env);

gives examples how to use it - btw. I have run this test on android device and it works.

jobject StlStringStringMapToJavaHashMap(JNIEnv *env, const std::map<std::string, std::string>& map) {
  jclass mapClass = env->FindClass("java/util/HashMap");
  if(mapClass == NULL)
    return NULL;

  jmethodID init = env->GetMethodID(mapClass, "<init>", "()V");
  jobject hashMap = env->NewObject(mapClass, init);
  jmethodID put = env->GetMethodID(mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

  std::map<std::string, std::string>::const_iterator citr = map.begin();
  for( ; citr != map.end(); ++citr) {
    jstring keyJava = env->NewStringUTF(citr->first.c_str());
    jstring valueJava = env->NewStringUTF(citr->second.c_str());

    env->CallObjectMethod(hashMap, put, keyJava, valueJava);

    env->DeleteLocalRef(keyJava);
    env->DeleteLocalRef(valueJava);
  }

  jobject hashMapGobal = static_cast<jobject>(env->NewGlobalRef(hashMap));
  env->DeleteLocalRef(hashMap);
  env->DeleteLocalRef(mapClass);

  return hashMapGobal;
}

// Based on android platform code from: /media/jni/android_media_MediaMetadataRetriever.cpp
void JavaHashMapToStlStringStringMap(JNIEnv *env, jobject hashMap, std::map<std::string, std::string>& mapOut) {
  // Get the Map's entry Set.
  jclass mapClass = env->FindClass("java/util/Map");
  if (mapClass == NULL) {
    return;
  }
  jmethodID entrySet =
    env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;");
  if (entrySet == NULL) {
    return;
  }
  jobject set = env->CallObjectMethod(hashMap, entrySet);
  if (set == NULL) {
    return;
  }
  // Obtain an iterator over the Set
  jclass setClass = env->FindClass("java/util/Set");
  if (setClass == NULL) {
    return;
  }
  jmethodID iterator =
    env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
  if (iterator == NULL) {
    return;
  }
  jobject iter = env->CallObjectMethod(set, iterator);
  if (iter == NULL) {
    return;
  }
  // Get the Iterator method IDs
  jclass iteratorClass = env->FindClass("java/util/Iterator");
  if (iteratorClass == NULL) {
    return;
  }
  jmethodID hasNext = env->GetMethodID(iteratorClass, "hasNext", "()Z");
  if (hasNext == NULL) {
    return;
  }
  jmethodID next =
    env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
  if (next == NULL) {
    return;
  }
  // Get the Entry class method IDs
  jclass entryClass = env->FindClass("java/util/Map$Entry");
  if (entryClass == NULL) {
    return;
  }
  jmethodID getKey =
    env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
  if (getKey == NULL) {
    return;
  }
  jmethodID getValue =
    env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
  if (getValue == NULL) {
    return;
  }
  // Iterate over the entry Set
  while (env->CallBooleanMethod(iter, hasNext)) {
    jobject entry = env->CallObjectMethod(iter, next);
    jstring key = (jstring) env->CallObjectMethod(entry, getKey);
    jstring value = (jstring) env->CallObjectMethod(entry, getValue);
    const char* keyStr = env->GetStringUTFChars(key, NULL);
    if (!keyStr) {  // Out of memory
      return;
    }
    const char* valueStr = env->GetStringUTFChars(value, NULL);
    if (!valueStr) {  // Out of memory
      env->ReleaseStringUTFChars(key, keyStr);
      return;
    }

    mapOut.insert(std::make_pair(std::string(keyStr), std::string(valueStr)));

    env->DeleteLocalRef(entry);
    env->ReleaseStringUTFChars(key, keyStr);
    env->DeleteLocalRef(key);
    env->ReleaseStringUTFChars(value, valueStr);
    env->DeleteLocalRef(value);
  }
}

void TestConversions(JNIEnv *env) {

  // Empty test
  {
    std::map<std::string, std::string> map, mapTest;
    jobject hashMap = StlStringStringMapToJavaHashMap(env, map);
    JavaHashMapToStlStringStringMap(env, hashMap, mapTest);
    assert(map == mapTest);
  }

  // One element test
  {
    std::map<std::string, std::string> map, mapTest;
    map["one"] = "uno";
    jobject hashMap = StlStringStringMapToJavaHashMap(env, map);
    JavaHashMapToStlStringStringMap(env, hashMap, mapTest);
    assert(map == mapTest);
  }

  // Two element test
  {
    std::map<std::string, std::string> map, mapTest;
    map["one"] = "uno";
    map["two"] = "duo";
    jobject hashMap = StlStringStringMapToJavaHashMap(env, map);
    JavaHashMapToStlStringStringMap(env, hashMap, mapTest);
    assert(map == mapTest);
  }

  // Huge number of elements test
  {
    std::map<std::string, std::string> map, mapTest;
    for (int n = 0; n < 10000; ++n) {
      map[std::to_string(n)] = std::to_string(n);
    }
    jobject hashMap = StlStringStringMapToJavaHashMap(env, map);
    JavaHashMapToStlStringStringMap(env, hashMap, mapTest);
    assert(map == mapTest);
  }
}
like image 35
marcinj Avatar answered Nov 13 '22 20:11

marcinj


For me I found that the signature of the "put" method needed to be different from that listed in the example above. i.e.

jmethodID put = env->GetMethodID(mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
like image 3
Lloyd Maddock Avatar answered Nov 13 '22 21:11

Lloyd Maddock


See here:

Some example code to call a String constructor:

jstring
 MyNewString(JNIEnv *env, jchar *chars, jint len)
 {
     jclass stringClass;
     jmethodID cid;
     jcharArray elemArr;
     jstring result;

     stringClass = (*env)->FindClass(env, "java/lang/String");
     if (stringClass == NULL) {
         return NULL; /* exception thrown */
     }
 /* Get the method ID for the String(char[]) constructor */
     cid = (*env)->GetMethodID(env, stringClass,
                               "<init>", "([C)V");
     if (cid == NULL) {
         return NULL; /* exception thrown */
     }

     /* Create a char[] that holds the string characters */
     elemArr = (*env)->NewCharArray(env, len);
     if (elemArr == NULL) {
         return NULL; /* exception thrown */
     }
     (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);

     /* Construct a java.lang.String object */
     result = (*env)->NewObject(env, stringClass, cid, elemArr);

     /* Free local references */
     (*env)->DeleteLocalRef(env, elemArr);
     (*env)->DeleteLocalRef(env, stringClass);
     return result;
 }
like image 2
Daniel Avatar answered Nov 13 '22 20:11

Daniel


One may also consider alternatives to directly using JNI - e.g. tools that can generate JNI code for you. For example, JANET (disclaimer: I wrote it) allows you to embed Java code in your native methods, so creating and using a hash map is then as simple as:

... (C++ code)
`Map map = new HashMap();` // embedded Java
... (C++ code)
... const char* foo = "foo";
`map.put(#$(foo), 50);` // ["foo" -> 50]

the back-ticked statements get translated by JANET to JNI code, so you never have to worry about signatures, reference handling, exception handling, etc. yet you still get the performance of JNI.

like image 2
Dawid Kurzyniec Avatar answered Nov 13 '22 19:11

Dawid Kurzyniec