text: /*
   * Copyright (C) 2012, Collabora Ltd.
   *   Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
   * Copyright (C) 2013, Fluendo S.A.
   *   Author: Andoni Morales <amorales@fluendo.com>
   * Copyright (C) 2014, Sebastian Dröge <sebastian@centricular.com>
   * Copyright (C) 2014, Collabora Ltd.
   *   Author: Matthieu Bouron <matthieu.bouron@collabora.com>
   * Copyright (C) 2015, Sebastian Dröge <sebastian@centricular.com>
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation
   * version 2.1 of the License.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
   *
   */
  
  #ifdef HAVE_CONFIG_H
  #include "config.h"
  #endif
  
  #include <gst/gst.h>
  #include <pthread.h>
  #include <gmodule.h>
  
  #include "gstjniutils.h"
  
  static GModule *java_module;
  static jint (*get_created_java_vms) (JavaVM ** vmBuf, jsize bufLen,
      jsize * nVMs);
  static jint (*create_java_vm) (JavaVM ** p_vm, JNIEnv ** p_env, void *vm_args);
  static JavaVM *java_vm;
  static gboolean started_java_vm = FALSE;
  static pthread_key_t current_jni_env;
  static jobject (*get_class_loader) (void);
  
  static struct
  {
    jclass klass;
    jmethodID get_limit, get_position;
    jmethodID set_limit, set_position;
    jmethodID clear;
  } java_nio_buffer;
  
  jclass
  gst_amc_jni_get_class (JNIEnv * env, GError ** err, const gchar * name)
  {
    jclass tmp, ret = NULL;
  
    GST_DEBUG ("Retrieving Java class %s", name);
  
    tmp = (*env)->FindClass (env, name);
    if ((*env)->ExceptionCheck (env) || !tmp) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to find class %s", name);
      goto done;
    }
  
    ret = (*env)->NewGlobalRef (env, tmp);
    if (!ret) {
      GST_ERROR ("Failed to get %s class global reference", name);
    }
  
  done:
    if (tmp)
      (*env)->DeleteLocalRef (env, tmp);
    tmp = NULL;
  
    return ret;
  }
  
  jmethodID
  gst_amc_jni_get_method_id (JNIEnv * env, GError ** err, jclass klass,
      const gchar * name, const gchar * signature)
  {
    jmethodID ret;
  
    ret = (*env)->GetMethodID (env, klass, name, signature);
    if ((*env)->ExceptionCheck (env) || !ret) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to get method ID %s (%s)", name,
          signature);
    }
    return ret;
  }
  
  jmethodID
  gst_amc_jni_get_static_method_id (JNIEnv * env, GError ** err, jclass klass,
      const gchar * name, const gchar * signature)
  {
    jmethodID ret;
  
    ret = (*env)->GetStaticMethodID (env, klass, name, signature);
    if ((*env)->ExceptionCheck (env) || !ret) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to get static method ID %s (%s)",
          name, signature);
    }
    return ret;
  }
  
  jfieldID
  gst_amc_jni_get_field_id (JNIEnv * env, GError ** err, jclass klass,
      const gchar * name, const gchar * type)
  {
    jfieldID ret;
  
    ret = (*env)->GetFieldID (env, klass, name, type);
    if ((*env)->ExceptionCheck (env) || !ret) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to get field ID %s (%s)", name, type);
    }
    return ret;
  }
  
  jfieldID
  gst_amc_jni_get_static_field_id (JNIEnv * env, GError ** err, jclass klass,
      const gchar * name, const gchar * type)
  {
    jfieldID ret;
  
    ret = (*env)->GetStaticFieldID (env, klass, name, type);
    if ((*env)->ExceptionCheck (env) || !ret) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to get static field ID %s (%s)", name,
          type);
    }
    return ret;
  }
  
  jobject
  gst_amc_jni_new_object (JNIEnv * env, GError ** err, gboolean global,
      jclass klass, jmethodID constructor, ...)
  {
    jobject tmp;
    va_list args;
  
    va_start (args, constructor);
    tmp = (*env)->NewObjectV (env, klass, constructor, args);
    va_end (args);
  
    if ((*env)->ExceptionCheck (env) || !tmp) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to create object");
      return NULL;
    }
  
    if (global)
      return gst_amc_jni_object_make_global (env, tmp);
    else
      return tmp;
  }
  
  jobject
  gst_amc_jni_new_object_from_static (JNIEnv * env, GError ** err,
      gboolean global, jclass klass, jmethodID method, ...)
  {
    jobject tmp;
    va_list args;
  
    va_start (args, method);
    tmp = (*env)->CallStaticObjectMethodV (env, klass, method, args);
    va_end (args);
  
    if ((*env)->ExceptionCheck (env) || !tmp) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to create object");
      return NULL;
    }
  
    if (global)
      return gst_amc_jni_object_make_global (env, tmp);
    else
      return tmp;
  }
  
  jobject
  gst_amc_jni_object_make_global (JNIEnv * env, jobject object)
  {
    jobject ret;
  
    ret = (*env)->NewGlobalRef (env, object);
    if (!ret) {
      GST_ERROR ("Failed to create global reference");
    }
    gst_amc_jni_object_local_unref (env, object);
  
    return ret;
  }
  
  jobject
  gst_amc_jni_object_ref (JNIEnv * env, jobject object)
  {
    jobject ret;
  
    ret = (*env)->NewGlobalRef (env, object);
    if (!ret) {
      GST_ERROR ("Failed to create global reference");
    }
    return ret;
  }
  
  void
  gst_amc_jni_object_unref (JNIEnv * env, jobject object)
  {
    g_return_if_fail (object != NULL);
  
    (*env)->DeleteGlobalRef (env, object);
  }
  
  void
  gst_amc_jni_object_local_unref (JNIEnv * env, jobject object)
  {
    g_return_if_fail (object != NULL);
  
    (*env)->DeleteLocalRef (env, object);
  }
  
  jstring
  gst_amc_jni_string_from_gchar (JNIEnv * env, GError ** err,
      gboolean global, const gchar * string)
  {
    jstring tmp;
  
    tmp = (*env)->NewStringUTF (env, string);
    if ((*env)->ExceptionCheck (env)) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to call Java method");
      tmp = NULL;
    }
  
    if (global)
      return gst_amc_jni_object_make_global (env, tmp);
    else
      return tmp;
  }
  
  gchar *
  gst_amc_jni_string_to_gchar (JNIEnv * env, jstring string, gboolean release)
  {
    const gchar *s = NULL;
    gchar *ret = NULL;
  
    s = (*env)->GetStringUTFChars (env, string, NULL);
    if (!s) {
      GST_ERROR ("Failed to convert string to UTF8");
      goto done;
    }
  
    ret = g_strdup (s);
    (*env)->ReleaseStringUTFChars (env, string, s);
  
  done:
    if (release) {
      (*env)->DeleteLocalRef (env, string);
    }
    return ret;
  }
  
  /* getExceptionSummary() and getStackTrace() taken from Android's
   *   platform/libnativehelper/JNIHelp.cpp
   * Modified to work with normal C strings and without C++.
   *
   * Copyright (C) 2006 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  /*
   * Returns a human-readable summary of an exception object. The buffer will
   * be populated with the "binary" class name and, if present, the
   * exception message.
   */
  static gchar *
  getExceptionSummary (JNIEnv * env, jthrowable exception)
  {
    GString *gs = g_string_new ("");
    jclass exceptionClass = NULL, classClass = NULL;
    jmethodID classGetNameMethod, getMessage;
    jstring classNameStr = NULL, messageStr = NULL;
    const char *classNameChars, *messageChars;
  
    /* get the name of the exception's class */
    exceptionClass = (*env)->GetObjectClass (env, exception);
    classClass = (*env)->GetObjectClass (env, exceptionClass);
    classGetNameMethod =
        (*env)->GetMethodID (env, classClass, "getName", "()Ljava/lang/String;");
  
    classNameStr =
        (jstring) (*env)->CallObjectMethod (env, exceptionClass,
        classGetNameMethod);
  
    if (classNameStr == NULL) {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      g_string_append (gs, "<error getting class name>");
      goto done;
    }
  
    classNameChars = (*env)->GetStringUTFChars (env, classNameStr, NULL);
    if (classNameChars == NULL) {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      g_string_append (gs, "<error getting class name UTF-8>");
      goto done;
    }
  
    g_string_append (gs, classNameChars);
  
    (*env)->ReleaseStringUTFChars (env, classNameStr, classNameChars);
  
    /* if the exception has a detail message, get that */
    getMessage =
        (*env)->GetMethodID (env, exceptionClass, "getMessage",
        "()Ljava/lang/String;");
    messageStr = (jstring) (*env)->CallObjectMethod (env, exception, getMessage);
    if (messageStr == NULL) {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      goto done;
    }
    g_string_append (gs, ": ");
  
    messageChars = (*env)->GetStringUTFChars (env, messageStr, NULL);
    if (messageChars != NULL) {
      g_string_append (gs, messageChars);
      (*env)->ReleaseStringUTFChars (env, messageStr, messageChars);
    } else {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      g_string_append (gs, "<error getting message>");
    }
  
  done:
    if (exceptionClass)
      (*env)->DeleteLocalRef (env, exceptionClass);
    if (classClass)
      (*env)->DeleteLocalRef (env, classClass);
    if (classNameStr)
      (*env)->DeleteLocalRef (env, classNameStr);
    if (messageStr)
      (*env)->DeleteLocalRef (env, messageStr);
  
    return g_string_free (gs, FALSE);
  }
  
  /*
   * Returns an exception (with stack trace) as a string.
   */
  static gchar *
  getStackTrace (JNIEnv * env, jthrowable exception)
  {
    GString *gs = g_string_new ("");
    jclass stringWriterClass = NULL, printWriterClass = NULL;
    jclass exceptionClass = NULL;
    jmethodID stringWriterCtor, stringWriterToStringMethod;
    jmethodID printWriterCtor, printStackTraceMethod;
    jobject stringWriter = NULL, printWriter = NULL;
    jstring messageStr = NULL;
    const char *utfChars;
  
    stringWriterClass = (*env)->FindClass (env, "java/io/StringWriter");
  
    if (stringWriterClass == NULL) {
      g_string_append (gs, "<error getting java.io.StringWriter class>");
      goto done;
    }
  
    stringWriterCtor =
        (*env)->GetMethodID (env, stringWriterClass, "<init>", "()V");
    stringWriterToStringMethod =
        (*env)->GetMethodID (env, stringWriterClass, "toString",
        "()Ljava/lang/String;");
  
    printWriterClass = (*env)->FindClass (env, "java/io/PrintWriter");
    if (printWriterClass == NULL) {
      g_string_append (gs, "<error getting java.io.PrintWriter class>");
      goto done;
    }
  
    printWriterCtor =
        (*env)->GetMethodID (env, printWriterClass, "<init>",
        "(Ljava/io/Writer;)V");
    stringWriter = (*env)->NewObject (env, stringWriterClass, stringWriterCtor);
    if (stringWriter == NULL) {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      g_string_append (gs, "<error creating new StringWriter instance>");
      goto done;
    }
  
    printWriter =
        (*env)->NewObject (env, printWriterClass, printWriterCtor, stringWriter);
    if (printWriter == NULL) {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      g_string_append (gs, "<error creating new PrintWriter instance>");
      goto done;
    }
  
    exceptionClass = (*env)->GetObjectClass (env, exception);
    printStackTraceMethod =
        (*env)->GetMethodID (env, exceptionClass, "printStackTrace",
        "(Ljava/io/PrintWriter;)V");
    (*env)->CallVoidMethod (env, exception, printStackTraceMethod, printWriter);
    if ((*env)->ExceptionCheck (env)) {
      (*env)->ExceptionClear (env);
      g_string_append (gs, "<exception while printing stack trace>");
      goto done;
    }
  
    messageStr = (jstring) (*env)->CallObjectMethod (env, stringWriter,
        stringWriterToStringMethod);
    if (messageStr == NULL) {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      g_string_append (gs, "<failed to call StringWriter.toString()>");
      goto done;
    }
  
    utfChars = (*env)->GetStringUTFChars (env, messageStr, NULL);
    if (utfChars == NULL) {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      g_string_append (gs, "<failed to get UTF chars for message>");
      goto done;
    }
  
    g_string_append (gs, utfChars);
  
    (*env)->ReleaseStringUTFChars (env, messageStr, utfChars);
  
  done:
    if (stringWriterClass)
      (*env)->DeleteLocalRef (env, stringWriterClass);
    if (printWriterClass)
      (*env)->DeleteLocalRef (env, printWriterClass);
    if (exceptionClass)
      (*env)->DeleteLocalRef (env, exceptionClass);
    if (stringWriter)
      (*env)->DeleteLocalRef (env, stringWriter);
    if (printWriter)
      (*env)->DeleteLocalRef (env, printWriter);
    if (messageStr)
      (*env)->DeleteLocalRef (env, messageStr);
  
    return g_string_free (gs, FALSE);
  }
  
  static JNIEnv *
  gst_amc_jni_attach_current_thread (void)
  {
    JNIEnv *env;
    JavaVMAttachArgs args;
    gint ret;
  
    GST_DEBUG ("Attaching thread %p", g_thread_self ());
    args.version = JNI_VERSION_1_6;
    args.name = NULL;
    args.group = NULL;
  
    if ((ret = (*java_vm)->AttachCurrentThread (java_vm, &env, &args)) != JNI_OK) {
      GST_ERROR ("Failed to attach current thread: %d", ret);
      return NULL;
    }
  
    return env;
  }
  
  static void
  gst_amc_jni_detach_current_thread (void *env)
  {
    gint ret;
  
    GST_DEBUG ("Detaching thread %p", g_thread_self ());
    if ((ret = (*java_vm)->DetachCurrentThread (java_vm)) != JNI_OK) {
      GST_DEBUG ("Failed to detach current thread: %d", ret);
    }
  }
  
  static JavaVM *
  get_application_java_vm (void)
  {
    GModule *module = NULL;
    JavaVM *(*get_java_vm) (void);
    JavaVM *vm = NULL;
  
    module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
  
    if (!module) {
      return NULL;
    }
  
    if (g_module_symbol (module, "gst_android_get_java_vm",
            (gpointer *) & get_java_vm) && get_java_vm) {
      vm = get_java_vm ();
    }
  
    g_module_close (module);
  
    return vm;
  }
  
  static gboolean
  check_nativehelper (void)
  {
    GModule *module;
    void **jni_invocation = NULL;
    gboolean ret = FALSE;
  
    module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
    if (!module)
      return ret;
  
    /* Check if libnativehelper is loaded in the process and if
     * it has these awful wrappers for JNI_CreateJavaVM and
     * JNI_GetCreatedJavaVMs that crash the app if you don't
     * create a JniInvocation instance first. If it isn't we
     * just fail here and don't initialize anything.
     * See this code for reference:
     * https://android.googlesource.com/platform/libnativehelper/+/master/JniInvocation.cpp
     */
    if (!g_module_symbol (module, "_ZN13JniInvocation15jni_invocation_E",
            (gpointer *) & jni_invocation)) {
      ret = TRUE;
    } else {
      ret = (jni_invocation != NULL && *jni_invocation != NULL);
    }
  
    g_module_close (module);
  
    return ret;
  }
  
  static gboolean
  load_java_module (const gchar * name)
  {
    java_module = g_module_open (name, G_MODULE_BIND_LOCAL);
    if (!java_module)
      goto load_failed;
  
    if (!g_module_symbol (java_module, "JNI_CreateJavaVM",
            (gpointer *) & create_java_vm)) {
      GST_ERROR ("Could not find 'JNI_CreateJavaVM' in '%s': %s",
          GST_STR_NULL (name), g_module_error ());
      create_java_vm = NULL;
    }
  
    if (!g_module_symbol (java_module, "JNI_GetCreatedJavaVMs",
            (gpointer *) & get_created_java_vms))
      goto symbol_error;
  
    return TRUE;
  
  load_failed:
    {
      GST_ERROR ("Failed to load Java module '%s': %s", GST_STR_NULL (name),
          g_module_error ());
      return FALSE;
    }
  symbol_error:
    {
      GST_ERROR ("Failed to locate required JNI symbols in '%s': %s",
          GST_STR_NULL (name), g_module_error ());
      g_module_close (java_module);
      java_module = NULL;
      return FALSE;
    }
  }
  
  static gboolean
  check_application_class_loader (void)
  {
    gboolean ret = TRUE;
    GModule *module = NULL;
  
    module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
    if (!module) {
      return FALSE;
    }
    if (!g_module_symbol (module, "gst_android_get_application_class_loader",
            (gpointer *) & get_class_loader)) {
      ret = FALSE;
    }
  
    g_module_close (module);
  
    return ret;
  }
  
  static gboolean
  initialize_classes (void)
  {
    JNIEnv *env;
    GError *err = NULL;
  
    env = gst_amc_jni_get_env ();
  
    java_nio_buffer.klass = gst_amc_jni_get_class (env, &err, "java/nio/Buffer");
    if (!java_nio_buffer.klass) {
      GST_ERROR ("Failed to get java.nio.Buffer class: %s", err->message);
      g_clear_error (&err);
      return FALSE;
    }
  
    java_nio_buffer.get_limit =
        gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "limit",
        "()I");
    if (!java_nio_buffer.get_limit) {
      GST_ERROR ("Failed to get java.nio.Buffer limit(): %s", err->message);
      g_clear_error (&err);
      return FALSE;
    }
  
    java_nio_buffer.get_position =
        gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "position",
        "()I");
    if (!java_nio_buffer.get_position) {
      GST_ERROR ("Failed to get java.nio.Buffer position(): %s", err->message);
      g_clear_error (&err);
      return FALSE;
    }
  
    java_nio_buffer.set_limit =
        gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "limit",
        "(I)Ljava/nio/Buffer;");
    if (!java_nio_buffer.set_limit) {
      GST_ERROR ("Failed to get java.nio.Buffer limit(): %s", err->message);
      g_clear_error (&err);
      return FALSE;
    }
  
    java_nio_buffer.set_position =
        gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "position",
        "(I)Ljava/nio/Buffer;");
    if (!java_nio_buffer.set_position) {
      GST_ERROR ("Failed to get java.nio.Buffer position(): %s", err->message);
      g_clear_error (&err);
      return FALSE;
    }
  
    java_nio_buffer.clear =
        gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "clear",
        "()Ljava/nio/Buffer;");
    if (!java_nio_buffer.clear) {
      GST_ERROR ("Failed to get java.nio.Buffer clear(): %s", err->message);
      g_clear_error (&err);
      return FALSE;
    }
  
    if (!check_application_class_loader ()) {
      GST_ERROR ("Could not find application class loader provider");
      return FALSE;
    }
  
    return TRUE;
  }
  
  static gboolean
  gst_amc_jni_initialize_java_vm (void)
  {
    jsize n_vms;
    gint ret;
  
    if (java_vm) {
      GST_DEBUG ("Java VM already provided by the application");
      return initialize_classes ();
    }
  
    java_vm = get_application_java_vm ();
    if (java_vm) {
      GST_DEBUG ("Java VM successfully requested from the application");
      return initialize_classes ();
    }
  
    /* Returns TRUE if we can safely
     * a) get the current VMs and
     * b) start a VM if none is started yet
     *
     * FIXME: On Android >= 4.4 we won't be able to safely start a
     * VM on our own without using private C++ API!
     */
    if (!check_nativehelper ()) {
      GST_ERROR ("Can't safely check for VMs or start a VM");
      return FALSE;
    }
  
    if (!load_java_module (NULL)) {
      if (!load_java_module ("libdvm"))
        return FALSE;
    }
  
    n_vms = 0;
    if ((ret = get_created_java_vms (&java_vm, 1, &n_vms)) != JNI_OK)
      goto get_created_failed;
  
    if (n_vms > 0) {
      GST_DEBUG ("Successfully got existing Java VM %p", java_vm);
    } else if (create_java_vm) {
      JNIEnv *env;
      JavaVMInitArgs vm_args;
      JavaVMOption options[4];
  
      GST_DEBUG ("Found no existing Java VM, trying to start one");
  
      options[0].optionString = "-verbose:jni";
      options[1].optionString = "-verbose:gc";
      options[2].optionString = "-Xcheck:jni";
      options[3].optionString = "-Xdebug";
  
      vm_args.version = JNI_VERSION_1_4;
      vm_args.options = options;
      vm_args.nOptions = 4;
      vm_args.ignoreUnrecognized = JNI_TRUE;
      if ((ret = create_java_vm (&java_vm, &env, &vm_args)) != JNI_OK)
        goto create_failed;
      GST_DEBUG ("Successfully created Java VM %p", java_vm);
  
      started_java_vm = TRUE;
    } else {
      GST_ERROR ("JNI_CreateJavaVM not available");
      java_vm = NULL;
    }
  
    if (java_vm == NULL)
      return FALSE;
  
    return initialize_classes ();
  
  get_created_failed:
    {
      GST_ERROR ("Failed to get already created VMs: %d", ret);
      g_module_close (java_module);
      java_module = NULL;
      return FALSE;
    }
  create_failed:
    {
      GST_ERROR ("Failed to create a Java VM: %d", ret);
      g_module_close (java_module);
      java_module = NULL;
      return FALSE;
    }
  }
  
  static void
  gst_amc_jni_set_error_string (JNIEnv * env, GError ** err, GQuark domain,
      gint code, const gchar * message)
  {
    jthrowable exception;
  
    if (!err) {
      if ((*env)->ExceptionCheck (env))
        (*env)->ExceptionClear (env);
      return;
    }
  
    if ((*env)->ExceptionCheck (env)) {
      if ((exception = (*env)->ExceptionOccurred (env))) {
        gchar *exception_description, *exception_stacktrace;
  
        /* Clear exception so that we can call Java methods again */
        (*env)->ExceptionClear (env);
  
        exception_description = getExceptionSummary (env, exception);
        exception_stacktrace = getStackTrace (env, exception);
        g_set_error (err, domain, code, "%s: %s\n%s", message,
            exception_description, exception_stacktrace);
        g_free (exception_description);
        g_free (exception_stacktrace);
  
        (*env)->DeleteLocalRef (env, exception);
      } else {
        (*env)->ExceptionClear (env);
        g_set_error (err, domain, code, "%s", message);
      }
    } else {
      g_set_error (err, domain, code, "%s", message);
    }
  }
  
  G_GNUC_PRINTF (5, 6)
       void gst_amc_jni_set_error (JNIEnv * env, GError ** err, GQuark domain,
      gint code, const gchar * format, ...)
  {
    gchar *message;
    va_list var_args;
  
    va_start (var_args, format);
    message = g_strdup_vprintf (format, var_args);
    va_end (var_args);
  
    gst_amc_jni_set_error_string (env, err, domain, code, message);
  
    g_free (message);
  }
  
  static gpointer
  gst_amc_jni_initialize_internal (gpointer data)
  {
    pthread_key_create (&current_jni_env, gst_amc_jni_detach_current_thread);
    return gst_amc_jni_initialize_java_vm ()? GINT_TO_POINTER (1) : NULL;
  }
  
  /* Allow the application to set the Java VM */
  void
  gst_amc_jni_set_java_vm (JavaVM * vm)
  {
    GST_DEBUG ("Application provides Java VM %p", vm);
    java_vm = vm;
  }
  
  gboolean
  gst_amc_jni_initialize (void)
  {
    GOnce once = G_ONCE_INIT;
  
    g_once (&once, gst_amc_jni_initialize_internal, NULL);
    return once.retval != NULL;
  }
  
  JNIEnv *
  gst_amc_jni_get_env (void)
  {
    JNIEnv *env;
  
    if ((env = pthread_getspecific (current_jni_env)) == NULL) {
      env = gst_amc_jni_attach_current_thread ();
      pthread_setspecific (current_jni_env, env);
    }
  
    return env;
  }
  
  gboolean
  gst_amc_jni_is_vm_started (void)
  {
    return started_java_vm;
  }
  
  jclass
  gst_amc_jni_get_application_class (JNIEnv * env, const gchar * name,
      GError ** err)
  {
    jclass tmp = NULL;
    jclass class = NULL;
    jstring name_jstr = NULL;
  
    jobject class_loader = NULL;
    jclass class_loader_cls = NULL;
    jmethodID load_class_id = 0;
  
    GST_LOG ("attempting to retrieve class %s", name);
  
    if (!get_class_loader) {
      g_set_error (err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,
          "Could not retreive application class loader function");
      goto done;
    }
  
    class_loader = get_class_loader ();
    if (!class_loader) {
      g_set_error (err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,
          "Could not retreive application class loader");
      goto done;
    }
  
    class_loader_cls = (*env)->GetObjectClass (env, class_loader);
    if (!class_loader_cls) {
      g_set_error (err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,
          "Could not retreive application class loader java class");
      goto done;
    }
  
    load_class_id =
        gst_amc_jni_get_method_id (env, err, class_loader_cls, "loadClass",
        "(Ljava/lang/String;)Ljava/lang/Class;");
    if (!load_class_id) {
      goto done;
    }
  
    name_jstr = gst_amc_jni_string_from_gchar (env, err, FALSE, name);
    if (!name_jstr) {
      goto done;
    }
  
    if (gst_amc_jni_call_object_method (env, err, class_loader,
            load_class_id, &tmp, name_jstr)) {
      class = gst_amc_jni_object_make_global (env, tmp);
    }
  
  done:
    gst_amc_jni_object_local_unref (env, name_jstr);
    gst_amc_jni_object_local_unref (env, class_loader_cls);
  
    return class;
  }
  
  #define CALL_STATIC_TYPE_METHOD(_type, _name,  _jname)                                                     \
  gboolean gst_amc_jni_call_static_##_name##_method (JNIEnv *env, GError ** err, jclass klass, jmethodID methodID, _type * value, ...)   \
    {                                                                                                          \
      gboolean ret = TRUE;                                                                                     \
      va_list args;                                                                                            \
      va_start(args, value);                                                                                \
      *value = (*env)->CallStatic##_jname##MethodV(env, klass, methodID, args);                                \
      if ((*env)->ExceptionCheck (env)) {                                                                      \
        gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,                               \
            "Failed to call static Java method");                                                         \
        ret = FALSE;                                                                                           \
      }                                                                                                        \
      va_end(args);                                                                                            \
      return ret;                                                                                              \
    }
  
  CALL_STATIC_TYPE_METHOD (gboolean, boolean, Boolean);
  CALL_STATIC_TYPE_METHOD (gint8, byte, Byte);
  CALL_STATIC_TYPE_METHOD (gshort, short, Short);
  CALL_STATIC_TYPE_METHOD (gint, int, Int);
  CALL_STATIC_TYPE_METHOD (gchar, char, Char);
  CALL_STATIC_TYPE_METHOD (gint64, long, Long);
  CALL_STATIC_TYPE_METHOD (gfloat, float, Float);
  CALL_STATIC_TYPE_METHOD (gdouble, double, Double);
  CALL_STATIC_TYPE_METHOD (jobject, object, Object);
  
  gboolean
  gst_amc_jni_call_static_void_method (JNIEnv * env, GError ** err, jclass klass,
      jmethodID methodID, ...)
  {
    gboolean ret = TRUE;
    va_list args;
    va_start (args, methodID);
  
    (*env)->CallStaticVoidMethodV (env, klass, methodID, args);
    if ((*env)->ExceptionCheck (env)) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to call static Java method");
      ret = FALSE;
    }
    va_end (args);
    return ret;
  }
  
  #define CALL_TYPE_METHOD(_type, _name,  _jname)                                                              \
  gboolean gst_amc_jni_call_##_name##_method (JNIEnv *env, GError ** err, jobject obj, jmethodID methodID, _type *value, ...)   \
    {                                                                                                          \
      gboolean ret = TRUE;                                                                                     \
      va_list args;                                                                                            \
      va_start(args, value);                                                                                \
      *value = (*env)->Call##_jname##MethodV(env, obj, methodID, args);                                        \
      if ((*env)->ExceptionCheck (env)) {                                                                      \
        gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,                               \
            "Failed to call Java method");                                                                \
        ret = FALSE;                                                                                           \
      }                                                                                                        \
      va_end(args);                                                                                            \
      return ret;                                                                                              \
    }
  
  CALL_TYPE_METHOD (gboolean, boolean, Boolean);
  CALL_TYPE_METHOD (gint8, byte, Byte);
  CALL_TYPE_METHOD (gshort, short, Short);
  CALL_TYPE_METHOD (gint, int, Int);
  CALL_TYPE_METHOD (gchar, char, Char);
  CALL_TYPE_METHOD (gint64, long, Long);
  CALL_TYPE_METHOD (gfloat, float, Float);
  CALL_TYPE_METHOD (gdouble, double, Double);
  CALL_TYPE_METHOD (jobject, object, Object);
  
  gboolean
  gst_amc_jni_call_void_method (JNIEnv * env, GError ** err, jobject obj,
      jmethodID methodID, ...)
  {
    gboolean ret = TRUE;
    va_list args;
    va_start (args, methodID);
  
    (*env)->CallVoidMethodV (env, obj, methodID, args);
    if ((*env)->ExceptionCheck (env)) {
      gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
          GST_LIBRARY_ERROR_FAILED, "Failed to call Java method");
      ret = FALSE;
    }
    va_end (args);
    return ret;
  }
  
  #define GET_TYPE_FIELD(_type, _name, _jname)                                               \
  gboolean gst_amc_jni_get_##_name##_field (JNIEnv *env, GError ** err, jobject obj, jfieldID fieldID, _type *value)   \
    {                                                                                                 \
      gboolean ret = TRUE;                                                                            \
                                                                                                      \
      *value = (*env)->Get##_jname##Field(env, obj, fieldID);                                         \
      if ((*env)->ExceptionCheck (env)) {                                                             \
        gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,                      \
            "Failed to get Java field");                                                         \
        ret = FALSE;                                                                                  \
      }                                                                                               \
      return ret;                                                                                     \
    }
  
  GET_TYPE_FIELD (gboolean, boolean, Boolean);
  GET_TYPE_FIELD (gint8, byte, Byte);
  GET_TYPE_FIELD (gshort, short, Short);
  GET_TYPE_FIELD (gint, int, Int);
  GET_TYPE_FIELD (gchar, char, Char);
  GET_TYPE_FIELD (gint64, long, Long);
  GET_TYPE_FIELD (gfloat, float, Float);
  GET_TYPE_FIELD (gdouble, double, Double);
  GET_TYPE_FIELD (jobject, object, Object);
  
  #define GET_STATIC_TYPE_FIELD(_type, _name, _jname)                                               \
  gboolean gst_amc_jni_get_static_##_name##_field (JNIEnv *env, GError ** err, jclass klass, jfieldID fieldID, _type *value)   \
    {                                                                                                 \
      gboolean ret = TRUE;                                                                            \
                                                                                                      \
      *value = (*env)->GetStatic##_jname##Field(env, klass, fieldID);                                 \
      if ((*env)->ExceptionCheck (env)) {                                                             \
        gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,                      \
            "Failed to get static Java field");                                                  \
        ret = FALSE;                                                                                  \
      }                                                                                               \
      return ret;                                                                                     \
    }
  
  GET_STATIC_TYPE_FIELD (gboolean, boolean, Boolean);
  GET_STATIC_TYPE_FIELD (gint8, byte, Byte);
  GET_STATIC_TYPE_FIELD (gshort, short, Short);
  GET_STATIC_TYPE_FIELD (gint, int, Int);
  GET_STATIC_TYPE_FIELD (gchar, char, Char);
  GET_STATIC_TYPE_FIELD (gint64, long, Long);
  GET_STATIC_TYPE_FIELD (gfloat, float, Float);
  GET_STATIC_TYPE_FIELD (gdouble, double, Double);
  GET_STATIC_TYPE_FIELD (jobject, object, Object);
  
  gboolean
  gst_amc_jni_get_buffer_array (JNIEnv * env, GError ** err, jobject array,
      GstAmcBuffer ** buffers, gsize * n_buffers)
  {
    jsize i;
  
    *n_buffers = (*env)->GetArrayLength (env, array);
    *buffers = g_new0 (GstAmcBuffer, *n_buffers);
  
    for (i = 0; i < *n_buffers; i++) {
      jobject buffer = NULL;
  
      buffer = (*env)->GetObjectArrayElement (env, array, i);
      if ((*env)->ExceptionCheck (env)) {
        gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
            GST_LIBRARY_ERROR_FAILED, "Failed to get buffer %d", i);
        goto error;
      }
  
      /* NULL buffers are not a problem and are happening when we configured
       * a surface as input/output */
      if (!buffer)
        continue;
  
      (*buffers)[i].object = gst_amc_jni_object_make_global (env, buffer);
      if (!(*buffers)[i].object) {
        gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
            GST_LIBRARY_ERROR_FAILED,
            "Failed to create global buffer reference %d", i);
        goto error;
      }
  
      (*buffers)[i].data =
          (*env)->GetDirectBufferAddress (env, (*buffers)[i].object);
      if (!(*buffers)[i].data) {
        gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
            GST_LIBRARY_ERROR_FAILED, "Failed to get buffer address %d", i);
        goto error;
      }
      (*buffers)[i].size =
          (*env)->GetDirectBufferCapacity (env, (*buffers)[i].object);
    }
  
    return TRUE;
  
  error:
    if (*buffers)
      gst_amc_jni_free_buffer_array (env, *buffers, *n_buffers);
    *buffers = NULL;
    *n_buffers = 0;
    return FALSE;
  }
  
  void
  gst_amc_jni_free_buffer_array (JNIEnv * env, GstAmcBuffer * buffers,
      gsize n_buffers)
  {
    jsize i;
  
    g_return_if_fail (buffers != NULL);
  
    for (i = 0; i < n_buffers; i++) {
      if (buffers[i].object)
        gst_amc_jni_object_unref (env, buffers[i].object);
    }
    g_free (buffers);
  }
  
  void
  gst_amc_buffer_free (GstAmcBuffer * buffer)
  {
    JNIEnv *env;
  
    g_return_if_fail (buffer != NULL);
  
    env = gst_amc_jni_get_env ();
  
    if (buffer->object)
      gst_amc_jni_object_unref (env, buffer->object);
    g_free (buffer);
  }
  
  GstAmcBuffer *
  gst_amc_buffer_copy (GstAmcBuffer * buffer)
  {
    JNIEnv *env;
    GstAmcBuffer *ret;
  
    g_return_val_if_fail (buffer != NULL, NULL);
  
    env = gst_amc_jni_get_env ();
  
    ret = g_new0 (GstAmcBuffer, 1);
  
    ret->object = gst_amc_jni_object_ref (env, buffer->object);
    ret->data = buffer->data;
    ret->size = buffer->size;
  
    return ret;
  }
  
  gboolean
  gst_amc_buffer_get_position_and_limit (GstAmcBuffer * buffer, GError ** err,
      gint * position, gint * limit)
  {
    JNIEnv *env;
  
    g_return_val_if_fail (buffer != NULL, FALSE);
    g_return_val_if_fail (buffer->object != NULL, FALSE);
  
    env = gst_amc_jni_get_env ();
  
    if (!gst_amc_jni_call_int_method (env, err, buffer->object,
            java_nio_buffer.get_position, position))
      return FALSE;
  
    if (!gst_amc_jni_call_int_method (env, err, buffer->object,
            java_nio_buffer.get_limit, limit))
      return FALSE;
  
    return TRUE;
  }
  
  gboolean
  gst_amc_buffer_set_position_and_limit (GstAmcBuffer * buffer, GError ** err,
      gint position, gint limit)
  {
    JNIEnv *env;
    jobject tmp;
  
    g_return_val_if_fail (buffer != NULL, FALSE);
    g_return_val_if_fail (buffer->object != NULL, FALSE);
  
    env = gst_amc_jni_get_env ();
  
    if (!gst_amc_jni_call_object_method (env, err, buffer->object,
            java_nio_buffer.set_limit, &tmp, limit))
      return FALSE;
  
    gst_amc_jni_object_local_unref (env, tmp);
  
    if (!gst_amc_jni_call_object_method (env, err, buffer->object,
            java_nio_buffer.set_position, &tmp, position))
      return FALSE;
  
    gst_amc_jni_object_local_unref (env, tmp);
  
    return TRUE;
  }
  
  gboolean
  gst_amc_buffer_clear (GstAmcBuffer * buffer, GError ** err)
  {
    JNIEnv *env;
    jobject tmp;
  
    g_return_val_if_fail (buffer != NULL, FALSE);
    g_return_val_if_fail (buffer->object != NULL, FALSE);
  
    env = gst_amc_jni_get_env ();
  
    if (!gst_amc_jni_call_object_method (env, err, buffer->object,
            java_nio_buffer.clear, &tmp))
      return FALSE;
  
    gst_amc_jni_object_local_unref (env, tmp);
  
    return TRUE;
  }
  