-- | JNI strings. Like C strings and unlike 'Data.ByteString.ByteString', these
-- are null-terminated. Unlike C strings, each character is (multi-byte) encoded
-- as UTF-8. Unlike UTF-8, embedded NULL characters are encoded as two bytes and
-- the four-byte UTF-8 format for characters is not recognized. A custom
-- encoding is used instead. See
-- <http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#modified_utf_8_strings>
-- for more details.
--
-- /NOTE:/ the current implementation does not support embedded NULL's and
-- four-byte characters.

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE MagicHash #-}

module Foreign.JNI.String
  ( String
  , toChars
  , fromChars
  , fromByteString
  , unsafeFromByteString
  , toByteString
  , withString
  ) where

import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Unsafe as BS
import Data.ByteString (ByteString)
import Data.String (IsString(..))
import Foreign.C.String (CString)
import qualified GHC.Foreign as GHC
import qualified GHC.IO.Encoding as GHC
import System.IO.Unsafe (unsafeDupablePerformIO)
import qualified Prelude
import Prelude hiding (String)

-- A JNI string is represented as a NUL terminated bytestring in UTF-8 encoding.
newtype String = String ByteString
  deriving (String -> String -> Bool
(String -> String -> Bool)
-> (String -> String -> Bool) -> Eq String
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: String -> String -> Bool
$c/= :: String -> String -> Bool
== :: String -> String -> Bool
$c== :: String -> String -> Bool
Eq, Eq String
Eq String
-> (String -> String -> Ordering)
-> (String -> String -> Bool)
-> (String -> String -> Bool)
-> (String -> String -> Bool)
-> (String -> String -> Bool)
-> (String -> String -> String)
-> (String -> String -> String)
-> Ord String
String -> String -> Bool
String -> String -> Ordering
String -> String -> String
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: String -> String -> String
$cmin :: String -> String -> String
max :: String -> String -> String
$cmax :: String -> String -> String
>= :: String -> String -> Bool
$c>= :: String -> String -> Bool
> :: String -> String -> Bool
$c> :: String -> String -> Bool
<= :: String -> String -> Bool
$c<= :: String -> String -> Bool
< :: String -> String -> Bool
$c< :: String -> String -> Bool
compare :: String -> String -> Ordering
$ccompare :: String -> String -> Ordering
$cp1Ord :: Eq String
Ord)

instance Show String where
  show :: String -> String
show String
str = ShowS
forall a. Show a => a -> String
show (String -> String
toChars String
str)

instance IsString String where
  fromString :: String -> String
fromString String
str = String -> String
fromChars String
str

fromChars :: Prelude.String -> String
{-# INLINE [0] fromChars #-}
fromChars :: String -> String
fromChars String
str = IO String -> String
forall a. IO a -> a
unsafeDupablePerformIO (IO String -> String) -> IO String -> String
forall a b. (a -> b) -> a -> b
$
    TextEncoding -> String -> (CString -> IO String) -> IO String
forall a. TextEncoding -> String -> (CString -> IO a) -> IO a
GHC.withCString TextEncoding
GHC.utf8 String
str ((CString -> IO String) -> IO String)
-> (CString -> IO String) -> IO String
forall a b. (a -> b) -> a -> b
$ \CString
cstr -> do
      -- we need to copy the trailing NUL
      CSize
len <- CString -> IO CSize
BS.c_strlen CString
cstr
      ByteString -> String
String (ByteString -> String) -> IO ByteString -> IO String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> CStringLen -> IO ByteString
BS.packCStringLen (CString
cstr, CSize -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral CSize
len Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)

toChars :: String -> Prelude.String
toChars :: String -> String
toChars (String ByteString
bs) = IO String -> String
forall a. IO a -> a
unsafeDupablePerformIO (IO String -> String) -> IO String -> String
forall a b. (a -> b) -> a -> b
$ ByteString -> (CString -> IO String) -> IO String
forall a. ByteString -> (CString -> IO a) -> IO a
BS.unsafeUseAsCString ByteString
bs ((CString -> IO String) -> IO String)
-> (CString -> IO String) -> IO String
forall a b. (a -> b) -> a -> b
$ TextEncoding -> CString -> IO String
GHC.peekCString TextEncoding
GHC.utf8

withString :: String -> (CString -> IO a) -> IO a
withString :: String -> (CString -> IO a) -> IO a
withString (String ByteString
bs) CString -> IO a
f = ByteString -> (CString -> IO a) -> IO a
forall a. ByteString -> (CString -> IO a) -> IO a
BS.unsafeUseAsCString ByteString
bs CString -> IO a
f

-- Discards the trailing NUL.
toByteString :: String -> ByteString
toByteString :: String -> ByteString
toByteString (String ByteString
bs) = ByteString -> ByteString
BS.init ByteString
bs

-- | O(1) if the input is null-terminated. Otherwise the input is copied into
-- a null-terminated buffer first.
fromByteString :: ByteString -> String
fromByteString :: ByteString -> String
fromByteString ByteString
bs
  | ByteString -> Word8
BS.last ByteString
bs Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0 = ByteString -> String
String ByteString
bs
  | Bool
otherwise = ByteString -> String
String (ByteString
bs ByteString -> Word8 -> ByteString
`BS.snoc` Word8
0)

-- | Same as 'fromByteString', but doesn't check whether the input is
-- null-terminated or not.
unsafeFromByteString :: ByteString -> String
unsafeFromByteString :: ByteString -> String
unsafeFromByteString = ByteString -> String
String