/**************************************************************
 * 
 * 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_connectivity.hxx"


#ifndef _CONNECTIVITY_JAVA_LANG_OBJJECT_HXX_
#include "java/lang/Class.hxx"
#endif
#include "connectivity/CommonTools.hxx"
#include <com/sun/star/uno/Exception.hpp>
#include "java/tools.hxx"
#include "java/sql/SQLException.hxx"
#include <vos/process.hxx>
#include <vos/mutex.hxx>
#include <osl/thread.h>
#include <com/sun/star/uno/Sequence.hxx>
#include "java/LocalRef.hxx"
#include "resource/jdbc_log.hrc"
#include <rtl/logfile.hxx>
#include <comphelper/logging.hxx>

#include <memory>

using namespace connectivity;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::lang;


// -----------------------------------------------------------------------------
::rtl::Reference< jvmaccess::VirtualMachine > getJavaVM2(const ::rtl::Reference< jvmaccess::VirtualMachine >& _rVM = ::rtl::Reference< jvmaccess::VirtualMachine >(),
														sal_Bool _bSet = sal_False)
{
	static ::rtl::Reference< jvmaccess::VirtualMachine > s_VM;
	if ( _rVM.is() || _bSet )
		s_VM = _rVM;
	return s_VM;
}
// -----------------------------------------------------------------------------
::rtl::Reference< jvmaccess::VirtualMachine > java_lang_Object::getVM(const Reference<XMultiServiceFactory >& _rxFactory)
{
	::rtl::Reference< jvmaccess::VirtualMachine > xVM = getJavaVM2();
	if ( !xVM.is() && _rxFactory.is() )
		xVM = getJavaVM2(::connectivity::getJavaVM(_rxFactory));

	return xVM;
}
// -----------------------------------------------------------------------------
SDBThreadAttach::SDBThreadAttach()
 : m_aGuard(java_lang_Object::getVM())
 , pEnv(NULL)
{
	pEnv = m_aGuard.getEnvironment();
    OSL_ENSURE(pEnv,"Environment is nULL!");
}
// -----------------------------------------------------------------------------
SDBThreadAttach::~SDBThreadAttach()
{
}
// -----------------------------------------------------------------------------
oslInterlockedCount& getJavaVMRefCount()
{
	static oslInterlockedCount s_nRefCount = 0;
	return s_nRefCount;
}
// -----------------------------------------------------------------------------
void SDBThreadAttach::addRef()
{
    osl_incrementInterlockedCount(&getJavaVMRefCount());
}
// -----------------------------------------------------------------------------
void SDBThreadAttach::releaseRef()
{
    osl_decrementInterlockedCount(&getJavaVMRefCount());
	if ( getJavaVMRefCount() == 0 )
	{
		getJavaVM2(::rtl::Reference< jvmaccess::VirtualMachine >(),sal_True);
	}
}
// -----------------------------------------------------------------------------
// statische Variablen der Klasse:
jclass java_lang_Object::theClass = 0;

jclass java_lang_Object::getMyClass() const
{
	if( !theClass )
        theClass = findMyClass("java/lang/Object");
	return theClass;
}
// der eigentliche Konstruktor
java_lang_Object::java_lang_Object(const Reference<XMultiServiceFactory >& _rxFactory)
            : m_xFactory(_rxFactory),object( 0 )
{
    SDBThreadAttach::addRef();
}

// der protected-Konstruktor fuer abgeleitete Klassen
java_lang_Object::java_lang_Object( JNIEnv * pXEnv, jobject myObj )
	: object( NULL )
{
    SDBThreadAttach::addRef();
	if( pXEnv && myObj )
		object = pXEnv->NewGlobalRef( myObj );
}

java_lang_Object::~java_lang_Object()
{
	if( object )
	{
		SDBThreadAttach t;
        clearObject(*t.pEnv);
	}
    SDBThreadAttach::releaseRef();
}
void java_lang_Object::clearObject(JNIEnv& rEnv)
{
	if( object )
	{
		rEnv.DeleteGlobalRef( object );
		object = NULL;
	}
}

void java_lang_Object::clearObject()
{
	if( object )
	{
		SDBThreadAttach t;
        clearObject(*t.pEnv);
	}
}
// der protected-Konstruktor fuer abgeleitete Klassen
void java_lang_Object::saveRef( JNIEnv * pXEnv, jobject myObj )
{
	OSL_ENSURE( myObj, "object in c++ -> Java Wrapper" );
	if( myObj )
		object = pXEnv->NewGlobalRef( myObj );
}


::rtl::OUString java_lang_Object::toString() const
{
    static jmethodID mID(NULL);
    return callStringMethod("toString",mID);
}

// --------------------------------------------------------------------------------
namespace
{
    bool    lcl_translateJNIExceptionToUNOException(
        JNIEnv* _pEnvironment, const Reference< XInterface >& _rxContext, SQLException& _out_rException )
    {
        jthrowable jThrow = _pEnvironment ? _pEnvironment->ExceptionOccurred() : NULL;
        if ( !jThrow )
            return false;

        _pEnvironment->ExceptionClear();
            // we have to clear the exception here because we want to handle it itself

        if ( _pEnvironment->IsInstanceOf( jThrow, java_sql_SQLException_BASE::st_getMyClass() ) )
        {
            ::std::auto_ptr< java_sql_SQLException_BASE > pException( new java_sql_SQLException_BASE( _pEnvironment, jThrow ) );
            _out_rException = SQLException( pException->getMessage(), _rxContext,
                pException->getSQLState(), pException->getErrorCode(), Any() );
            return true;
        }
        else if ( _pEnvironment->IsInstanceOf( jThrow, java_lang_Throwable::st_getMyClass() ) )
        {
            ::std::auto_ptr< java_lang_Throwable > pThrow( new java_lang_Throwable( _pEnvironment, jThrow ) );
            ::rtl::OUString sMessage = pThrow->getMessage();
            if ( !sMessage.getLength() )
                sMessage = pThrow->getLocalizedMessage();
            if(  !sMessage.getLength() )
                sMessage = pThrow->toString();
            _out_rException = SQLException( sMessage, _rxContext, ::rtl::OUString(), -1, Any() );
            return true;
        }
        else
            _pEnvironment->DeleteLocalRef( jThrow );
        return false;
    }
}

// --------------------------------------------------------------------------------
void java_lang_Object::ThrowLoggedSQLException( const ::comphelper::ResourceBasedEventLogger& _rLogger, JNIEnv* _pEnvironment,
    const Reference< XInterface >& _rxContext )
{
    SQLException aException;
    if ( lcl_translateJNIExceptionToUNOException( _pEnvironment, _rxContext, aException ) )
    {
        _rLogger.log( ::com::sun::star::logging::LogLevel::SEVERE, STR_LOG_THROWING_EXCEPTION, aException.Message, aException.SQLState, aException.ErrorCode );
        throw aException;
    }
}

// --------------------------------------------------------------------------------
void java_lang_Object::ThrowSQLException( JNIEnv* _pEnvironment, const Reference< XInterface>& _rxContext )
{
    SQLException aException;
    if ( lcl_translateJNIExceptionToUNOException( _pEnvironment, _rxContext, aException ) )
        throw aException;
}
// -----------------------------------------------------------------------------
void java_lang_Object::obtainMethodId(JNIEnv* _pEnv,const char* _pMethodName, const char* _pSignature,jmethodID& _inout_MethodID) const
{
    if  ( !_inout_MethodID )
    {
        _inout_MethodID  = _pEnv->GetMethodID( getMyClass(), _pMethodName, _pSignature );
        OSL_ENSURE( _inout_MethodID, _pSignature );
        if  ( !_inout_MethodID )
            throw SQLException();
    } // if  ( !_inout_MethodID )
}
// -----------------------------------------------------------------------------
sal_Bool java_lang_Object::callBooleanMethod( const char* _pMethodName, jmethodID& _inout_MethodID ) const
{
	jboolean out( sal_False );

    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callBooleanMethod: no Java enviroment anymore!" );
    obtainMethodId(t.pEnv, _pMethodName,"()Z", _inout_MethodID);
    // call method
	out = t.pEnv->CallBooleanMethod( object, _inout_MethodID );
	ThrowSQLException( t.pEnv, NULL );

	return out;
}
// -----------------------------------------------------------------------------
sal_Bool java_lang_Object::callBooleanMethodWithIntArg( const char* _pMethodName, jmethodID& _inout_MethodID, sal_Int32 _nArgument ) const
{
	jboolean out( sal_False );
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callBooleanMethodWithIntArg: no Java enviroment anymore!" );
	obtainMethodId(t.pEnv, _pMethodName,"(I)Z", _inout_MethodID);
    // call method
	out = t.pEnv->CallBooleanMethod( object, _inout_MethodID, _nArgument );
	ThrowSQLException( t.pEnv, NULL );

	return out;
}
// -------------------------------------------------------------------------
jobject java_lang_Object::callResultSetMethod( JNIEnv& _rEnv,const char* _pMethodName, jmethodID& _inout_MethodID ) const
{
    // call method
    jobject out = callObjectMethod(&_rEnv,_pMethodName,"()Ljava/sql/ResultSet;", _inout_MethodID);
    return out;
}
// -------------------------------------------------------------------------
sal_Int32 java_lang_Object::callIntMethod( const char* _pMethodName, jmethodID& _inout_MethodID,bool _bIgnoreException ) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callIntMethod: no Java enviroment anymore!" );
    obtainMethodId(t.pEnv, _pMethodName,"()I", _inout_MethodID);

    // call method
	jint out( t.pEnv->CallIntMethod( object, _inout_MethodID ) );
    if ( _bIgnoreException )
        isExceptionOccured(t.pEnv,sal_True);
    else
	    ThrowSQLException( t.pEnv, NULL );

	return (sal_Int32)out;
}
// -------------------------------------------------------------------------
sal_Int32 java_lang_Object::callIntMethodWithIntArg( const char* _pMethodName, jmethodID& _inout_MethodID,sal_Int32 _nArgument ) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callIntMethod: no Java enviroment anymore!" );
    obtainMethodId(t.pEnv, _pMethodName,"(I)I", _inout_MethodID);
    // call method
	jint out( t.pEnv->CallIntMethod( object, _inout_MethodID , _nArgument) );
	ThrowSQLException( t.pEnv, NULL );

	return (sal_Int32)out;
}
// -------------------------------------------------------------------------
void java_lang_Object::callVoidMethod( const char* _pMethodName, jmethodID& _inout_MethodID) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callIntMethod: no Java enviroment anymore!" );
    obtainMethodId(t.pEnv, _pMethodName,"()V", _inout_MethodID);

    // call method
	t.pEnv->CallVoidMethod( object, _inout_MethodID );
	ThrowSQLException( t.pEnv, NULL );
}
// -------------------------------------------------------------------------
void java_lang_Object::callVoidMethodWithIntArg( const char* _pMethodName, jmethodID& _inout_MethodID, sal_Int32 _nArgument,bool _bIgnoreException ) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callIntMethod: no Java enviroment anymore!" );
    obtainMethodId(t.pEnv, _pMethodName,"(I)V", _inout_MethodID);

    // call method
	t.pEnv->CallVoidMethod( object, _inout_MethodID,_nArgument );
    if ( _bIgnoreException )
        isExceptionOccured(t.pEnv,sal_True);
    else
	    ThrowSQLException( t.pEnv, NULL );
}
// -------------------------------------------------------------------------
void java_lang_Object::callVoidMethodWithBoolArg( const char* _pMethodName, jmethodID& _inout_MethodID, sal_Int32 _nArgument,bool _bIgnoreException ) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callIntMethod: no Java enviroment anymore!" );
    obtainMethodId(t.pEnv, _pMethodName,"(Z)V", _inout_MethodID);
    // call method
	t.pEnv->CallVoidMethod( object, _inout_MethodID,_nArgument );
    if ( _bIgnoreException )
        isExceptionOccured(t.pEnv,sal_True);
    else
	    ThrowSQLException( t.pEnv, NULL );
}
// -----------------------------------------------------------------------------
::rtl::OUString java_lang_Object::callStringMethod( const char* _pMethodName, jmethodID& _inout_MethodID ) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callStringMethod: no Java enviroment anymore!" );
	
    // call method
    jstring out = (jstring)callObjectMethod(t.pEnv,_pMethodName,"()Ljava/lang/String;", _inout_MethodID);
	return JavaString2String( t.pEnv, out );
}
// -----------------------------------------------------------------------------
jobject java_lang_Object::callObjectMethod( JNIEnv * _pEnv,const char* _pMethodName,const char* _pSignature, jmethodID& _inout_MethodID ) const
{
    // obtain method ID
    obtainMethodId(_pEnv, _pMethodName,_pSignature, _inout_MethodID);
    // call method
	jobject out = _pEnv->CallObjectMethod( object, _inout_MethodID);
	ThrowSQLException( _pEnv, NULL );
    return out;
}

// -----------------------------------------------------------------------------
jobject java_lang_Object::callObjectMethodWithIntArg( JNIEnv * _pEnv,const char* _pMethodName,const char* _pSignature, jmethodID& _inout_MethodID , sal_Int32 _nArgument) const
{
    obtainMethodId(_pEnv, _pMethodName,_pSignature, _inout_MethodID);
    // call method
	jobject out = _pEnv->CallObjectMethod( object, _inout_MethodID,_nArgument );
	ThrowSQLException( _pEnv, NULL );
    return out;
}
// -----------------------------------------------------------------------------
::rtl::OUString java_lang_Object::callStringMethodWithIntArg( const char* _pMethodName, jmethodID& _inout_MethodID , sal_Int32 _nArgument) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callStringMethod: no Java enviroment anymore!" );
	jstring out = (jstring)callObjectMethodWithIntArg(t.pEnv,_pMethodName,"(I)Ljava/lang/String;",_inout_MethodID,_nArgument);
	return JavaString2String( t.pEnv, out );
}
// -------------------------------------------------------------------------
void java_lang_Object::callVoidMethodWithStringArg( const char* _pMethodName, jmethodID& _inout_MethodID,const ::rtl::OUString& _nArgument ) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callIntMethod: no Java enviroment anymore!" );
    obtainMethodId(t.pEnv, _pMethodName,"(Ljava/lang/String;)V", _inout_MethodID);

    jdbc::LocalRef< jstring > str( t.env(),convertwchar_tToJavaString(t.pEnv,_nArgument));
    // call method
	t.pEnv->CallVoidMethod( object, _inout_MethodID , str.get());
	ThrowSQLException( t.pEnv, NULL );
}
// -------------------------------------------------------------------------
sal_Int32 java_lang_Object::callIntMethodWithStringArg( const char* _pMethodName, jmethodID& _inout_MethodID,const ::rtl::OUString& _nArgument ) const
{
    SDBThreadAttach t;
    OSL_ENSURE( t.pEnv, "java_lang_Object::callIntMethodWithStringArg: no Java enviroment anymore!" );
    obtainMethodId(t.pEnv, _pMethodName,"(Ljava/lang/String;)I", _inout_MethodID);

    //TODO: Check if the code below is needed
            //jdbc::LocalRef< jstring > str( t.env(), convertwchar_tToJavaString( t.pEnv, sql ) );
            //{
            //    jdbc::ContextClassLoaderScope ccl( t.env(),
            //        m_pConnection ? m_pConnection->getDriverClassLoader() : jdbc::GlobalRef< jobject >(),
            //        m_aLogger,
            //        *this
            //    );

    jdbc::LocalRef< jstring > str( t.env(),convertwchar_tToJavaString(t.pEnv,_nArgument));
    // call method
	jint out = t.pEnv->CallIntMethod( object, _inout_MethodID , str.get());
	ThrowSQLException( t.pEnv, NULL );
    return (sal_Int32)out;
}
// -----------------------------------------------------------------------------
jclass java_lang_Object::findMyClass(const char* _pClassName)
{
	// die Klasse muss nur einmal geholt werden, daher statisch
	SDBThreadAttach t;
	jclass tempClass = t.pEnv->FindClass(_pClassName); OSL_ENSURE(tempClass,"Java : FindClass nicht erfolgreich!");
    if(!tempClass)
	{
		t.pEnv->ExceptionDescribe();
		t.pEnv->ExceptionClear();
	}
	jclass globClass = (jclass)t.pEnv->NewGlobalRef( tempClass );
	t.pEnv->DeleteLocalRef( tempClass );
	return globClass;
}

