/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_bridges_java_uno.hxx"

#include "jni_bridge.h"

#include "jvmaccess/unovirtualmachine.hxx"
#include "rtl/ref.hxx"
#include "rtl/unload.h"
#include "rtl/strbuf.hxx"
#include "uno/lbnames.h"


using namespace ::std;
using namespace ::rtl;
using namespace ::osl;
using namespace ::jni_uno;

namespace
{
extern "C"
{

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

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

//------------------------------------------------------------------------------
void SAL_CALL Mapping_map_to_uno(
    uno_Mapping * mapping, void ** ppOut,
    void * pIn, typelib_InterfaceTypeDescription * td )
    SAL_THROW_EXTERN_C()
{
    uno_Interface ** ppUnoI = (uno_Interface **)ppOut;
    jobject javaI = (jobject) pIn;
    
    OSL_ASSERT( sizeof (void *) == sizeof (jobject) );
	OSL_ENSURE( ppUnoI && td, "### null ptr!" );

	if (0 == javaI)
    {
        if (0 != *ppUnoI)
        {
            uno_Interface * p = *(uno_Interface **)ppUnoI;
            (*p->release)( p );
            *ppUnoI = 0;
        }
    }
    else
	{
        try
        {
            Bridge const * bridge =
                static_cast< Mapping const * >( mapping )->m_bridge;
            JNI_guarded_context jni(
                bridge->m_jni_info,
                reinterpret_cast< ::jvmaccess::UnoVirtualMachine * >(
                    bridge->m_java_env->pContext ) );
            
            JNI_interface_type_info const * info =
                static_cast< JNI_interface_type_info const * >(
                    bridge->m_jni_info->get_type_info(
                        jni, (typelib_TypeDescription *)td ) );
            uno_Interface * pUnoI = bridge->map_to_uno( jni, javaI, info );
            if (0 != *ppUnoI)
            {
                uno_Interface * p = *(uno_Interface **)ppUnoI;
                (*p->release)( p );
            }
            *ppUnoI = pUnoI;
        }
        catch (BridgeRuntimeError & err)
        {
#if OSL_DEBUG_LEVEL > 0
            OString cstr_msg(
                OUStringToOString(
                    OUSTR("[jni_uno bridge error] ") + err.m_message,
                    RTL_TEXTENCODING_ASCII_US ) );
            OSL_ENSURE( 0, cstr_msg.getStr() );
#else
            (void) err; // unused
#endif
        }
        catch (::jvmaccess::VirtualMachine::AttachGuard::CreationException &)
        {
            OSL_ENSURE(
                0,
                "[jni_uno bridge error] attaching current thread "
                "to java failed!" );
        }
	}
}

//------------------------------------------------------------------------------
void SAL_CALL Mapping_map_to_java(
    uno_Mapping * mapping, void ** ppOut,
    void * pIn, typelib_InterfaceTypeDescription * td )
    SAL_THROW_EXTERN_C()
{
    jobject * ppJavaI = (jobject *) ppOut;
    uno_Interface * pUnoI = (uno_Interface *)pIn;

    OSL_ASSERT( sizeof (void *) == sizeof (jobject) );
	OSL_ENSURE( ppJavaI && td, "### null ptr!" );

    try
    {
        if (0 == pUnoI)
        {
            if (0 != *ppJavaI)
            {
                Bridge const * bridge =
                    static_cast< Mapping const * >( mapping )->m_bridge;
                JNI_guarded_context jni(
                    bridge->m_jni_info,
                    reinterpret_cast< ::jvmaccess::UnoVirtualMachine * >(
                        bridge->m_java_env->pContext ) );
                jni->DeleteGlobalRef( *ppJavaI );
                *ppJavaI = 0;
            }
        }
        else
        {
            Bridge const * bridge =
                static_cast< Mapping const * >( mapping )->m_bridge;
            JNI_guarded_context jni(
                bridge->m_jni_info,
                reinterpret_cast< ::jvmaccess::UnoVirtualMachine * >(
                    bridge->m_java_env->pContext ) );
            
            JNI_interface_type_info const * info =
                static_cast< JNI_interface_type_info const * >(
                    bridge->m_jni_info->get_type_info(
                        jni, (typelib_TypeDescription *)td ) );
            jobject jlocal = bridge->map_to_java( jni, pUnoI, info );
            if (0 != *ppJavaI)
                jni->DeleteGlobalRef( *ppJavaI );
            *ppJavaI = jni->NewGlobalRef( jlocal );
            jni->DeleteLocalRef( jlocal );
        }
    }
    catch (BridgeRuntimeError & err)
    {
#if OSL_DEBUG_LEVEL > 0
        OString cstr_msg(
            OUStringToOString(
                OUSTR("[jni_uno bridge error] ") + err.m_message,
                RTL_TEXTENCODING_ASCII_US ) );
        OSL_ENSURE( 0, cstr_msg.getStr() );
#else
            (void) err; // unused
#endif
    }
    catch (::jvmaccess::VirtualMachine::AttachGuard::CreationException &)
    {
        OSL_ENSURE(
            0,
            "[jni_uno bridge error] attaching current thread to java failed!" );
    }
}

//______________________________________________________________________________
void SAL_CALL Bridge_free( uno_Mapping * mapping )
    SAL_THROW_EXTERN_C()
{
    Mapping * that = static_cast< Mapping * >( mapping );
    delete that->m_bridge;
}

}

rtl_StandardModuleCount g_moduleCount = MODULE_COUNT_INIT;

}

