/**
 * @copyright
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you 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.
 * ====================================================================
 * @endcopyright
 *
 * @file JNIUtil.cpp
 * @brief Implementation of the class JNIUtil
 */

/* Include apr.h first, or INT64_C won't be defined properly on some C99
   compilers, when other headers include <stdint.h> before defining some
   macros.

   See apr.h for the ugly details */
#include <apr.h>

#include "JNIUtil.h"
#include "Array.h"

#include <sstream>
#include <vector>
#include <locale.h>

#include <apr_strings.h>
#include <apr_tables.h>
#include <apr_general.h>
#include <apr_lib.h>
#include <apr_file_info.h>
#include <apr_time.h>

#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
#include "svn_ra.h"
#include "svn_utf.h"
#include "svn_wc.h"
#include "svn_dso.h"
#include "svn_path.h"
#include "svn_cache_config.h"
#include "private/svn_atomic.h"
#include "private/svn_utf_private.h"
#include "svn_private_config.h"

#include "SVNBase.h"
#include "JNIMutex.h"
#include "JNICriticalSection.h"
#include "JNIStringHolder.h"
#include "Pool.h"


#include "jniwrapper/jni_env.hpp"

// Static members of JNIUtil are allocated here.
apr_pool_t *JNIUtil::g_pool = NULL;
std::list<SVNBase*> JNIUtil::g_finalizedObjects;
JNIMutex *JNIUtil::g_finalizedObjectsMutex = NULL;
JNIMutex *JNIUtil::g_logMutex = NULL;
JNIMutex *JNIUtil::g_configMutex = NULL;
bool JNIUtil::g_initException;
int JNIUtil::g_logLevel = JNIUtil::noLog;
std::ofstream JNIUtil::g_logStream;

/* The error code we will use to signal a Java exception */
static const apr_status_t
SVN_ERR_JAVAHL_WRAPPED = SVN_ERR_MALFUNC_CATEGORY_START
                         + SVN_ERR_CATEGORY_SIZE - 10;

/**
 * Return the JNI environment to use
 * @return the JNI environment
 */
JNIEnv *JNIUtil::getEnv()
{
  return Java::Env().get();
}

/**
 * Initialize the environment for all requests.
 * @param env   the JNI environment for this request
 */
bool JNIUtil::JNIInit(JNIEnv *env)
{
  // Clear all standing exceptions.
  env->ExceptionClear();

  // Lock the list of finalized objects.
  JNICriticalSection cs(*g_finalizedObjectsMutex) ;
  if (isExceptionThrown())
    return false;

  // Delete all finalized, but not yet deleted objects.
  for (std::list<SVNBase*>::iterator it = g_finalizedObjects.begin();
       it != g_finalizedObjects.end();
       ++it)
    {
      delete *it;
    }
  g_finalizedObjects.clear();

  return true;
}

/* Forwarder for calling JNIGlobalInit from JNI_OnLoad(). */
bool initialize_jni_util(JNIEnv *env)
{
  return JNIUtil::JNIGlobalInit(env);
}

namespace {

volatile svn_atomic_t *gentle_crash_write_loc = NULL;

svn_error_t *
gently_crash_the_jvm(svn_boolean_t can_return,
                     const char *file, int line, const char *expr)
{
  if (!can_return)
    {
      // Try not to abort; aborting prevents the JVM from creating
      // a crash log, which is oh so useful for debugging.
      // We can't just raise a SEGV signal, either, because it will
      // be not be caught in the context that we're interested in
      // getting the stack trace from.

      // Try reading from and writing to the zero page
      const svn_atomic_t zeropage = svn_atomic_read(gentle_crash_write_loc);
      svn_atomic_set(gentle_crash_write_loc, zeropage);
    }

  // Forward to the standard malfunction handler, which does call
  // abort when !can_return; this will only happen if the write to the
  // zero page did not cause a SEGV.
  return svn_error_raise_on_malfunction(can_return, file, line, expr);
}
} // Anonymous namespace

/**
 * Initialize the environment for all requests.
 * This method must be called in a single-threaded context.
 * @param env   the JNI environment for this request
 */
