jvm-streaming-0.2: src/Language/Java/Streaming.hs
-- | Expose Java iterators as streams from the
-- <http://hackage.haskell.org/package/streaming streaming> package.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
module Language.Java.Streaming () where
import Control.Distributed.Closure.TH
import qualified Data.Coerce as Coerce
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Int (Int64)
import Data.Singletons (SomeSing(..))
import Data.Word (Word8)
import Foreign.Ptr (FunPtr, Ptr, freeHaskellFunPtr, intPtrToPtr, ptrToIntPtr)
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
import qualified Foreign.JNI as JNI
import Foreign.JNI.Types (jnull)
import GHC.Stable
( castPtrToStablePtr
, castStablePtrToPtr
, deRefStablePtr
, freeStablePtr
, newStablePtr
)
import Language.Java
import Language.Java.Inline
import Streaming (Stream, Of)
import qualified Streaming.Prelude as Streaming
isPoppableStream :: IORef (Stream (Of a) IO ()) -> IO Word8
isPoppableStream ref = do
readIORef ref >>= Streaming.uncons >>= \case
Nothing -> return $ fromIntegral $ fromEnum False
Just (x, stream) -> do
writeIORef ref (Streaming.cons x stream)
return $ fromIntegral $ fromEnum True
popStream :: Reflect a ty => IORef (Stream (Of a) IO ()) -> IO (J ty)
popStream ref = do
stream <- readIORef ref
Streaming.uncons stream >>= \case
Nothing -> do
exc :: J ('Class "java.util.NoSuchElementException") <- new []
JNI.throw exc
return jnull
Just (x, stream') -> do
writeIORef ref stream'
reflect x
type JNIFun a = JNIEnv -> Ptr JObject -> IO a
foreign import ccall "wrapper" wrapObjectFun
:: JNIFun (Ptr (J ty)) -> IO (FunPtr (JNIFun (Ptr (J ty))))
foreign import ccall "wrapper" wrapBooleanFun
:: JNIFun Word8 -> IO (FunPtr (JNIFun Word8))
-- Export only to get a FunPtr.
foreign export ccall "jvm_streaming_freeIterator" freeIterator
:: JNIEnv -> Ptr JObject -> Int64 -> IO ()
foreign import ccall "&jvm_streaming_freeIterator" freeIteratorPtr
:: FunPtr (JNIEnv -> Ptr JObject -> Int64 -> IO ())
data FunPtrTable = forall ty. FunPtrTable
{ hasNextPtr :: FunPtr (JNIFun Word8)
, nextPtr :: FunPtr (JNIFun (Ptr (J ty)))
}
freeIterator :: JNIEnv -> Ptr JObject -> Int64 -> IO ()
freeIterator _ _ ptr = do
let sptr = castPtrToStablePtr $ intPtrToPtr $ fromIntegral ptr
FunPtrTable{..} <- deRefStablePtr sptr
freeHaskellFunPtr hasNextPtr
freeHaskellFunPtr nextPtr
freeStablePtr sptr
newIterator
:: Reflect a ty
=> Stream (Of a) IO ()
-> IO (J ('Iface "java.util.Iterator"))
newIterator stream = do
ref <- newIORef stream
hasNextPtr <- wrapBooleanFun $ \_ _ -> isPoppableStream ref
nextPtr <- wrapObjectFun $ \_ _ ->
-- Conversion is safe, because result is always a reflected object.
unsafeForeignPtrToPtr <$> Coerce.coerce <$> popStream ref
-- Keep FunPtr's in a table that can be referenced from the Java side, so
-- that they can be freed.
tblPtr :: Int64 <- fromIntegral . ptrToIntPtr . castStablePtrToPtr <$> newStablePtr FunPtrTable{..}
iterator <-
[java| new java.util.Iterator() {
@Override
public native boolean hasNext();
@Override
public native Object next();
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private native void hsFinalize(long tblPtr);
@Override
public void finalize() {
hsFinalize($tblPtr);
}
} |]
klass <- JNI.getObjectClass iterator
JNI.registerNatives klass
[ JNI.JNINativeMethod
"hasNext"
(methodSignature [] (sing :: Sing ('Prim "boolean")))
hasNextPtr
, JNI.JNINativeMethod
"next"
(methodSignature [] (sing :: Sing ('Class "java.lang.Object")))
nextPtr
, JNI.JNINativeMethod
"hsFinalize"
(methodSignature [SomeSing (sing :: Sing ('Prim "long"))] (sing :: Sing 'Void))
freeIteratorPtr
]
return iterator
withStatic [d|
type instance Interp (Stream (Of a) m r) = 'Iface "java.util.Iterator"
instance Reify a ty => Reify (Stream (Of a) IO ()) ('Iface "java.util.Iterator") where
reify itLocal = do
-- We make sure the iterator remains valid while we reference it.
it <- JNI.newGlobalRef itLocal
return $ Streaming.untilRight $ do
call it "hasNext" [] >>= \case
False -> return (Right ())
True -> do
obj <- call it "next" []
Left <$> reify (unsafeCast (obj :: JObject))
instance Reflect a ty => Reflect (Stream (Of a) IO ()) ('Iface "java.util.Iterator") where
reflect x = newIterator x
|]