/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <sal/config.h>
#include <sal/log.hxx>

#include <cassert>
#include <memory>

#include "jni_bridge.h"
#include "jni_helper.h"
#include "jniunoenvironmentdata.hxx"

#include <jvmaccess/unovirtualmachine.hxx>
#include <rtl/ref.hxx>
#include <uno/lbnames.h>

using namespace ::jni_uno;

namespace
{
extern "C"
{


void Mapping_acquire( uno_Mapping * mapping ) noexcept
{
    Mapping const * that = static_cast< Mapping const * >( mapping );
    that->m_bridge->acquire();
}


void Mapping_release( uno_Mapping * mapping ) noexcept
{
    Mapping const * that = static_cast< Mapping const * >( mapping );
    that->m_bridge->release();
}


void Mapping_map_to_uno(
    uno_Mapping * mapping, void ** ppOut,
    void * pIn, typelib_InterfaceTypeDescription * td ) noexcept
{
    uno_Interface ** ppUnoI = reinterpret_cast<uno_Interface **>(ppOut);
    jobject javaI = static_cast<jobject>(pIn);

    static_assert(sizeof (void *) == sizeof (jobject), "must be the same size");
    assert(ppUnoI != nullptr);
    assert(td != nullptr);

    if (javaI == nullptr)
    {
        if (*ppUnoI != nullptr)
        {
            uno_Interface * p = *ppUnoI;
            (*p->release)( p );
            *ppUnoI = nullptr;
        }
    }
    else
    {
        try
        {
            Bridge const * bridge =
                static_cast< Mapping const * >( mapping )->m_bridge;
            JNI_guarded_context jni(
                bridge->getJniInfo(),
                (static_cast<jni_uno::JniUnoEnvironmentData *>(
                    bridge->m_java_env->pContext)
                 ->machine));

            JNI_interface_type_info const * info =
                static_cast< JNI_interface_type_info const * >(
                    bridge->getJniInfo()->get_type_info(
                        jni, &td->aBase ) );
            uno_Interface * pUnoI = bridge->map_to_uno( jni, javaI, info );
            if (*ppUnoI != nullptr)
            {
                uno_Interface * p = *ppUnoI;
                (*p->release)( p );
            }
            *ppUnoI = pUnoI;
        }
        catch (const BridgeRuntimeError & err)
        {
            SAL_WARN(
                "bridges",
                "ignoring BridgeRuntimeError \"" << err.m_message << "\"");
        }
        catch (const ::jvmaccess::VirtualMachine::AttachGuard::CreationException &)
        {
            SAL_WARN("bridges", "attaching current thread to java failed");
        }
    }
}


void Mapping_map_to_java(
    uno_Mapping * mapping, void ** ppOut,
    void * pIn, typelib_InterfaceTypeDescription * td ) noexcept
{
    jobject * ppJavaI = reinterpret_cast<jobject *>(ppOut);
    uno_Interface * pUnoI = static_cast<uno_Interface *>(pIn);

    static_assert(sizeof (void *) == sizeof (jobject), "must be the same size");
    assert(ppJavaI != nullptr);
    assert(td != nullptr);

    try
    {
        if (pUnoI == nullptr)
        {
            if (*ppJavaI != nullptr)
            {
                Bridge const * bridge =
                    static_cast< Mapping const * >( mapping )->m_bridge;
                JNI_guarded_context jni(
                    bridge->getJniInfo(),
                    (static_cast<jni_uno::JniUnoEnvironmentData *>(
                        bridge->m_java_env->pContext)
                     ->machine));
                jni->DeleteGlobalRef( *ppJavaI );
                *ppJavaI = nullptr;
            }
        }
        else
        {
            Bridge const * bridge =
                static_cast< Mapping const * >( mapping )->m_bridge;
            JNI_guarded_context jni(
                bridge->getJniInfo(),
                (static_cast<jni_uno::JniUnoEnvironmentData *>(
                    bridge->m_java_env->pContext)
                 ->machine));

            JNI_interface_type_info const * info =
                static_cast< JNI_interface_type_info const * >(
                    bridge->getJniInfo()->get_type_info(
                        jni, &td->aBase ) );
            jobject jlocal = bridge->map_to_java( jni, pUnoI, info );
            if (*ppJavaI != nullptr)
                jni->DeleteGlobalRef( *ppJavaI );
            *ppJavaI = jni->NewGlobalRef( jlocal );
            jni->DeleteLocalRef( jlocal );
        }
    }
    catch (const BridgeRuntimeError & err)
    {
        SAL_WARN(
            "bridges",
            "ignoring BridgeRuntimeError \"" << err.m_message << "\"");
    }
    catch (const ::jvmaccess::VirtualMachine::AttachGuard::CreationException &)
    {
        SAL_WARN("bridges", "attaching current thread to java failed");
    }
}


void Bridge_free( uno_Mapping * mapping ) noexcept
{
    Mapping * that = static_cast< Mapping * >( mapping );
    delete that->m_bridge;
}

}

}