bool JNIUtil::JNIGlobalInit(JNIEnv *env)
{
  svn_error_t *err;

  /* This has to happen before any pools are created. */
  if ((err = svn_dso_initialize2()))
    {
      if (stderr && err->message)
        fprintf(stderr, "%s", err->message);

      svn_error_clear(err);
      return FALSE;
    }

  /* Create our top-level pool.
     N.B.: APR was initialized by JNI_OnLoad. */
  g_pool = svn_pool_create(NULL);

  apr_allocator_t* allocator = apr_pool_allocator_get(g_pool);

  if (allocator)
    {
      /* Keep a maximum of 1 free block, to release memory back to the JVM
         (and other modules). */
      apr_allocator_max_free_set(allocator, 1);
    }

  svn_utf_initialize2(FALSE, g_pool); /* Optimize character conversions */

  // Initialize the libraries we use
  err = svn_fs_initialize(g_pool);
  if (!err)
    err = svn_ra_initialize(g_pool);
  if (err)
    {
      if (stderr && err->message)
        fprintf(stderr, "%s", err->message);

      svn_error_clear(err);
      return FALSE;
    }

  /* We shouldn't fill the JVMs memory with FS cache data unless
     explicitly requested. And we don't either, because the caches get
     allocated outside the JVM heap. Duh. */
  {
    svn_cache_config_t settings = *svn_cache_config_get();
    settings.single_threaded = FALSE;
    svn_cache_config_set(&settings);
  }

#ifdef ENABLE_NLS
#ifdef WIN32
  {
    WCHAR ucs2_path[MAX_PATH];
    const char *utf8_path;
    const char *internal_path;
    svn_error_t *err;
    apr_pool_t *pool = svn_pool_create(g_pool);

    /* get dll name - our locale info will be in '../share/locale' */
    HINSTANCE moduleHandle = GetModuleHandle("libsvnjavahl-1");
    GetModuleFileNameW(moduleHandle, ucs2_path,
                       sizeof(ucs2_path) / sizeof(ucs2_path[0]));
    err = svn_utf__win32_utf16_to_utf8(&utf8_path, ucs2_path, NULL, pool);
    if (err)
      {
        if (stderr)
          svn_handle_error2(err, stderr, false, "svn: ");
        svn_error_clear(err);
        return false;
      }

    internal_path = svn_dirent_internal_style(utf8_path, pool);
    /* get base path name */
    internal_path = svn_dirent_dirname(internal_path, pool);
    internal_path = svn_dirent_join(internal_path, SVN_LOCALE_RELATIVE_PATH,
                                  pool);
    bindtextdomain(PACKAGE_NAME, internal_path);
    svn_pool_destroy(pool);
  }
#else
  bindtextdomain(PACKAGE_NAME, SVN_LOCALE_DIR);
#endif
#endif

#if defined(WIN32) || defined(__CYGWIN__)
  /* See http://svn.apache.org/repos/asf/subversion/trunk/notes/asp-dot-net-hack.txt */
  /* ### This code really only needs to be invoked by consumers of
     ### the libsvn_wc library, which basically means SVNClient. */
  if (getenv("SVN_ASP_DOT_NET_HACK"))
    {
      err = svn_wc_set_adm_dir("_svn", g_pool);
      if (err)
        {
          if (stderr)
            {
              fprintf(stderr,
                      "%s: error: SVN_ASP_DOT_NET_HACK failed: %s\n",
                      "svnjavahl", err->message);
            }
          svn_error_clear(err);
          return FALSE;
        }
    }
#endif

  svn_error_set_malfunction_handler(svn_error_raise_on_malfunction);

  // Build all mutexes.
  g_finalizedObjectsMutex = new JNIMutex(g_pool);
  if (isExceptionThrown())
    return false;

  g_logMutex = new JNIMutex(g_pool);
  if (isExceptionThrown())
    return false;

  g_configMutex = new JNIMutex(g_pool);
  if (isExceptionThrown())
    return false;

  // Set a malfunction handler that tries not to call abort, because
  // that would prevent the JVM from creating a crash and stack log file.
  svn_error_set_malfunction_handler(gently_crash_the_jvm);

  return true;
}

/**
 * Returns the global (not request specific) pool.
 * @return global pool
 */
apr_pool_t *JNIUtil::getPool()
{
  return g_pool;
}

void JNIUtil::raiseThrowable(const char *name, const char *message)
{
  if (getLogLevel() >= errorLog)
    {
      JNICriticalSection cs(*g_logMutex);
      g_logStream << "Throwable raised <" << message << ">" << std::endl;
    }

  JNIEnv *env = getEnv();
  jclass clazz = env->FindClass(name);
  if (isJavaExceptionThrown())
    return;

  env->ThrowNew(clazz, message);
}