namespace jni_uno
{

//______________________________________________________________________________
void Bridge::acquire() const SAL_THROW( () )
{
    if (1 == osl_incrementInterlockedCount( &m_ref ))
    {
        if (m_registered_java2uno)
        {
            uno_Mapping * mapping = const_cast< Mapping * >( &m_java2uno );
            uno_registerMapping(
                &mapping, Bridge_free,
                m_java_env, (uno_Environment *)m_uno_env, 0 );
        }
        else
        {
            uno_Mapping * mapping = const_cast< Mapping * >( &m_uno2java );
            uno_registerMapping(
                &mapping, Bridge_free,
                (uno_Environment *)m_uno_env, m_java_env, 0 );
        }
    }
}

//______________________________________________________________________________
void Bridge::release() const SAL_THROW( () )
{
    if (! osl_decrementInterlockedCount( &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 )
{
    // bootstrapping bridge jni_info
    m_jni_info = JNI_info::get_jni_info(
        reinterpret_cast< ::jvmaccess::UnoVirtualMachine * >(
            m_java_env->pContext ) );

    OSL_ASSERT( 0 != m_java_env && 0 != m_uno_env );
    (*((uno_Environment *)m_uno_env)->acquire)( (uno_Environment *)m_uno_env );
    (*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;

    (*g_moduleCount.modCnt.acquire)( &g_moduleCount.modCnt );
}

//______________________________________________________________________________
Bridge::~Bridge() SAL_THROW( () )
{
    (*m_java_env->release)( m_java_env );
    (*((uno_Environment *)m_uno_env)->release)( (uno_Environment *)m_uno_env );

    (*g_moduleCount.modCnt.release)( &g_moduleCount.modCnt );
}


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

    JLocalAutoRef jo_exc( *this, m_env->ExceptionOccurred() );
    m_env->ExceptionClear();
    OSL_ASSERT( jo_exc.is() );
    if (! jo_exc.is())
    {
        throw BridgeRuntimeError(
            OUSTR("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 (JNI_FALSE != m_env->ExceptionCheck())
    {
        m_env->ExceptionClear();
        throw BridgeRuntimeError(
            OUSTR("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(
        (jclass) jo_Object.get(), "toString", "()Ljava/lang/String;" );
    if (JNI_FALSE != m_env->ExceptionCheck())
    {
        m_env->ExceptionClear();
        throw BridgeRuntimeError(
            OUSTR("cannot get method id of java.lang.Object.toString()!") +
            get_stack_trace() );
    }
    OSL_ASSERT( 0 != method_Object_toString );

    JLocalAutoRef jo_descr(
        *this, m_env->CallObjectMethodA(
            jo_exc.get(), method_Object_toString, 0 ) );
    if (m_env->ExceptionCheck()) // no chance at all
    {
        m_env->ExceptionClear();
        throw BridgeRuntimeError(
            OUSTR("error examining java exception object!") +
            get_stack_trace() );
    }
    
    jsize len = m_env->GetStringLength( (jstring) jo_descr.get() );
    auto_ptr< rtl_mem > ustr_mem(
        rtl_mem::allocate(
            sizeof (rtl_uString) + (len * sizeof (sal_Unicode)) ) );
    rtl_uString * ustr = (rtl_uString *)ustr_mem.get();
    m_env->GetStringRegion( (jstring) jo_descr.get(), 0, len, ustr->buffer );
    if (m_env->ExceptionCheck())
    {
        m_env->ExceptionClear();
        throw BridgeRuntimeError(
            OUSTR("invalid java string object!") + get_stack_trace() );
    }
    ustr->refCount = 1;
    ustr->length = len;
    ustr->buffer[ len ] = '\0';
    OUString message( (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 != 0) {
        *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 = 0;
    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(
            (jclass) jo_JNI_proxy.get(), "get_stack_trace",
            "(Ljava/lang/Throwable;)Ljava/lang/String;" );
        if (assert_no_exception() && (0 != method))
        {
            jvalue arg;
            arg.l = jo_exc;
            JLocalAutoRef jo_stack_trace(
                *this, m_env->CallStaticObjectMethodA(
                    (jclass) jo_JNI_proxy.get(), method, &arg ) );
            if (assert_no_exception())
            {
                jsize len =
                    m_env->GetStringLength( (jstring) jo_stack_trace.get() );
                auto_ptr< rtl_mem > ustr_mem(
                    rtl_mem::allocate(
                        sizeof (rtl_uString) + (len * sizeof (sal_Unicode)) ) );
                rtl_uString * ustr = (rtl_uString *)ustr_mem.get();
                m_env->GetStringRegion(
                    (jstring) jo_stack_trace.get(), 0, len, ustr->buffer );
                if (assert_no_exception())
                {
                    ustr->refCount = 1;
                    ustr->length = len;
                    ustr->buffer[ len ] = '\0';
                    return OUString(
                        (rtl_uString *)ustr_mem.release(), SAL_NO_ACQUIRE );
                }
            }
        }
    }
    return OUString();
}

}

using namespace ::jni_uno;

extern "C"
{
namespace
{

//------------------------------------------------------------------------------
void SAL_CALL java_env_disposing( uno_Environment * java_env )
    SAL_THROW_EXTERN_C()
{
    ::jvmaccess::UnoVirtualMachine * machine =
          reinterpret_cast< ::jvmaccess::UnoVirtualMachine * >(
              java_env->pContext );
    java_env->pContext = 0;
    machine->release();
}
}

//------------------------------------------------------------------------------
JNIEXPORT void SAL_CALL uno_initEnvironment( uno_Environment * java_env )
    SAL_THROW_EXTERN_C()
{
    java_env->environmentDisposing = java_env_disposing;
    java_env->pExtEnv = 0; // no extended support
    OSL_ASSERT( 0 != java_env->pContext );

    ::jvmaccess::UnoVirtualMachine * machine =
          reinterpret_cast< ::jvmaccess::UnoVirtualMachine * >(
              java_env->pContext );
    machine->acquire();
}

//------------------------------------------------------------------------------
JNIEXPORT void SAL_CALL uno_ext_getMapping(
    uno_Mapping ** ppMapping, uno_Environment * pFrom, uno_Environment * pTo )
    SAL_THROW_EXTERN_C()
{
    OSL_ASSERT( 0 != ppMapping && 0 != pFrom && 0 != pTo );
    if (0 != *ppMapping)
    {
        (*(*ppMapping)->release)( *ppMapping );
        *ppMapping = 0;
    }

    OSL_ASSERT( JNI_FALSE == sal_False );
    OSL_ASSERT( JNI_TRUE == sal_True );
    OSL_ASSERT( sizeof (jboolean) == sizeof (sal_Bool) );
    OSL_ASSERT( sizeof (jchar) == sizeof (sal_Unicode) );
    OSL_ASSERT( sizeof (jdouble) == sizeof (double) );
    OSL_ASSERT( sizeof (jfloat) == sizeof (float) );
    OSL_ASSERT( sizeof (jbyte) == sizeof (sal_Int8) );
    OSL_ASSERT( sizeof (jshort) == sizeof (sal_Int16) );
    OSL_ASSERT( sizeof (jint) == sizeof (sal_Int32) );
    OSL_ASSERT( sizeof (jlong) == sizeof (sal_Int64) );
    if ((JNI_FALSE == sal_False) &&
        (JNI_TRUE == sal_True) &&
        (sizeof (jboolean) == sizeof (sal_Bool)) &&
        (sizeof (jchar) == sizeof (sal_Unicode)) &&
        (sizeof (jdouble) == sizeof (double)) &&
        (sizeof (jfloat) == sizeof (float)) &&
        (sizeof (jbyte) == sizeof (sal_Int8)) &&
        (sizeof (jshort) == sizeof (sal_Int16)) &&
        (sizeof (jint) == sizeof (sal_Int32)) &&
        (sizeof (jlong) == sizeof (sal_Int64)))
    {
        OUString const & from_env_typename =
            OUString::unacquired( &pFrom->pTypeName );
        OUString const & to_env_typename =
            OUString::unacquired( &pTo->pTypeName );
        
        uno_Mapping * mapping = 0;

        try
        {
            if (from_env_typename.equalsAsciiL(
                    RTL_CONSTASCII_STRINGPARAM(UNO_LB_JAVA) ) &&
                to_env_typename.equalsAsciiL(
                    RTL_CONSTASCII_STRINGPARAM(UNO_LB_UNO) ))
            {
                Bridge * bridge =
                    new Bridge( pFrom, pTo->pExtEnv, true ); // ref count = 1
                mapping = &bridge->m_java2uno;
                uno_registerMapping(
                    &mapping, Bridge_free,
                    pFrom, (uno_Environment *)pTo->pExtEnv, 0 );
            }
            else if (from_env_typename.equalsAsciiL(
                         RTL_CONSTASCII_STRINGPARAM(UNO_LB_UNO) ) &&
                     to_env_typename.equalsAsciiL(
                         RTL_CONSTASCII_STRINGPARAM(UNO_LB_JAVA) ))
            {
                Bridge * bridge =
                    new Bridge( pTo, pFrom->pExtEnv, false ); // ref count = 1
                mapping = &bridge->m_uno2java;
                uno_registerMapping(
                    &mapping, Bridge_free,
                    (uno_Environment *)pFrom->pExtEnv, pTo, 0 );
            }
        }
        catch (BridgeRuntimeError & err)
        {
#if OSL_DEBUG_LEVEL > 0
            OString cstr_msg(
                OUStringToOString(
                    OUSTR("[jni_uno bridge error] ") + err.m_message,
                    RTL_TEXTENCODING_ASCII_US ) );
            OSL_ENSURE( 0, cstr_msg.getStr() );
#else
            (void) err; // unused
#endif
        }
        catch (::jvmaccess::VirtualMachine::AttachGuard::CreationException &)
        {
            OSL_ENSURE(
                0,
                "[jni_uno bridge error] attaching current thread "
                "to java failed!" );
        }

        *ppMapping = mapping;
    }
}

//------------------------------------------------------------------------------
JNIEXPORT sal_Bool SAL_CALL component_canUnload( TimeValue * pTime )
    SAL_THROW_EXTERN_C()
{
    return (*g_moduleCount.canUnload)( &g_moduleCount, pTime );
}
}