packages feed

j2hs-0.99.1: src/Translate.hss

{-# LANGUAGE Haskell2010 #-}
{-# OPTIONS -Wall #-}

module Translate where

import Typomatic
import Utils

import Foreign.Java.Utils
import Data.Map (Map)

import Language.Haskell.Reflect
import Language.Java.Reflect
import qualified Language.Java.Reflect.Types as Types

import Data.Function

import qualified Data.Char as Char
import qualified Data.List as List
import qualified Data.Set  as Set
import qualified Data.Map  as Map

import qualified Haskell.X as X

import Control.Arrow


checkedLookup info name = maybe fail id (Map.lookup name info)
  where fail = error """class definition for '#{name}' not found."""



pkgModExports :: ClassInfo -> [JavaClass] -> String
pkgModExports info classes = concatMap (\(t, b) -> """    -- * #{show t}s\n#{b}""")
        $ map (second $ concatMap format)
        $ X.aggregateAL
        $ zip (map classType classes) classes
  where
    format clazz =
        let modName   = (classModName info . className) clazz
            haskName  = takeClassName $ modName
            javaName  = takeClassName $ className clazz
            dataDecl  = if classIface clazz then "" else
                """    #{dataTName haskName} (..),\n"""
        in """
            -- ** #{show $ classType clazz} #{javaName}
            -- | For constructors, methods, and so on, see: "#{modName}".
            #{tyclTName haskName},
        #{dataDecl}\
            #{newtTName haskName} #{if classEnum clazz then "(..)" else ""},
        """


pkgModImports :: ClassInfo -> [JavaClass] -> String
pkgModImports info classes = concatMap (format . getModName) classes
  where
    format modName = """import #{modName}__ hiding (info')\n"""
    getModName = (classModName info . className)


pkgModDecl :: ClassInfo -> [JavaClass] -> String
pkgModDecl info classes = ""


classModImports :: ClassInfo -> JavaClass -> String
classModImports info clazz = concatMap format references
  where
    format package = """import qualified #{package}\n"""
    references = Set.toList
        $ Set.map (fst . splitClassName . getModName . checkedLookup info)
        $ Set.fromList (className clazz : dependencies)
    dependencies = classDependencies clazz
    getModName = (classModName info . className)


classModExports :: ClassInfo -> JavaClass -> String
classModExports info clazz = "    -- * Methods\n" ++ concatMap methodExport methodNames
  where
    methods = List.sortBy (compare `on` methodName) (classMethods clazz)
    methodNames = mkMethodNames methods
    methodExport name = name ++ ",    \n"


classModDecl :: ClassInfo -> JavaClass -> String
classModDecl info clazz = concatMap methodDecl $ zip methodNames methods
  where
    methods = List.sortBy (compare `on` methodName) (classMethods clazz)
    methodNames = mkMethodNames methods
    methodDecl (name, method)
        | arrayTypes = """
            #{name} :: #{signature}
            #{name} = Prelude.undefined
            """
        | otherwise = """
            #{haddock}
            #{name} :: #{signature}
            #{name}#{argsDecl} = do
                (Prelude.Just clazz) <- JNI.getClass "#{className clazz}"
                (Prelude.Just method) <- clazz `JNI.#{getMethod}` #{jniSignature}
            #{thisArg}\
            #{readArgs}\
                result <- JNI.#{callMethod} method #{argsRefs}
                #{convertResult} result
            """
      where
        arrayTypes
--            | any isArrayType (methodParams method) = True
            | (Just ret) <- fst (methodReturnType method) = isArrayType ret
            | otherwise = False
        isArrayType x = case x of JArr _ -> True ; _ -> False
        isStatic = methodStatic method
        javaSignature = fJavaSignature argsInfo
        javaReturnType = fJavaReturnType argsInfo
        jniSignature = fJniSignature argsInfo
        signature = fSignature argsInfo
        getMethod = if isStatic then "getStaticMethod" else "getMethod"
        callMethod = if isStatic then "callStaticMethodE" else "callMethodE"
        args = fArgNames argsInfo
        argsNotThis = (if isStatic then id else tail) args
        argsInfo = runTypomatic info (typomatic clazz method)
        argsDecl = concatMap (' ':) args
        argsRefs = concatMap (++ "' ") args
        haddock = """
            -- | @#{if isStatic then "static " else "virtual "}\
            #{if methodSynchronized method then "synchronized " else ""}\
            #{if methodNative method then "native " else ""}\
            #{if methodFinal method then "final " else ""}\
            #{javaSignature} -> #{javaReturnType}@"""
        thisArg = if isStatic then "" else """    #{head args}' <- JNI.asObject #{head args}\n"""
        readArgs = concatMap (("    " ++). uncurry readArg) (zip argsNotThis (map fst $ methodParams method))
        readArg name jtype = case jtype of
            JBoolean -> """#{name}' <- JNIS.toBoolean #{name}\n"""
            JChar    -> """#{name}' <- JNIS.toChar #{name}\n"""
            JByte    -> """#{name}' <- JNIS.toByte #{name}\n"""
            JShort   -> """#{name}' <- JNIS.toShort #{name}\n"""
            JInt     -> """#{name}' <- JNIS.toInt #{name}\n"""
            JLong    -> """#{name}' <- JNIS.toLong #{name}\n"""
            JFloat   -> """#{name}' <- JNIS.toFloat #{name}\n"""
            JDouble  -> """#{name}' <- JNIS.toDouble #{name}\n"""
            JObj _   -> """#{name}' <- Prelude.Just <$> JNI.asObject #{name}\n"""
            JArr _   -> """#{name}' <- JNIS.asMaybeArrayObject #{name}\n"""
        convertResult = ("JNIS." ++) $ case fst (methodReturnType method) of
            Nothing -> "toVoidResult"
            Just t -> case t of
                JBoolean -> "toBooleanResult"
                JChar    -> "toCharResult"
                JByte    -> "toByteResult"
                JShort   -> "toShortResult"
                JInt     -> "toIntResult"
                JLong    -> "toLongResult"
                JFloat   -> "toFloatResult"
                JDouble  -> "toDoubleResult"
                JObj n   -> "toObjectResult"
                JArr c   -> "toArrayResult"

        

mkMethodNames :: [JavaMethod] -> [String]
mkMethodNames = concatMap (zipWith (flip (++)) (iterate (++"'") ""))
                . List.group . map sanitize . map methodName
  where
    sanitize name
        | Char.isUpper (head name) = '_' : name
        | name `elem` haskellKeywords = name ++ "'"
        | otherwise = name


-- Hidden Modules and Boot files


classModExports' :: ClassInfo -> JavaClass -> String
classModExports' info clazz = """
        #{tyclTName haskName},
        #{dataTName haskName} (..),
        #{newtTName haskName} #{if classEnum clazz then "(..)" else ""},
    """
  where
    haskName = takeClassName $ classModName clazz
    javaName = takeClassName $ className clazz


classBootExports' :: ClassInfo -> JavaClass -> String
classBootExports' info clazz = """
        #{tyclTName haskName},
        #{dataTName haskName} (..),
        #{newtTName haskName},
    """
  where
    haskName = takeClassName $ classModName clazz
    javaName = takeClassName $ className clazz


classBootImports' :: ClassInfo -> JavaClass -> String
classBootImports' info clazz
    = concatMap (format . classModName) supertypes
  where
    format modName = """import qualified #{modName}__\n"""
    supertypes = map (checkedLookup info)
               $ Set.toList
               $ Set.fromList (classParents clazz ++ classIfaces clazz)


classModImports' :: ClassInfo -> JavaClass -> String
classModImports' info clazz = ""


classBootDecl' :: ClassInfo -> JavaClass -> String
classBootDecl' info clazz
    | classEnum clazz = """
        class #{tyclTDecl}
        data #{newtTDecl} = #{enumConstants}
        data #{dataTDecl} = #{dataCDecl}
        """
    | otherwise = """
        class #{tyclTDecl}
        newtype #{newtTDecl} = #{newtCDecl} JNI.JObject
        data #{dataTDecl} = #{dataCDecl}
        """
  where baseName  = takeClassName $ classModName clazz
        params    = concatMap (\(p:ps) -> ' ' : Char.toLower p : ps)
                      $ map (tyVarName . paramName) $ classTypeParams clazz
        newtTDecl = newtTName baseName ++ params
        newtCDecl = newtCName baseName
        dataTDecl = dataTName baseName ++ params
        dataCDecl = dataCName baseName ++ params
        tyclTDecl = """(JNI.JavaObject this#{context}) => #{tyclTName baseName} this"""
        context = concatMap parentContext (classParents clazz)
        parentContext parent = """, #{qualifiedName} this"""
          where parentClass = info Map.! parent
                modName = classModName parentClass
                qualifiedName = modName ++ "__." ++ (tyclTName $ takeClassName modName)
        enumConstants = concat $ List.intersperse " | " $ map sanitize $ map snd $ classEnumConstants clazz
          where sanitize (x:xs) = Char.toUpper x : xs


classModDecl' :: ClassInfo -> JavaClass -> String
classModDecl' info clazz
    | classEnum clazz = """
        instance JNI.JavaObject (#{newtTDecl}) where
            asObject = Prelude.undefined
        instance #{tyclTName baseName} (#{newtTDecl})
        instance JNIS.InstanceOf (#{dataTDecl}) where
            type CoercedType (#{dataTDecl}) = (#{newtTDecl})
        #{instances}
        """
    | otherwise = """
        instance JNI.JavaObject (#{newtTDecl}) where
            asObject (#{newtCName baseName} obj) = return obj
        instance JNIS.UnsafeCast (#{newtTDecl}) where
            unsafeFromJObject obj = return (#{newtCDecl} obj)
        instance #{tyclTName baseName} (#{newtTDecl})
        instance JNIS.InstanceOf (#{dataTDecl}) where
            type CoercedType (#{dataTDecl}) = (#{newtTDecl})
            coerce o t = do
                obj <- JNI.asObject o
                (Prelude.Just clazz) <- JNI.getClass "#{fullClassName}"
                isInstanceOf <- obj `JNI.isInstanceOf` clazz
                if isInstanceOf
                    then Prelude.Just <$> (JNIS.unsafeFromJObject obj)
                    else return Prelude.Nothing
        #{instances}
        """
  where fullClassName = className clazz
        baseName  = takeClassName $ classModName clazz
        params    = concatMap (\(p:ps) -> ' ' : Char.toLower p : ps)
                      $ map (tyVarName . paramName) $ classTypeParams clazz
        newtTDecl = newtTName baseName ++ params
        newtCDecl = newtCName baseName
        dataTDecl = dataTName baseName ++ params
        dataCDecl = dataCName baseName ++ params
        instances = concatMap parentInstance (classParents clazz)
        parentInstance parent = """
            instance #{qualifiedName} (#{newtTDecl})
            """
          where parentClass = info Map.! parent
                modName = classModName parentClass
                qualifiedName = modName ++ "__." ++ (tyclTName $ takeClassName modName)