j2hs-0.9: src/Typomatic.hs
{-# LANGUAGE Haskell2010
, GeneralizedNewtypeDeriving
, DeriveDataTypeable
#-}
{-# OPTIONS
-Wall
-fno-warn-name-shadowing
#-}
-- | INTERNAL module is used to infer Haskell types from Java types.
module Typomatic (
runTypomatic,
typomatic,
ArgInfo (..),
dataTName,
dataCName,
newtTName,
newtCName,
tyclTName
) where
import Language.Java.Reflect
import Control.Monad.State hiding (void)
import qualified Control.Monad.State as State
import Data.Functor.Identity
import Data.Functor ((<$>))
import Data.Generics
import qualified Data.Set as Set
import Data.Set (Set)
import qualified Data.Map as Map
import Data.Map (Map)
import qualified Data.List as List
dataTName, dataCName, newtTName, newtCName, tyclTName :: String -> String
dataTName = (++ "''")
dataCName = id -- (++ "")
newtTName = (++ "'")
newtCName = (++ "'")
tyclTName = id -- (++ "")
-- | This is the information which is ultimately
-- gathered by the use of this module.
data ArgInfo = ArgInfo {
fSignature :: String,
fArguments :: [TVar],
fReturnType :: TVar,
fArgNames :: [String],
fJavaSignature :: String,
fJavaReturnType :: String,
fJniSignature :: String
}
tr :: Eq a => a -> a -> [a] -> [a]
tr a b (x:xs)
| a == x = b : tr a b xs
| otherwise = x : tr a b xs
tr _ _ [] = []
breakLast :: [a] -> ([a], a)
breakLast [a] = ([], a)
breakLast (a:as) =
let (init', last') = breakLast as
in (a:init', last')
breakLast _ = error "Foreign.Java.Util.breakLast: empty list"
--------------------
-- Type variables --
--------------------
data TVar = TVar String | TVars [TVar]
deriving (Eq, Ord, Show, Data, Typeable)
printTVar :: TVar -> String
printTVar var = case var of
(TVar v) -> v
(TVars vs) -> concat ["(", tail (concatMap ((' ':) . printTVar) vs), ")"]
---------------
-- Utilities --
---------------
printJniType :: JavaType -> String
printJniType t = case t of
JBoolean -> "JNI.boolean"
JChar -> "JNI.char"
JByte -> "JNI.byte"
JShort -> "JNI.short"
JInt -> "JNI.int"
JLong -> "JNI.long"
JFloat -> "JNI.float"
JDouble -> "JNI.double"
JObj n -> "JNI.object \"" ++ n ++ "\""
-- arrays are treated as objects:
JArr c -> "JNIS.object' \"[" ++ printJniRawType c ++ "\""
printJniRawType :: JavaType -> String
printJniRawType t = case t of
JBoolean -> "Z"
JChar -> "C"
JByte -> "B"
JShort -> "S"
JInt -> "I"
JLong -> "J"
JFloat -> "F"
JDouble -> "D"
JObj n -> 'L' : tr '.' '/' n ++ ";"
JArr c -> '[' : printJniRawType c
printJniSignature :: JavaMethod -> String
printJniSignature method = show name ++ " JNI.::= " ++ args ++ ret
where
name = methodName method
args = concatMap ((++ " --> ") . printJniType) (map fst $ methodParams method)
ret = maybe "JNI.void" printJniType (fst $ methodReturnType method)
typomatic :: JavaClass -> JavaMethod -> Typomatic ArgInfo
typomatic clazz method_ = do
let className = classFullName clazz
classParams = classTypeParams clazz
-- sanitize tyVars tied to the method by distinguishing
-- from tyVars tied to the class by adding an apostrophe
-- to the name of the tyVar if it is tied to the method.
methodTypeVars = map paramName (methodTypeParams method_)
safe var@(TyVar str)
| var `elem` methodTypeVars = TyVar (str ++ "'")
| otherwise = var
method = everywhere (mkT safe) method_
-- the following three functions create the list of
-- argument parameters, including @this@ (if the method
-- is not static) and the return type. The return type
-- is separated later on again.
thisParam = if null classParams
then NotSoGeneric className
else Parameterized {
jgtBasetype = className,
jgtParameters = (map (TypeVarReference . paramName) classParams)
}
params = (if methodStatic method then [] else [thisParam])
++ map snd (methodParams method)
++ [maybe (TypeVarReference (TyVar "()"))
(const $ snd $ methodReturnType method)
(fst $ methodReturnType method)]
jtypes = (if methodStatic method then [] else [JObj className])
++ map fst (methodParams method)
-- turns a JavaGenericType definition into type variables.
-- The names are taken from the monad via 'newVar'.
tvar param = case jgtType param of
WildcardT -> do
name <- newVar
return $ TVar name
ParameterizedT -> do
name <- newVar
params <- mapM tvar (jgtParameters param)
return $ TVars $ TVar name : params
GenericArrayT -> do
name <- newVar
return $ TVar name
TypeVarReferenceT -> do
let name = (tyVarName (jgtName param))
return $ TVar name
NotSoGenericT -> do
name <- newVar
return $ TVar name
-- creates a haskell signature (-> String)
signature typeVars returnVar = do
-- retrieve the context and turn each variable into a String.
contexts <- getContext >>= mapM (\(tvar, context) -> do
return $ context ++ " " ++ printTVar tvar)
let argTypes = concatMap ((++ " -> ") . printTVar) typeVars
-- the final type is wrapped in the Java monad
returnType = "JNI.Java " ++ printTVar returnVar
-- finally assemble the conetext.
context = if null contexts then "" else
"(" ++ concat (List.intersperse ", " contexts) ++ ") => "
-- return the full signature, consisting of the context,
-- the type of the arguments, and the return type.
return $ concat [context, argTypes, returnType]
-- create a java signature (-> String)
javaSignature = do
let name = methodName method
args = map printJavaType $ map fst $ methodParams method
return $ name ++ "(" ++ concat (List.intersperse ", " args) ++ ")"
-- if this is not a static method the first argument
-- is /this/. This merely pushed the name into the list
-- of type variable names in the monad.
when (not $ methodStatic method) (pushVar "this")
-- get type variables for all arguments, including the
-- return type (as the return type may be the same as
-- one of the argument types).
--
-- Split the result into arguments and return var again,
-- since the return variable will get special treatment
-- henceforth.
(typeVars, returnVar_) <- breakLast <$> mapM tvar params
-- augment the return type variable, i.e. if it is not
-- a type variable at all, replace the variable name by
-- a constant reference to a specific type.
--
-- This is only the case with parameterized type variables
-- and not-so-generic ones.
returnVar <- case fst (methodReturnType method) of
Just (JObj typeName) -> case jgtType (snd $ methodReturnType method) of
ParameterizedT -> do
clazz <- getClass typeName
let (TVars (TVar _ : ts)) = returnVar_
return $ TVars (TVar (newtTName (classModName clazz)) : ts)
NotSoGenericT -> do
clazz <- getClass typeName
return $ TVar (newtTName (classModName clazz))
_ -> return returnVar_
_ -> return returnVar_
-- Add contexts for all arguments and augment array types
let makeContext typeVar jtype = case jtype of
JObj name -> do
clazz <- getClass name
addContext typeVar $ tyclTName $ classModName clazz
return typeVar
JArr componentType -> do
addContext typeVar "JNIS.Array"
return typeVar
JBoolean -> do
addContext typeVar "JNIS.JBoolean"
return typeVar
JChar -> do
addContext typeVar "JNIS.JChar"
return typeVar
JByte -> do
addContext typeVar "JNIS.JByte"
return typeVar
JShort -> do
addContext typeVar "JNIS.JShort"
return typeVar
JInt -> do
addContext typeVar "JNIS.JInt"
return typeVar
JLong -> do
addContext typeVar "JNIS.JLong"
return typeVar
JFloat -> do
addContext typeVar "JNIS.JFloat"
return typeVar
JDouble -> do
addContext typeVar "JNIS.JDouble"
return typeVar
-- Here makeContexts is applied (see above). In the same pass a new
-- set of typeVars (typeVars') is generated, since makeContexts
-- migth further investigate array and create type variables for
-- their component types.
typeVars' <- mapM (uncurry makeContext) (zip typeVars jtypes)
-- Create contexts for the ultimate return type.
returnVar' <- case fst (methodReturnType method) of
Nothing -> do
let tvar = TVar "void"
addContext tvar "JNIS.VoidResult"
return tvar
Just t -> case t of
JObj _ -> do
let tvar = TVars [TVar "object", returnVar]
addContext tvar "JNIS.ObjectResult"
return tvar
JArr _ -> do
let tvar = TVar "array"
addContext tvar "JNIS.ArrayResult"
return tvar
JBoolean -> do
let tvar = TVar "boolean"
addContext tvar "JNIS.BooleanResult"
return tvar
JChar -> do
let tvar = TVar "char"
addContext tvar "JNIS.CharResult"
return tvar
JByte -> do
let tvar = TVar "byte"
addContext tvar "JNIS.ByteResult"
return tvar
JShort -> do
let tvar = TVar "short"
addContext tvar "JNIS.ShortResult"
return tvar
JInt -> do
let tvar = TVar "int"
addContext tvar "JNIS.IntResult"
return tvar
JLong -> do
let tvar = TVar "long"
addContext tvar "JNIS.LongResult"
return tvar
JFloat -> do
let tvar = TVar "float"
addContext tvar "JNIS.FloatResult"
return tvar
JDouble -> do
let tvar = TVar "double"
addContext tvar "JNIS.DoubleResult"
return tvar
-- generate the Haskell signature (a String)
sig <- signature typeVars' returnVar'
-- generate the Java signature (a String).
-- This is used for documentation purposed later on
-- (i.e. inserted as haddock docstring).
jsig <- javaSignature
-- generate the names of the arguments
let argNames = (if methodStatic method then id else ("this":) . init)
$ zipWith (\_ i -> 'a' : show i) typeVars [(1 :: Integer)..]
-- assemble and return all the calculated information
return $ ArgInfo {
fArguments = typeVars,
fReturnType = returnVar',
fArgNames = argNames,
fSignature = sig,
fJavaSignature = jsig,
fJavaReturnType = maybe "void" printJavaType (fst $ methodReturnType method),
fJniSignature = printJniSignature method
}
--------------------------------------------
-- The following are utilities for the monad
--------------------------------------------
-- | The state of the monad.
data TypomaticState = TypomaticState {
tVars :: [String],
tContext :: Set (TVar, String),
tParams :: [String],
tClasses :: Map String JavaClass
}
-- | Retrieve the definition of a class.
--
-- The monad has an internal store of class names
-- and their definitions. See 'tClasses'.
getClass :: String -> Typomatic JavaClass
getClass name = do
state <- State.get
return ((tClasses state) Map.! name)
-- | Get the current context as a list.
getContext :: Typomatic [(TVar, String)]
getContext = State.get >>= return . Set.toList . tContext
-- | Add a context for a specific type variable.
addContext :: TVar -> String -> Typomatic ()
addContext tvar string = do
state <- State.get
State.put (state {tContext = ((tvar, string) `Set.insert` tContext state)})
-- | Introduce a new name.
--
-- This simply takes the next element in the infinite
-- 'tVars' list and stores the tail back in the monad.
newVar :: Typomatic String
newVar = do
state <- State.get
let (v:vs) = tVars state
State.put (state { tVars = vs})
return v
-- | Push a new name in the front of the available names.
pushVar :: String -> Typomatic ()
pushVar name = do
state <- State.get
State.put (state {tVars = (name : tVars state)})
-- | The monad.
newtype Typomatic a = Typomatic { _runTypomatic :: StateT TypomaticState Identity a }
deriving (Monad, MonadState TypomaticState, Functor)
-- | Run a computation in the monad.
runTypomatic :: Map String JavaClass -> Typomatic a -> a
runTypomatic classes =
let state = TypomaticState { -- initial state
tVars = map (('v':) . show) [(1 :: Integer)..],
tContext = Set.empty,
tParams = [],
tClasses = classes
} in fst . runIdentity . flip runStateT state . _runTypomatic