void
JNIUtil::throwNativeException(const char *className, const char *msg,
                              const char *source, int aprErr)
{
  JNIEnv *env = getEnv();
  jclass clazz = env->FindClass(className);

  // Create a local frame for our references
  env->PushLocalFrame(LOCAL_FRAME_SIZE);
  if (JNIUtil::isJavaExceptionThrown())
    return;

  if (getLogLevel() >= exceptionLog)
    {
      JNICriticalSection cs(*g_logMutex);
      g_logStream << "Subversion JavaHL exception thrown, message:<";
      g_logStream << msg << ">";
      if (source)
        g_logStream << " source:<" << source << ">";
      if (aprErr != -1)
        g_logStream << " apr-err:<" << aprErr << ">";
      g_logStream << std::endl;
    }
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NOTHING();

  jstring jmessage = makeJString(msg);
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NOTHING();
  jstring jsource = makeJString(source);
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NOTHING();

  jmethodID mid = env->GetMethodID(clazz, "<init>",
                                   "(Ljava/lang/String;Ljava/lang/String;I)V");
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NOTHING();
  jobject nativeException = env->NewObject(clazz, mid, jmessage, jsource,
                                           static_cast<jint>(aprErr));
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NOTHING();

  env->Throw(static_cast<jthrowable>(env->PopLocalFrame(nativeException)));
}

void
JNIUtil::putErrorsInTrace(svn_error_t *err,
                          std::vector<jobject> &stackTrace)
{
  if (!err)
    return;

  JNIEnv *env = getEnv();

  // First, put all our child errors in the stack trace
  putErrorsInTrace(err->child, stackTrace);

  // Now, put our own error in the stack trace
  jclass stClazz = env->FindClass("java/lang/StackTraceElement");
  if (isJavaExceptionThrown())
    return;

  static jmethodID ctor_mid = 0;
  if (ctor_mid == 0)
    {
      ctor_mid = env->GetMethodID(stClazz, "<init>",
                                  "(Ljava/lang/String;Ljava/lang/String;"
                                  "Ljava/lang/String;I)V");
      if (isJavaExceptionThrown())
        return;
    }

  jstring jdeclClass = makeJString("native");
  if (isJavaExceptionThrown())
    return;

  char *tmp_path;
  char *path = svn_dirent_dirname(err->file, err->pool);
  while ((tmp_path = strchr(path, '/')))
    *tmp_path = '.';

  jstring jmethodName = makeJString(path);
  if (isJavaExceptionThrown())
    return;

  jstring jfileName = makeJString(svn_dirent_basename(err->file, err->pool));
  if (isJavaExceptionThrown())
    return;

  jobject jelement = env->NewObject(stClazz, ctor_mid, jdeclClass, jmethodName,
                                    jfileName, (jint) err->line);

  stackTrace.push_back(jelement);

  env->DeleteLocalRef(stClazz);
  env->DeleteLocalRef(jdeclClass);
  env->DeleteLocalRef(jmethodName);
  env->DeleteLocalRef(jfileName);
}

namespace {
struct MessageStackItem
{
  apr_status_t m_code;
  std::string m_message;
  bool m_generic;