namespace jni_uno
{


void Bridge::acquire() const
{
    if (++m_ref != 1)
        return;

    if (m_registered_java2uno)
    {
        uno_Mapping * mapping = const_cast< Mapping * >( &m_java2uno );
        uno_registerMapping(
            &mapping, Bridge_free,
            m_java_env, &m_uno_env->aBase, nullptr );
    }
    else
    {
        uno_Mapping * mapping = const_cast< Mapping * >( &m_uno2java );
        uno_registerMapping(
            &mapping, Bridge_free,
            &m_uno_env->aBase, m_java_env, nullptr );
    }
}


void Bridge::release() const
{
    if (! --m_ref )
    {
        uno_revokeMapping(
            m_registered_java2uno
            ? const_cast< Mapping * >( &m_java2uno )
            : const_cast< Mapping * >( &m_uno2java ) );
    }
}


Bridge::Bridge(
    uno_Environment * java_env, uno_ExtEnvironment * uno_env,
    bool registered_java2uno )
    : m_ref( 1 ),
      m_uno_env( uno_env ),
      m_java_env( java_env ),
      m_registered_java2uno( registered_java2uno )
{
    assert(m_java_env != nullptr);
    assert(m_uno_env != nullptr);

    // uno_initEnvironment (below) cannot report errors directly, so it clears
    // its pContext upon error to indirectly report errors from here:
    if (static_cast<jni_uno::JniUnoEnvironmentData *>(m_java_env->pContext)
        == nullptr)
    {
        throw BridgeRuntimeError(u"error during JNI-UNO's uno_initEnvironment"_ustr);
    }

    (*m_uno_env->aBase.acquire)( &m_uno_env->aBase );
    (*m_java_env->acquire)( m_java_env );

    // java2uno
    m_java2uno.acquire = Mapping_acquire;
    m_java2uno.release = Mapping_release;
    m_java2uno.mapInterface = Mapping_map_to_uno;
    m_java2uno.m_bridge = this;
    // uno2java
    m_uno2java.acquire = Mapping_acquire;
    m_uno2java.release = Mapping_release;
    m_uno2java.mapInterface = Mapping_map_to_java;
    m_uno2java.m_bridge = this;
}


Bridge::~Bridge()
{
    (*m_java_env->release)( m_java_env );
    (*m_uno_env->aBase.release)( &m_uno_env->aBase );
}

JNI_info const * Bridge::getJniInfo() const {
    return static_cast<jni_uno::JniUnoEnvironmentData *>(m_java_env->pContext)
        ->info;
}

void JNI_context::java_exc_occurred() const
{
    // !don't rely on JNI_info!

    JLocalAutoRef jo_exc( *this, m_env->ExceptionOccurred() );
    m_env->ExceptionClear();
    assert(jo_exc.is());
    if (! jo_exc.is())
    {
        throw BridgeRuntimeError(
            "java exception occurred, but not available!?" +
            get_stack_trace() );
    }

    // call toString(); don't rely on m_jni_info
    jclass jo_class = m_env->FindClass( "java/lang/Object" );
    if (m_env->ExceptionCheck())
    {
        m_env->ExceptionClear();
        throw BridgeRuntimeError(
            "cannot get class java.lang.Object!" + get_stack_trace() );
    }
    JLocalAutoRef jo_Object( *this, jo_class );
    // method Object.toString()
    jmethodID method_Object_toString = m_env->GetMethodID(
        static_cast<jclass>(jo_Object.get()), "toString", "()Ljava/lang/String;" );
    if (m_env->ExceptionCheck())
    {
        m_env->ExceptionClear();
        throw BridgeRuntimeError(
            "cannot get method id of java.lang.Object.toString()!" +
            get_stack_trace() );
    }
    assert(method_Object_toString != nullptr);

    JLocalAutoRef jo_descr(
        *this, m_env->CallObjectMethodA(
            jo_exc.get(), method_Object_toString, nullptr ) );
    if (m_env->ExceptionCheck()) // no chance at all
    {
        m_env->ExceptionClear();
        throw BridgeRuntimeError(
            "error examining java exception object!" +
            get_stack_trace() );
    }

    jsize len = m_env->GetStringLength( static_cast<jstring>(jo_descr.get()) );
    std::unique_ptr< rtl_mem > ustr_mem(
        rtl_mem::allocate(
            sizeof (rtl_uString) + (len * sizeof (sal_Unicode)) ) );
    rtl_uString * ustr = reinterpret_cast<rtl_uString *>(ustr_mem.get());
    m_env->GetStringRegion( static_cast<jstring>(jo_descr.get()), 0, len, reinterpret_cast<jchar *>(ustr->buffer) );
    if (m_env->ExceptionCheck())
    {
        m_env->ExceptionClear();
        throw BridgeRuntimeError(
            "invalid java string object!" + get_stack_trace() );
    }
    ustr->refCount = 1;
    ustr->length = len;
    ustr->buffer[ len ] = '\0';
    OUString message( reinterpret_cast<rtl_uString *>(ustr_mem.release()), SAL_NO_ACQUIRE );

    throw BridgeRuntimeError( message + get_stack_trace( jo_exc.get() ) );
}


void JNI_context::getClassForName(
    jclass * classClass, jmethodID * methodForName) const
{
    jclass c = m_env->FindClass("java/lang/Class");
    if (c != nullptr) {
        *methodForName = m_env->GetStaticMethodID(
            c, "forName",
            "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
    }
    *classClass = c;
}


jclass JNI_context::findClass(
    char const * name, jclass classClass, jmethodID methodForName,
    bool inException) const
{
    jclass c = nullptr;
    JLocalAutoRef s(*this, m_env->NewStringUTF(name));
    if (s.is()) {
        jvalue a[3];
        a[0].l = s.get();
        a[1].z = JNI_FALSE;
        a[2].l = m_class_loader;
        c = static_cast< jclass >(
            m_env->CallStaticObjectMethodA(classClass, methodForName, a));
    }
    if (!inException) {
        ensure_no_exception();
    }
    return c;
}


OUString JNI_context::get_stack_trace( jobject jo_exc ) const
{
    JLocalAutoRef jo_JNI_proxy(
        *this,
        find_class( *this, "com.sun.star.bridges.jni_uno.JNI_proxy", true ) );
    if (assert_no_exception())
    {
        // static method JNI_proxy.get_stack_trace()
        jmethodID method = m_env->GetStaticMethodID(
            static_cast<jclass>(jo_JNI_proxy.get()), "get_stack_trace",
            "(Ljava/lang/Throwable;)Ljava/lang/String;" );
        if (assert_no_exception() && (method != nullptr))
        {
            jvalue arg;
            arg.l = jo_exc;
            JLocalAutoRef jo_stack_trace(
                *this, m_env->CallStaticObjectMethodA(
                    static_cast<jclass>(jo_JNI_proxy.get()), method, &arg ) );
            if (assert_no_exception())
            {
                jsize len =
                    m_env->GetStringLength( static_cast<jstring>(jo_stack_trace.get()) );
                std::unique_ptr< rtl_mem > ustr_mem(
                    rtl_mem::allocate(
                        sizeof (rtl_uString) + (len * sizeof (sal_Unicode)) ) );
                rtl_uString * ustr = reinterpret_cast<rtl_uString *>(ustr_mem.get());
                m_env->GetStringRegion(
                    static_cast<jstring>(jo_stack_trace.get()), 0, len, reinterpret_cast<jchar *>(ustr->buffer) );
                if (assert_no_exception())
                {
                    ustr->refCount = 1;
                    ustr->length = len;
                    ustr->buffer[ len ] = '\0';
                    return OUString(
                        reinterpret_cast<rtl_uString *>(ustr_mem.release()), SAL_NO_ACQUIRE );
                }
            }
        }
    }
    return OUString();
}

}

using namespace ::jni_uno;

extern "C" {

static void java_env_dispose(uno_Environment * env) {
    auto * envData
        = static_cast<jni_uno::JniUnoEnvironmentData *>(env->pContext);
    if (envData == nullptr)        return;

    jobject async;
    {
        std::unique_lock g(envData->mutex);
        async = envData->asynchronousFinalizer;
        envData->asynchronousFinalizer = nullptr;
    }
    if (async == nullptr)        return;

    try {
        JNI_guarded_context jni(envData->info, envData->machine);
        jni->CallObjectMethodA(
            async, envData->info->m_method_AsynchronousFinalizer_drain,
            nullptr);
        jni.ensure_no_exception();
        jni->DeleteGlobalRef(async);
    } catch (const BridgeRuntimeError & e) {
        SAL_WARN(
            "bridges",
            "ignoring BridgeRuntimeError \"" << e.m_message << "\"");
    } catch (
        jvmaccess::VirtualMachine::AttachGuard::CreationException &)
    {
        SAL_WARN(
            "bridges",
            ("ignoring jvmaccess::VirtualMachine::AttachGuard"
             "::CreationException"));
    }
}

static void java_env_disposing(uno_Environment * env) {
    java_env_dispose(env);
    delete static_cast<jni_uno::JniUnoEnvironmentData *>(env->pContext);
}

#ifdef DISABLE_DYNLOADING
#define uno_initEnvironment java_uno_initEnvironment
#endif


SAL_DLLPUBLIC_EXPORT void uno_initEnvironment( uno_Environment * java_env ) noexcept
{
    try {
        // JavaComponentLoader::getJavaLoader (in
        // stoc/source/javaloader/javaloader.cxx) stores a
        // jvmaccess::UnoVirtualMachine pointer into java_env->pContext; replace
        // it here with either a pointer to a full JniUnoEnvironmentData upon
        // success, or with a null pointer upon failure (as this function cannot
        // directly report back failure, so it uses that way to indirectly
        // report failure later from within the Bridge ctor):
        rtl::Reference<jvmaccess::UnoVirtualMachine> vm(
            static_cast<jvmaccess::UnoVirtualMachine *>(java_env->pContext));
        java_env->pContext = nullptr;
        java_env->dispose = java_env_dispose;
        java_env->environmentDisposing = java_env_disposing;
        java_env->pExtEnv = nullptr; // no extended support
        std::unique_ptr<jni_uno::JniUnoEnvironmentData> envData(
            new jni_uno::JniUnoEnvironmentData(vm));
        {
            JNI_guarded_context jni(envData->info, envData->machine);
            JLocalAutoRef ref(
                jni,
                jni->NewObject(
                    envData->info->m_class_AsynchronousFinalizer,
                    envData->info->m_ctor_AsynchronousFinalizer));
            jni.ensure_no_exception();
            envData->asynchronousFinalizer = jni->NewGlobalRef(ref.get());
            jni.ensure_no_exception();
        }
        java_env->pContext = envData.release();
    } catch (const BridgeRuntimeError & e) {
        SAL_WARN("bridges", "BridgeRuntimeError \"" << e.m_message << "\"");
    } catch (jvmaccess::VirtualMachine::AttachGuard::CreationException &) {
        SAL_WARN(
            "bridges",
            "jvmaccess::VirtualMachine::AttachGuard::CreationException");
    }
}

#ifdef DISABLE_DYNLOADING
#define uno_ext_getMapping java_uno_ext_getMapping
#endif


SAL_DLLPUBLIC_EXPORT void uno_ext_getMapping(
    uno_Mapping ** ppMapping, uno_Environment * pFrom, uno_Environment * pTo ) noexcept
{
    assert(ppMapping != nullptr);
    assert(pFrom != nullptr);
    assert(pTo != nullptr);
    if (*ppMapping != nullptr)
    {
        (*(*ppMapping)->release)( *ppMapping );
        *ppMapping = nullptr;
    }

    static_assert(int(JNI_FALSE) == int(false), "must be equal");
    static_assert(int(JNI_TRUE) == int(true), "must be equal");
    static_assert(sizeof (jboolean) == sizeof (sal_Bool), "must be the same size");
    static_assert(sizeof (jchar) == sizeof (sal_Unicode), "must be the same size");
    static_assert(sizeof (jdouble) == sizeof (double), "must be the same size");
    static_assert(sizeof (jfloat) == sizeof (float), "must be the same size");
    static_assert(sizeof (jbyte) == sizeof (sal_Int8), "must be the same size");
    static_assert(sizeof (jshort) == sizeof (sal_Int16), "must be the same size");
    static_assert(sizeof (jint) == sizeof (sal_Int32), "must be the same size");
    static_assert(sizeof (jlong) == sizeof (sal_Int64), "must be the same size");

    OUString const & from_env_typename =
        OUString::unacquired( &pFrom->pTypeName );
    OUString const & to_env_typename =
        OUString::unacquired( &pTo->pTypeName );

    uno_Mapping * mapping = nullptr;

    try
    {
        if ( from_env_typename == UNO_LB_JAVA && to_env_typename == UNO_LB_UNO )
        {
            Bridge * bridge =
                new Bridge( pFrom, pTo->pExtEnv, true ); // ref count = 1
            mapping = &bridge->m_java2uno;
            uno_registerMapping(
                &mapping, Bridge_free,
                pFrom, &pTo->pExtEnv->aBase, nullptr );
            // coverity[leaked_storage] - on purpose
        }
        else if ( from_env_typename == UNO_LB_UNO && to_env_typename == UNO_LB_JAVA )
        {
            Bridge * bridge =
                new Bridge( pTo, pFrom->pExtEnv, false ); // ref count = 1
            mapping = &bridge->m_uno2java;
            uno_registerMapping(
                &mapping, Bridge_free,
                &pFrom->pExtEnv->aBase, pTo, nullptr );
            // coverity[leaked_storage] - on purpose
        }
    }
    catch (const BridgeRuntimeError & err)
    {
        SAL_WARN("bridges", "BridgeRuntimeError \"" << err.m_message << "\"");
    }

    *ppMapping = mapping;
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
