packages feed

jvm-0.6.0: src/common/Language/Java/Internal.hs

-- | Internal functions to invoke JNI methods
--
-- The functions in this module avoid using
-- 'Language.Java.Coercible' so they can be reused in interfaces which
-- use other ways to convert between Haskell and Java values.
--
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeApplications #-}

module Language.Java.Internal
  ( newJ
  , callToJValue
  , callStaticToJValue
  , getStaticFieldAsJValue
  , getClass
  , setGetClassFunction
  ) where

import Data.IORef
import Data.Singletons (SingI(..), SomeSing(..))
import Foreign.JNI hiding (throw)
import Foreign.JNI.Types
import qualified Foreign.JNI.String as JNI
import System.IO.Unsafe (unsafeDupablePerformIO, unsafePerformIO)

-- | Sets the function to use for loading classes.
--
-- 'findClass' is used by default.
--
setGetClassFunction
  :: (forall ty. IsReferenceType ty => Sing (ty :: JType) -> IO JClass)
  -> IO ()
setGetClassFunction f = writeIORef getClassFunctionRef $ GetClassFun f

-- | Yields a class referece. It behaves as 'findClass' unless
-- 'setGetClassFunction' is used.
getClass :: IsReferenceType ty => Sing (ty :: JType) -> IO JClass
getClass s = readIORef getClassFunctionRef >>= \(GetClassFun f) -> f s

newtype GetClassFun =
    GetClassFun (forall ty. IsReferenceType ty =>
                   Sing (ty :: JType) -> IO JClass
                )

{-# NOINLINE getClassFunctionRef #-}
getClassFunctionRef :: IORef GetClassFun
getClassFunctionRef =
    unsafePerformIO $ newIORef (GetClassFun (findClass . referenceTypeName))

newJ
  :: forall sym ty.
     ( ty ~ 'Class sym
     , SingI ty
     )
  => [SomeSing JType] -- ^ Singletons of argument types
  -> [JValue]
  -> IO (J ty)
{-# INLINE newJ #-}
newJ argsings args = do
    let voidsing = sing :: Sing 'Void
        klass = unsafeDupablePerformIO $ do
          lk <- getClass (sing :: Sing ('Class sym))
          gk <- newGlobalRef lk
          deleteLocalRef lk
          return gk
    unsafeCast <$> newObject klass (methodSignature argsings voidsing) args

callToJValue
  :: forall ty1 k. (IsReferenceType ty1, SingI ty1)
  => Sing (k :: JType)
  -> J ty1 -- ^ Any object
  -> JNI.String -- ^ Method name
  -> [SomeSing JType] -- ^ Singletons of argument types
  -> [JValue] -- ^ Arguments
  -> IO JValue
{-# INLINE callToJValue #-}
callToJValue retsing obj mname argsings args = do
    let klass = unsafeDupablePerformIO $ do
                  lk <- getClass (sing :: Sing ty1)
                  gk <- newGlobalRef lk
                  deleteLocalRef lk
                  return gk
        method = unsafeDupablePerformIO $ getMethodID klass mname (methodSignature argsings retsing)
    case retsing of
      SPrim "boolean" -> JBoolean . fromIntegral . fromEnum <$>
                           callBooleanMethod obj method args
      SPrim "byte" -> JByte <$> callByteMethod obj method args
      SPrim "char" -> JChar <$> callCharMethod obj method args
      SPrim "short" -> JShort <$> callShortMethod obj method args
      SPrim "int" -> JInt <$> callIntMethod obj method args
      SPrim "long" -> JLong <$> callLongMethod obj method args
      SPrim "float" -> JFloat <$> callFloatMethod obj method args
      SPrim "double" -> JDouble <$> callDoubleMethod obj method args

      SVoid -> do
        callVoidMethod obj method args
        -- The void result is not inspected.
        return (error "inspected output of method returning void")
      _ -> JObject <$> callObjectMethod obj method args

callStaticToJValue
  :: Sing (k :: JType)
  -> JNI.String -- ^ Class name
  -> JNI.String -- ^ Method name
  -> [SomeSing JType] -- ^ Singletons of argument types
  -> [JValue] -- ^ Arguments
  -> IO JValue
{-# INLINE callStaticToJValue #-}
callStaticToJValue retsing cname mname argsings args = do
    let klass = unsafeDupablePerformIO $ do
                  lk <- getClass (SClass (JNI.toChars cname))
                  gk <- newGlobalRef lk
                  deleteLocalRef lk
                  return gk
        method = unsafeDupablePerformIO $ getStaticMethodID klass mname (methodSignature argsings retsing)
    case retsing of
      SPrim "boolean" -> JBoolean . fromIntegral . fromEnum <$>
                           callStaticBooleanMethod klass method args
      SPrim "byte" -> JByte <$> callStaticByteMethod klass method args
      SPrim "char" -> JChar <$> callStaticCharMethod klass method args
      SPrim "short" -> JShort <$> callStaticShortMethod klass method args
      SPrim "int" -> JInt <$> callStaticIntMethod klass method args
      SPrim "long" -> JLong <$> callStaticLongMethod klass method args
      SPrim "float" -> JFloat <$> callStaticFloatMethod klass method args
      SPrim "double" -> JDouble <$> callStaticDoubleMethod klass method args
      SVoid -> do
        callStaticVoidMethod klass method args
        -- The void result is not inspected.
        return (error "inspected output of method returning void")
      _ -> JObject <$> callStaticObjectMethod klass method args

getStaticFieldAsJValue
  :: Sing (ty :: JType)
  -> JNI.String -- ^ Class name
  -> JNI.String -- ^ Static field name
  -> IO JValue
{-# INLINE getStaticFieldAsJValue #-}
getStaticFieldAsJValue retsing cname fname = do
  let klass = unsafeDupablePerformIO $ do
                lk <- getClass (SClass (JNI.toChars cname))
                gk <- newGlobalRef lk
                deleteLocalRef lk
                return gk
      field = unsafeDupablePerformIO $ getStaticFieldID klass fname (signature retsing)
  case retsing of
    SPrim "boolean" -> JBoolean <$> getStaticBooleanField klass field
    SPrim "byte" -> JByte <$> getStaticByteField klass field
    SPrim "char" -> JChar <$> getStaticCharField klass field
    SPrim "short" -> JShort <$> getStaticShortField klass field
    SPrim "int" -> JInt <$> getStaticIntField klass field
    SPrim "long" -> JLong <$> getStaticLongField klass field
    SPrim "float" -> JFloat <$> getStaticFloatField klass field
    SPrim "double" -> JDouble <$> getStaticDoubleField klass field
    SVoid -> fail "getStaticField cannot yield an object of type void"
    _ -> JObject <$> getStaticObjectField klass field