  MessageStackItem(apr_status_t code, const char* message,
                   bool generic = false)
    : m_code(code),
      m_message(message),
      m_generic(generic)
    {}
};
typedef std::vector<MessageStackItem> ErrorMessageStack;

/*
 * Build the error message from the svn error into buffer.  This
 * method iterates through all the chained errors
 *
 * @param err               the subversion error
 * @param buffer            the buffer where the formated error message will
 *                          be stored
 * @return An array of error codes and messages
 */
ErrorMessageStack assemble_error_message(
    svn_error_t *err, std::string &result)
{
  // buffer for a single error message
  char errbuf[1024];
  apr_status_t parent_apr_err = 0;
  ErrorMessageStack message_stack;

  /* Pretty-print the error */
  /* Note: we can also log errors here someday. */

  for (int depth = 0; err;
       ++depth, parent_apr_err = err->apr_err, err = err->child)
    {
      /* When we're recursing, don't repeat the top-level message if its
       * the same as before. */
      if ((depth == 0 || err->apr_err != parent_apr_err)
          && err->apr_err != SVN_ERR_JAVAHL_WRAPPED)
        {
          const char *message;
          /* Is this a Subversion-specific error code? */
          if ((err->apr_err > APR_OS_START_USEERR)
              && (err->apr_err <= APR_OS_START_CANONERR))
            message = svn_strerror(err->apr_err, errbuf, sizeof(errbuf));
          /* Otherwise, this must be an APR error code. */
          else
            {
              /* Messages coming from apr_strerror are in the native
                 encoding, it's a good idea to convert them to UTF-8. */
              apr_strerror(err->apr_err, errbuf, sizeof(errbuf));
              svn_error_t* utf8_err =
                svn_utf_cstring_to_utf8(&message, errbuf, err->pool);
              if (utf8_err)
                {
                  /* Use fuzzy transliteration instead. */
                  svn_error_clear(utf8_err);
                  message = svn_utf_cstring_from_utf8_fuzzy(errbuf, err->pool);
                }
            }

          message_stack.push_back(
              MessageStackItem(err->apr_err, message, true));
        }
      if (err->message)
        {
          message_stack.push_back(
              MessageStackItem(err->apr_err, err->message));
        }
    }

  for (ErrorMessageStack::const_iterator it = message_stack.begin();
       it != message_stack.end(); ++it)
    {
      if (!it->m_generic)
        result += "svn: ";
      result += it->m_message;
      result += '\n';
    }
  return message_stack;
}

jobject construct_Jmessage_stack(const ErrorMessageStack& message_stack)
{
  JNIEnv *env = JNIUtil::getEnv();
  env->PushLocalFrame(LOCAL_FRAME_SIZE);
  if (JNIUtil::isJavaExceptionThrown())
    return NULL;

  jclass list_clazz = env->FindClass("java/util/ArrayList");
  if (JNIUtil::isJavaExceptionThrown())
    POP_AND_RETURN_NULL;
  jmethodID mid = env->GetMethodID(list_clazz, "<init>", "(I)V");
  if (JNIUtil::isJavaExceptionThrown())
    POP_AND_RETURN_NULL;
  jmethodID add_mid = env->GetMethodID(list_clazz, "add",
                                       "(Ljava/lang/Object;)Z");
  if (JNIUtil::isJavaExceptionThrown())
    POP_AND_RETURN_NULL;
  jobject jlist = env->NewObject(list_clazz, mid, jint(message_stack.size()));
  if (JNIUtil::isJavaExceptionThrown())
    POP_AND_RETURN_NULL;

  jclass clazz = env->FindClass(JAVAHL_CLASS("/ClientException$ErrorMessage"));
  if (JNIUtil::isJavaExceptionThrown())
    POP_AND_RETURN_NULL;
  mid = env->GetMethodID(clazz, "<init>",
                         "(ILjava/lang/String;Z)V");
  if (JNIUtil::isJavaExceptionThrown())
    POP_AND_RETURN_NULL;

  for (ErrorMessageStack::const_iterator it = message_stack.begin();
       it != message_stack.end(); ++it)
    {
      jobject jmessage = JNIUtil::makeJString(it->m_message.c_str());
      if (JNIUtil::isJavaExceptionThrown())
        POP_AND_RETURN_NULL;
      jobject jitem = env->NewObject(clazz, mid,
                                     jint(it->m_code), jmessage,
                                     jboolean(it->m_generic));
      if (JNIUtil::isJavaExceptionThrown())
        POP_AND_RETURN_NULL;
      env->CallBooleanMethod(jlist, add_mid, jitem);
      if (JNIUtil::isJavaExceptionThrown())
        POP_AND_RETURN_NULL;

      env->DeleteLocalRef(jmessage);
      env->DeleteLocalRef(jitem);
    }
  return env->PopLocalFrame(jlist);
}
} // anonymous namespace

std::string JNIUtil::makeSVNErrorMessage(svn_error_t *err,
                                         jstring *jerror_message,
                                         jobject *jmessage_stack)
{
  // This function may be called with a pending Java exception.
  // It is incorrect to call Java methods (see code below) with a pending
  // exception. Stash it away until this function exits.
  StashException stash(getEnv());

  if (jerror_message)
    *jerror_message = NULL;
  if (jmessage_stack)
    *jmessage_stack = NULL;

  std::string buffer;
  err = svn_error_purge_tracing(err);
  if (err == NULL || err->apr_err == 0
      || !(jerror_message || jmessage_stack))
  return buffer;

  ErrorMessageStack message_stack = assemble_error_message(err, buffer);
  if (jerror_message)
    *jerror_message = makeJString(buffer.c_str());
  if (jmessage_stack)
    *jmessage_stack = construct_Jmessage_stack(message_stack);
  return buffer;
}

