{-# LANGUAGE Haskell2010 #-} {-# OPTIONS -Wall -fno-warn-name-shadowing #-} {- | The @j2hs@ command - create Haskell bindings for Java classes. -} module Java2Haskell where import Prelude hiding (print) import Options import Bindings import Utils import Foreign.Java import Foreign.Java.Utils import Foreign.Java.IO import Language.Java.Reflect import Data.NamedRecord import qualified Data.List as L import Data.Strings import Data.Generics import Data.Bimap (Bimap) import Data.Map (Map) import Data.MultiMap (MultiMap) import Data.Set (Set) import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.MultiMap as MultiMap import Control.Monad import Control.Monad.IO.Class import System.Directory import System.FilePath import System.IO hiding (print) import System.Exit ifVerbose :: MonadIO m => Options -> String -> m () ifVerbose opts = when (opts `get` optVerbose) . liftIO . putStr j2hs :: Options -> [String] -> IO () j2hs opts args = do classpath <- mapM canonicalizePath $ opts `get` optClasspath targetDir <- return (opts `get` optTargetDirectory) let reflectDump_ = opts `get` optLoadReflectDump reflectDump <- if null reflectDump_ then return Nothing else canonicalizePath reflectDump_ >>= return . Just let classpath' = concat $ L.intersperse ":" classpath indent = (" " ++) ifVerbose opts ((\_ -> let { __ = {-# LINE 70 "Java2Haskell.hss" #-} concat ["Classpath:\n", (concatMap (\x -> " " ++ x ++ "\n") (classpath)), ""] {-# LINE 72 "Java2Haskell.hss" #-} } in __) undefined) print "Initializing JVM..." initJava [((\_ -> let { __ = {-# LINE 74 "Java2Haskell.hss" #-} concat ["-Djava.class.path=", (classpath'), ""] {-# LINE 74 "Java2Haskell.hss" #-} } in __) undefined)] println " Done." unless (null args) $ runJava $ do io $ do ifVerbose opts ((\_ -> let { __ = {-# LINE 79 "Java2Haskell.hss" #-} concat ["Target Directory:\n ", (targetDir), "\n"] {-# LINE 82 "Java2Haskell.hss" #-} } in __) undefined) createDirectoryIfMissing True targetDir setCurrentDirectory targetDir ifVerbose opts ((\_ -> let { __ = {-# LINE 86 "Java2Haskell.hss" #-} concat ["Looking for Java classes:\n", (concatMap (\x -> " " ++ x ++ "\n") args), ""] {-# LINE 88 "Java2Haskell.hss" #-} } in __) undefined) -- Gather all classes that need to be translated print "Looking up classes..." classes <- findClasses (opts `get` optSearchDepth) args println ((\_ -> let { __ = {-# LINE 94 "Java2Haskell.hss" #-} concat [" ", (show $ length classes), " found."] {-# LINE 94 "Java2Haskell.hss" #-} } in __) undefined) ifVerbose opts $ unlines (map indent classes) -- Calculate the Haskell module names for packages and classes let packages = Set.toList $ Set.fromList $ map (maybe "" id . takePackageName) classes -- temporary class names classModules_ = foldr (uncurry g) xEmpty $ map f classes where f x = (x, makeClassModuleName x) g clazzName modName all = xInsert clazzName newName all where newName = head $ dropWhile (`xMemberR` all) $ iterate (++ "_") modName -- package names. -- This func keeps in mind that package names must not clash with class names. packageModules = foldr (uncurry g) xEmpty $ map f packages where f x = (x, makePackageModuleName x) g pkgName modName all = xInsert pkgName newName all where newName = head $ dropWhile (\n -> n `xMemberR` classModules_ || n `xMemberR` all) $ iterate (++ "Package") modName -- finally the class names. -- This func keeps in mind that the package names might have been augmented -- before. i.e. the package java.awt.image will be Java.Awt.ImagePackage now, -- thus all classes inside the package have their full name changed too. classModules :: Map String String classModules = Map.fromList $ map f $ xToList classModules_ where f (clazzName, moduleName) = (clazzName, joinClassName (newPackageName, classModuleName)) where classModuleName = takeClassName moduleName packageName = maybe "" id $ takePackageName clazzName newPackageName = packageModules `xGet` packageName -- A mapping from packages to classes classesByPackage :: MultiMap String String classesByPackage = MultiMap.fromList $ map (\clazz -> (fst $ splitClassName clazz, clazz)) classes -- The list of hidden modules hiddenModules = map (++ "__") $ Map.elems classModules classInfo <- gatherClassInfo opts reflectDump classModules findJavaCore classesByPackage classInfo when (opts `get` optOnlyReflect) $ io $ do mapM_ println $ Map.elems classInfo exitSuccess generateModules opts classInfo classesByPackage classModules packageModules hiddenModules return () {- findJavaCore2 :: Map String JavaClass -> Java () findJavaCore2 classInfo = do let classes = -} findJavaCore :: MultiMap String String -> Map String JavaClass -> Java () findJavaCore classesByPackage classInfo = do let packageDependencies :: Map String (Set String) packageDependencies = Map.map findDependencies (MultiMap.toMap classesByPackage) where findDependencies classes = Set.fromList packageDependencies where dependencies = concatMap (classDependencies . (classInfo Map.!)) classes packageDependencies = map (fst . splitClassName) dependencies resolve :: Set String -> Set String resolve = Set.fromList . concatMap (Set.toList . (packageDependencies Map.!)) . Set.toList fullPackageDependencies = Map.map (exhaustively resolve) packageDependencies coreJava = intersections (Map.elems fullPackageDependencies) println (show $ Set.size coreJava) println (Set.toList coreJava) forM_ (Map.toList fullPackageDependencies) $ \(pkg, deps) -> do let deps' = (deps Set.\\ coreJava) print ((\_ -> let { __ = {-# LINE 185 "Java2Haskell.hss" #-} concat ["", (pkg), ", ", (show $ Set.size deps'), " (", (show $ Set.size deps), ") dependencies:\n "] {-# LINE 187 "Java2Haskell.hss" #-} } in __) undefined) println $ Set.toList deps' -- Gather information about classes via Reflection -- or (depending on the opts set) from a dump file. gatherClassInfo :: Options -> Maybe FilePath -> Map String String -> Java (Map String JavaClass) gatherClassInfo opts reflectDump classModules = do print ((\_ -> let { __ = {-# LINE 200 "Java2Haskell.hss" #-} concat ["Gathering refection information..."] {-# LINE 200 "Java2Haskell.hss" #-} } in __) undefined) let classNames = Map.keys classModules updName name clazz = (name, clazz { classModName = classModules Map.! name }) getClassInfo :: Java (Map String JavaClass) getClassInfo = Map.fromList `fmap` map (uncurry updName) `fmap` zip classNames `fmap` everywhere (mkT (\(TyVar v) -> TyVar ('_':v))) `fmap` reflectClasses classNames readClassInfo :: String -> Java (Map String JavaClass) readClassInfo reflectFile = io $ do file <- openFile reflectFile ReadMode contents <- hGetContents file let contents' = read contents result <- contents' `seq` return contents' hClose file return result classInfo <- maybe getClassInfo (\f -> print " (from dump)" >> readClassInfo f) reflectDump println " Done." unless (null (opts `get` optSaveReflectDump)) $ io $ do file <- openFile (opts `get` optSaveReflectDump) WriteMode print ((\_ -> let { __ = {-# LINE 225 "Java2Haskell.hss" #-} concat ["Saving dump of reflection info..."] {-# LINE 225 "Java2Haskell.hss" #-} } in __) undefined) hPutStr file (show classInfo) println " Done." hClose file return classInfo -- This creates a graphviz file which can be used to -- visualize inter-package dependencies visualizeDependencies :: Map String (Set String) -> IO () visualizeDependencies packageDependencies = do file <- openFile "dependencies.neato" WriteMode hPutStrLn file ((\_ -> let { __ = {-# LINE 241 "Java2Haskell.hss" #-} concat ["digraph G {\n overlap = false;"] {-# LINE 241 "Java2Haskell.hss" #-} } in __) undefined) forM_ (Map.toList packageDependencies) $ \(pkg, deps) -> do forM_ (Set.toList deps) $ \dep -> do let augment = map (\x -> if x == '.' then '_' else x) pkg' = augment pkg dep' = augment dep hPutStr file ((\_ -> let { __ = {-# LINE 248 "Java2Haskell.hss" #-} concat [" ", (pkg'), " -> ", (dep'), "\n"] {-# LINE 248 "Java2Haskell.hss" #-} } in __) undefined) hPutStrLn file ((\_ -> let { __ = {-# LINE 250 "Java2Haskell.hss" #-} concat ["}"] {-# LINE 250 "Java2Haskell.hss" #-} } in __) undefined) hClose file -- This actually creates the Haskell module files generateModules :: Options -> Map String JavaClass -> MultiMap String String -> Map String String -> Bimap CaseInsensitiveString CaseInsensitiveString -> [String] -> Java () generateModules opts classInfo classesByPackage classModules packageModules hiddenModules = do println ((\_ -> let { __ = {-# LINE 264 "Java2Haskell.hss" #-} concat ["Generating modules for ", (show $ Map.size classModules), " classes in ", (show $ xSize packageModules), " packages..."] {-# LINE 265 "Java2Haskell.hss" #-} } in __) undefined) -- A list of all modules let allModules = xKeysR packageModules ++ Map.elems classModules -- Create all the package files. -- These contain the Java classes as Haskell types. forM_ (xToList packageModules) $ \(packageName, packageMod) -> do let dirName = strJoin [pathSeparator] (strSplitAll "." packageMod) -- Create the directory for the package io $ do createDirectoryIfMissing True dirName let classes = map (classInfo Map.!) (MultiMap.lookup packageName classesByPackage) -- Create the Haskell module file for the Java package file <- openFile (dirName ++ ".hs") WriteMode hPutStrLn file $ printJavaPackageModule packageName packageMod classInfo classes hClose file -- Create all the class files. -- These contain the java methods as Haskell functions. forM_ (Map.toList classModules) $ \(clazzName, clazzMod) -> do let fileName = strJoin [pathSeparator] (strSplitAll "." clazzMod) clazz = classInfo Map.! clazzName io $ do -- Create the Haskell module file for the Java class. file <- openFile (fileName ++ ".hs") WriteMode hPutStrLn file $ printJavaClassModule clazz clazzMod classInfo hClose file -- Create the *hidden* Haskell module file for the Java class. file <- openFile (fileName ++ "__.hs") WriteMode hPutStrLn file $ printJavaClassModule' clazz clazzMod classInfo hClose file -- write Cabal file when (opts `get` optCabalProject) $ io $ do cabalFile <- openFile ((opts `get` optProjectName) ++ ".cabal") WriteMode hPutStrLn cabalFile ((\_ -> let { __ = {-# LINE 308 "Java2Haskell.hss" #-} concat ["name: ", (opts `get` optProjectName), "\nversion: ", (opts `get` optProjectVersion), "\ncabal-version: >= 1.8\nbuild-type: Simple\n\nLibrary {\n build-depends:\n base >= 4 && < 5,\n java-bridge\n exposed-modules:\n ", (strJoin ",\n " allModules), "\n other-modules:\n ", (strJoin ",\n " hiddenModules), " }\n"] {-# LINE 322 "Java2Haskell.hss" #-} } in __) undefined) hClose cabalFile