packages feed

jarfind-0.1.0.3: Main.hs

module Main (main) where

import Codec.Archive.Zip
import qualified Data.ByteString.Lazy as B
import Control.Monad
import Control.Applicative ((<$>))
import Data.List
import Data.Bits
import Data.Maybe
import Data.Char

import Data.Array.ST
import Data.Array.Unboxed

import Data.Binary
import Data.Binary.Get

import System.Console.GetOpt
import System.Environment

import Text.Regex.TDFA
import JarFind

-- Types related to cmdline arguments
 
newtype SearchSource = SearchSource (Class->Bool)
data SearchTarget = SearchClass
                  | SearchMember (Member->Bool)

data Args = Args { dataSource   :: [ClassFileSource]
                 , searchSource :: SearchSource
                 , searchTarget :: SearchTarget
                 }

data Result = FoundClass Location Class
            | FoundMember Location Class Member
              deriving Show


----------------------------------------------------------------
--                    ARGUMENTS PARSING                       --
----------------------------------------------------------------


data PlainArgs = PlainArgs { typeRegex :: Maybe String
                           , memberRegex :: Maybe String
                           , typeAccess :: Maybe String
                           , memberAccess :: Maybe String
                           , target :: Maybe String 
                           }

emptyArgs :: PlainArgs
emptyArgs = PlainArgs Nothing Nothing Nothing Nothing Nothing

data MemberKind = FieldKind | MethodKind deriving Eq

parseArgs :: PlainArgs -> [String] -> Args
parseArgs a paths = Args dataSource (SearchSource typeFilter) searchTarget
    where searchTarget = case (memberRegex a, memberAccess a, target a) of 
                            (Nothing, Nothing, Nothing) -> SearchClass
                            _                           -> SearchMember memberFilter

          searchMembers = isJust (memberRegex a) 

          typeFilter = accessAndRegex (access (typeAccess a)) (typeRegex a) clsAccess clsName
          
          memberFilter mem = (memKind `maybeEq` kind mem)
                           && accessAndRegex (access (memberAccess a)) (memberRegex a) 
                                             mAccess mName mem
              where kind (Field  _ _ _) = FieldKind
                    kind (Method _ _ _) = MethodKind

          memKind = case (target a) of 
                        Just ('f':_) -> Just FieldKind 
                        Just ('m':_) -> Just MethodKind 
                        _            -> Nothing

          dataSource = map toDataSource paths
          toDataSource p | ".jar"   `isSuffixOf` p = JarFile p
                         | ".class" `isSuffixOf` p = ClassFile p
                         | ":"      `isInfixOf`  p = ClassPath . map toDataSource $ (==':') `unjoin` p
                         | otherwise               = error $ "Unsupported datasource type: "++p

accessAndRegex acc rx getAcc getName x =  (acc `maybeEq` (getAcc x)) 
                                       && (nameMatchesRegex x)
    where nameMatchesRegex x = case rx of 
                                  Nothing -> True
                                  Just rx -> getName x =~ rx
          
maybeEq Nothing  _ = True
maybeEq (Just a) b = a == b
          
access a = case a of
              Just "public"    -> Just Public
              Just "private"   -> Just Private
              Just "protected" -> Just Protected
              Just "package"   -> Just Package
              _                -> Nothing

unjoin :: (a -> Bool) -> [a] -> [[a]]
unjoin p s = go [] [] s
    where go res cur []     = reverse (cur:res)
          go res cur (x:xs) | p x       = go (cur:res) [] xs
                            | otherwise = go res (x:cur) xs



----------------------------------------------------------------
--                            MAIN                            --
----------------------------------------------------------------
main :: IO ()
main = do
    (opts, paths, errs) <- getOpt Permute options `liftM` getArgs
    let compose = foldr (.) id
    case errs of
        [] -> run (parseArgs (compose opts emptyArgs) paths)
        _  -> mapM_ putStrLn errs >> showHelp


options :: [OptDescr (PlainArgs -> PlainArgs)]
options = [ Option ['c'] []         (OptArg (\s a -> a { typeRegex    = s }) "RX")   
                "Search for/in types whose name (with package) contains a match of this regex",
            Option ['m'] []         (OptArg (\s a -> a { memberRegex  = s }) "RX")  
                "Search for members whose name contains a match of this regex",
            Option []    ["ca"]     (OptArg (\s a -> a { typeAccess   = s })"ACCESS")  
                "Search for/in types having the specified access (public/private/protected/package)",
            Option []    ["ma"]     (OptArg (\s a -> a { memberAccess = s }) "ACCESS") 
                "Search for members having the specified access (public/private/protected/package)",
            Option ['t'] ["target"] (OptArg (\s a -> a { target       = s }) "TYPE")   
                "Search for members of the given type (field=f/method=m)"]

showHelp :: IO ()
showHelp = putStrLn $ usageInfo usage options
    where usage="jarf [OPTION]... FILE... - Search for classes/methods/interfaces in JAR file(s)"

run :: Args -> IO ()
run (Args dataS searchS searchT) = do
    classes <- parseFileSource (ClassPath dataS)
    mapM_ (putStrLn . present) . concatMap (search searchS searchT) $ classes

-- Results presentation

present :: Result -> String
present (FoundClass loc cls) = presentLoc loc ++ ": " ++ presentClass cls
present (FoundMember loc cls mem) = presentLoc loc ++ ": " ++ 
                                    presentClass cls ++ ": " ++ 
                                    show (mAccess mem) ++ " " ++ 
                                    (mName mem) ++ " :: " ++ 
                                    (mSig mem)

presentLoc :: Location -> String
presentLoc (InFile path) = path
presentLoc (InJar jp ip) = jp ++ "!" ++ ip

presentClass :: Class -> String
presentClass (Class name acc False _) = show acc ++ " class "     ++ name
presentClass (Class name acc True _)  = show acc ++ " interface " ++ name

-- Search

search :: SearchSource -> SearchTarget -> (Location,Class) -> [Result]
search (SearchSource classP) SearchClass (loc,c) = 
    if (classP c) then [FoundClass loc c] else []
search (SearchSource classP) (SearchMember memP) (loc,c) =
    if (classP c) then [FoundMember loc c mem | mem <- clsMembers c, memP mem]
                  else []