jthrowable JNIUtil::wrappedCreateClientException(svn_error_t *err, jthrowable jcause)
{
  jstring jmessage;
  jobject jstack;
  std::string msg = makeSVNErrorMessage(err, &jmessage, &jstack);
  if (JNIUtil::isJavaExceptionThrown())
    return NULL;

  std::string source;
#ifdef SVN_DEBUG
#ifndef SVN_ERR__TRACING
  if (err->file)
    {
      source = err->file;
      if (err->line > 0)
        {
          source += ':';
          source += err->line;
        }
    }
#endif
#endif

  if (!jcause)
    jcause = JNIUtil::unwrapJavaException(err);

  // Much of the following is stolen from throwNativeException().  As much as
  // we'd like to call that function, we need to do some manual stack
  // unrolling, so it isn't feasible.

  JNIEnv *env = getEnv();

  // Create a local frame for our references
  env->PushLocalFrame(LOCAL_FRAME_SIZE);
  if (JNIUtil::isJavaExceptionThrown())
    return NULL;

  jclass clazz = env->FindClass(JAVAHL_CLASS("/ClientException"));
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NULL;

  if (getLogLevel() >= exceptionLog)
    {
      JNICriticalSection cs(*g_logMutex);
      g_logStream << "Subversion JavaHL exception thrown, message:<";
      g_logStream << msg << ">";
      if (!source.empty())
        g_logStream << " source:<" << source << ">";
      if (err->apr_err != -1)
        g_logStream << " apr-err:<" << err->apr_err << ">";
      g_logStream << std::endl;
    }
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NULL;

  jstring jsource = (source.empty() ? NULL : makeJString(source.c_str()));
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NULL;

  jmethodID mid = env->GetMethodID(clazz, "<init>",
                                   "(Ljava/lang/String;"
                                   "Ljava/lang/Throwable;"
                                   "Ljava/lang/String;I"
                                   "Ljava/util/List;)V");
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NULL;
  jobject nativeException = env->NewObject(clazz, mid, jmessage, jcause,
                                           jsource, jint(err->apr_err),
                                           jstack);
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NULL;

#ifdef SVN_ERR__TRACING
  // Add all the C error stack trace information to the Java Exception

  // Get the standard stack trace, and vectorize it using the Array class.
  static jmethodID mid_gst = 0;
  if (mid_gst == 0)
    {
      mid_gst = env->GetMethodID(clazz, "getStackTrace",
                                 "()[Ljava/lang/StackTraceElement;");
      if (isJavaExceptionThrown())
        POP_AND_RETURN_NULL;
    }
  Array stackTraceArray((jobjectArray) env->CallObjectMethod(nativeException,
                                                             mid_gst));
  std::vector<jobject> oldStackTrace = stackTraceArray.vector();

  // Build the new stack trace elements from the chained errors.
  std::vector<jobject> newStackTrace;
  putErrorsInTrace(err, newStackTrace);

  // Join the new elements with the old ones
  for (std::vector<jobject>::const_iterator it = oldStackTrace.begin();
            it < oldStackTrace.end(); ++it)
    {
      newStackTrace.push_back(*it);
    }

  jclass stClazz = env->FindClass("java/lang/StackTraceElement");
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NULL;

  const jsize stSize = static_cast<jsize>(newStackTrace.size());
  if (stSize < 0 || stSize != newStackTrace.size())
    {
      env->ThrowNew(env->FindClass("java.lang.ArithmeticException"),
                    "Overflow converting C size_t to JNI jsize");
      POP_AND_RETURN_NULL;
    }
  jobjectArray jStackTrace = env->NewObjectArray(stSize, stClazz, NULL);
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NULL;

  int i = 0;
  for (std::vector<jobject>::const_iterator it = newStackTrace.begin();
       it < newStackTrace.end(); ++it)
    {
      env->SetObjectArrayElement(jStackTrace, i, *it);
      ++i;
    }

  // And put the entire trace back into the exception
  static jmethodID mid_sst = 0;
  if (mid_sst == 0)
    {
      mid_sst = env->GetMethodID(clazz, "setStackTrace",
                                 "([Ljava/lang/StackTraceElement;)V");
      if (isJavaExceptionThrown())
        POP_AND_RETURN_NULL;
    }
  env->CallVoidMethod(nativeException, mid_sst, jStackTrace);
  if (isJavaExceptionThrown())
    POP_AND_RETURN_NULL;
#endif

  return static_cast<jthrowable>(env->PopLocalFrame(nativeException));
}

jthrowable JNIUtil::createClientException(svn_error_t *err, jthrowable jcause)
{
  jthrowable jexc = NULL;
  try {
    jexc = wrappedCreateClientException(err, jcause);
  } catch (...) {
    svn_error_clear(err);
    throw;
  }
  svn_error_clear(err);
  return jexc;
}

void JNIUtil::handleSVNError(svn_error_t *err, jthrowable jcause)
{
  jthrowable jexc = createClientException(err, jcause);
  if (jexc)
    getEnv()->Throw(jexc);
}

void JNIUtil::putFinalizedClient(SVNBase *object)
{
  enqueueForDeletion(object);
}

void JNIUtil::enqueueForDeletion(SVNBase *object)
{
  JNICriticalSection cs(*g_finalizedObjectsMutex);
  if (!isExceptionThrown())
    g_finalizedObjects.push_back(object);
}

/**
 * Handle an apr error (those are not expected) by throwing an error.
 * @param error the apr error number
 * @param op the apr function returning the error
 */
void JNIUtil::handleAPRError(int error, const char *op)
{
  char buffer[2048];

  apr_snprintf(buffer, sizeof(buffer),
               _("an error occurred in function %s with return value %d"),
               op, error);

  throwError(buffer);
}

namespace {
const char* known_exception_to_cstring(apr_pool_t* pool)
{
  JNIEnv *env = JNIUtil::getEnv();

  // This function may be called with a pending Java exception.
  // It is incorrect to call Java methods (see code below) with a pending
  // exception. Stash it away until this function exits.
  jthrowable t = env->ExceptionOccurred();
  StashException stashed(env);

  jclass cls = env->GetObjectClass(t);

  jstring jclass_name;
  {
    jmethodID mid = env->GetMethodID(cls, "getClass", "()Ljava/lang/Class;");
    jobject clsobj = env->CallObjectMethod(t, mid);
    if (JNIUtil::isJavaExceptionThrown())
      return NULL;

    jclass basecls = env->GetObjectClass(clsobj);
    mid = env->GetMethodID(basecls, "getName", "()Ljava/lang/String;");
    jclass_name = (jstring) env->CallObjectMethod(clsobj, mid);
    if (JNIUtil::isJavaExceptionThrown())
      return NULL;
  }

  jstring jmessage;
  {
    jmethodID mid = env->GetMethodID(cls, "getMessage",
                                     "()Ljava/lang/String;");
    jmessage = (jstring) env->CallObjectMethod(t, mid);
    if (JNIUtil::isJavaExceptionThrown())
      return NULL;
  }

  JNIStringHolder class_name(jclass_name);
  if (jmessage)
    {
      JNIStringHolder message(jmessage);
      return apr_pstrcat(pool, class_name.c_str(), ": ", message.c_str(), NULL);
    }
  else
    return class_name.pstrdup(pool);
  // ### Conditionally add t.printStackTrace() to msg?
}

const char* exception_to_cstring(apr_pool_t* pool)
{
  const char *msg;
  if (JNIUtil::getEnv()->ExceptionCheck())
    {
      msg = known_exception_to_cstring(pool);
    }
  else
    {
      msg = NULL;
    }
  return msg;
}
} // anonymous namespace

const char *
JNIUtil::thrownExceptionToCString(SVN::Pool &in_pool)
{
  return exception_to_cstring(in_pool.getPool());
}

svn_error_t*
JNIUtil::checkJavaException(apr_status_t errorcode)
{
  if (!getEnv()->ExceptionCheck())
    return SVN_NO_ERROR;
  svn_error_t* err = svn_error_create(errorcode, NULL, NULL);
  const char* const msg = known_exception_to_cstring(err->pool);
  if (msg)
    err->message = apr_psprintf(err->pool, _("Java exception: %s"), msg);
  else
    err->message = _("Java exception");


  /* ### TODO: Use apr_pool_userdata_set() on the pool we just created
               for the error chain to keep track of the actual Java
               exception while the error is inside Subversion.

               Once the error chain re-enters JavaHL we can check
               if there is a true exception that we can add to the chain.

               If the error is cleared in Subversion (which may happen
               during composing error chains, etc.) the cleanup handler
               handles properly releasing the exception.

    apr_status_t
    apr_pool_userdata_set(const void *data,
                          const char *key,
                          apr_status_t (*cleanup)(void *),
                          apr_pool_t *pool)
   */
  return err;
}

/**
 * Create a Java string from a native UTF-8 string.
 * @param txt   native UTF-8 string
 * @return the Java string. It is a local reference, which should be deleted
 *         as soon a possible
 */
jstring JNIUtil::makeJString(const char *txt)
{
  if (txt == NULL)
    // A NULL pointer is equates to a null java.lang.String.
    return NULL;

  JNIEnv *env = getEnv();
  return env->NewStringUTF(txt);
}

/**
 * Initialite the log file.
 * @param level the log level
 * @param the name of the log file
 */
void JNIUtil::initLogFile(int level, jstring path)
{
  // lock this operation
  JNICriticalSection cs(*g_logMutex);
  if (g_logLevel > noLog) // if the log file has been opened
    g_logStream.close();

  // remember the log level
  g_logLevel = level;
  JNIStringHolder myPath(path);
  if (g_logLevel > noLog) // if a new log file is needed
    {
      // open it
      g_logStream.open(myPath, std::ios::app);
    }
}

/**
 * Returns the current log level.
 * @return the log level
 */
int JNIUtil::getLogLevel()
{
  return g_logLevel;
}

/**
 * Write a message to the log file if needed.
 * @param the log message
 */
void JNIUtil::logMessage(const char *message)
{
  // lock the log file
  JNICriticalSection cs(*g_logMutex);
  g_logStream << message << std::endl;
}

/**
 * Create a java.util.Date object from an apr time.
 * @param time  the apr time
 * @return the java.util.Date. This is a local reference.  Delete as
 *         soon as possible
 */
jobject JNIUtil::createDate(apr_time_t time)
{
  jlong javatime = time /1000;
  JNIEnv *env = getEnv();
  jclass clazz = env->FindClass("java/util/Date");
  if (isJavaExceptionThrown())
    return NULL;

  static jmethodID mid = 0;
  if (mid == 0)
    {
      mid = env->GetMethodID(clazz, "<init>", "(J)V");
      if (isJavaExceptionThrown())
        return NULL;
    }

  jobject ret = env->NewObject(clazz, mid, javatime);
  if (isJavaExceptionThrown())
    return NULL;

  env->DeleteLocalRef(clazz);

  return ret;
}

apr_time_t
JNIUtil::getDate(jobject jdate)
{
  JNIEnv *env = getEnv();
  jclass clazz = env->FindClass("java/util/Date");
  if (isJavaExceptionThrown())
    return 0;

  static jmethodID mid = 0;
  if (mid == 0)
    {
      mid = env->GetMethodID(clazz, "getTime", "()J");
      if (isJavaExceptionThrown())
        return 0;
    }

  jlong jmillis = env->CallLongMethod(jdate, mid);
  if (isJavaExceptionThrown())
    return 0;

  env->DeleteLocalRef(clazz);

  return jmillis * 1000;
}

/**
 * Create a Java byte array from an array of characters.
 * @param data      the character array
 * @param length    the number of characters in the array
 */
jbyteArray JNIUtil::makeJByteArray(const void *data, int length)
{
  // a NULL will create no Java array
  if (!data)
    return NULL;

  JNIEnv *env = getEnv();

  // Allocate the Java array.
  jbyteArray ret = env->NewByteArray(length);
  if (isJavaExceptionThrown() || ret == NULL)
      return NULL;

  // Access the bytes.
  jbyte *retdata = env->GetByteArrayElements(ret, NULL);
  if (isJavaExceptionThrown())
    return NULL;

  // Copy the bytes.
  memcpy(retdata, data, length);

  // Release the bytes.
  env->ReleaseByteArrayElements(ret, retdata, 0);
  if (isJavaExceptionThrown())
    return NULL;

  return ret;
}

/**
 * Create a Java byte array from an svn_string_t.
 * @param str       the string
 */
jbyteArray JNIUtil::makeJByteArray(const svn_string_t *str)
{
  // a NULL will create no Java array
  if (!str)
    return NULL;

  return JNIUtil::makeJByteArray(str->data, static_cast<int>(str->len));
}

/**
 * Throw a Java NullPointerException.  Used when input parameters
 * which should not be null are that.
 *
 * @param message   the name of the parameter that is null
 */
void JNIUtil::throwNullPointerException(const char *message)
{
  if (getLogLevel() >= errorLog)
    logMessage("NullPointerException thrown");

  JNIEnv *env = getEnv();
  jclass clazz = env->FindClass("java/lang/NullPointerException");
  if (isJavaExceptionThrown())
    return;

  env->ThrowNew(clazz, message);
}

svn_error_t *JNIUtil::preprocessPath(const char *&path, apr_pool_t *pool)
{
  /* URLs and wc-paths get treated differently. */
  if (svn_path_is_url(path))
    {
      /* No need to canonicalize a URL's case or path separators. */

      /* Convert to URI. */
      path = svn_path_uri_from_iri(path, pool);

      /* Auto-escape some ASCII characters. */
      path = svn_path_uri_autoescape(path, pool);

      /* The above doesn't guarantee a valid URI. */
      if (! svn_path_is_uri_safe(path))
        return svn_error_createf(SVN_ERR_BAD_URL, NULL,
                                 _("URL '%s' is not properly URI-encoded"),
                                 path);

      /* Verify that no backpaths are present in the URL. */
      if (svn_path_is_backpath_present(path))
        return svn_error_createf(SVN_ERR_BAD_URL, NULL,
                                 _("URL '%s' contains a '..' element"),
                                 path);

      /* strip any trailing '/' */
      path = svn_uri_canonicalize(path, pool);
    }
  else  /* not a url, so treat as a path */
    {
      /* Normalize path to subversion internal style */

      /* ### In Subversion < 1.6 this method on Windows actually tried
         to lookup the path on disk to fix possible invalid casings in
         the passed path. (An extremely expensive operation; especially
         on network drives).

         This 'feature'is now removed as it penalizes every correct
         path passed, and also breaks behavior of e.g.
           'svn status .' returns '!' file, because there is only a "File"
             on disk.
            But when you then call 'svn status file', you get '? File'.

         As JavaHL is designed to be platform independent I assume users
         don't want this broken behavior on non round-trippable paths, nor
         the performance penalty.
       */

      path = svn_dirent_internal_style(path, pool);

      /* For kicks and giggles, let's absolutize it. */
      SVN_ERR(svn_dirent_get_absolute(&path, path, pool));
    }

  return NULL;
}

/* Tag to use on the apr_pool_t to store a WrappedException reference */
static const char *WrapExceptionTag = "org.apache.subversion.JavaHL.svnerror";

class WrappedException
{
  JNIEnv *m_env;
  jthrowable m_exception;
#ifdef SVN_DEBUG
  bool m_fetched;
#endif
public:
  WrappedException(JNIEnv *env)
  {
    m_env = env;

    // Fetch exception inside local frame
    jthrowable exceptionObj = env->ExceptionOccurred();

    // Now clear exception status
    env->ExceptionClear();

    // As adding a reference in exception state fails
    m_exception = static_cast<jthrowable>(env->NewGlobalRef(exceptionObj));

#ifdef SVN_DEBUG
    m_fetched = false;
#endif
  }

  static jthrowable get_exception(apr_pool_t *pool)
  {
      void *data;
      if (! apr_pool_userdata_get(&data, WrapExceptionTag, pool))
      {
          WrappedException *we = reinterpret_cast<WrappedException *>(data);

          if (we)
          {
#ifdef SVN_DEBUG
              we->m_fetched = TRUE;
#endif
              // Create reference in local frame, as the pool will be cleared
              return static_cast<jthrowable>(
                            we->m_env->NewLocalRef(we->m_exception));
          }
      }
      return NULL;
  }

private:
  ~WrappedException()
  {
#ifdef SVN_DEBUG
      if (!m_fetched)
          SVN_DBG(("Cleared svn_error_t * before Java exception was fetched"));
#endif
      m_env->DeleteGlobalRef(m_exception);
  }
public:
  static apr_status_t cleanup(void *data)
  {
    WrappedException *we = reinterpret_cast<WrappedException *>(data);

    delete we;
    return APR_SUCCESS;
  }
};

svn_error_t* JNIUtil::wrapJavaException()
{
  if (!isExceptionThrown())
    return SVN_NO_ERROR;

  svn_error_t *err = svn_error_create(SVN_ERR_JAVAHL_WRAPPED, NULL,
                                      "Wrapped Java Exception");
  apr_pool_userdata_set(new WrappedException(getEnv()), WrapExceptionTag,
                        WrappedException::cleanup, err->pool);
  return err;
}

jthrowable JNIUtil::unwrapJavaException(const svn_error_t *err)
{
    if (!err)
        return NULL;
    return
        WrappedException::get_exception(err->pool);
}

StashException::StashException(JNIEnv* env)
{
  m_env = env;
  m_stashed = NULL;
  stashException();
}

StashException::~StashException()
{
  if (m_stashed)
    m_env->Throw(m_stashed);
}

void StashException::stashException()
{
  jthrowable jexc = m_env->ExceptionOccurred();
  if (!jexc)
    return;

  if (!m_stashed)
    m_stashed = jexc;

  m_env->ExceptionClear();
}
