module Database.Design.Ampersand.FSpec.GenerateUML (generateUML) where
import Database.Design.Ampersand.Basics
import Database.Design.Ampersand.Core.AbstractSyntaxTree (explMarkup,aMarkup2String,Rule,Purpose(..))
import Database.Design.Ampersand.Graphic.ClassDiagram
import Database.Design.Ampersand.Graphic.Fspec2ClassDiagrams
import Database.Design.Ampersand.FSpec
import Database.Design.Ampersand.Core.ParseTree(PandocFormat(ReST))
import Data.List
import qualified Data.Map as Map
import Control.Monad.State.Lazy (State, gets, evalState, modify)
fatal :: Int -> String -> a
fatal = fatalMsg "FSpec.GenerateUML"
-- TODO: escape
-- TODO: names of model, package, assoc (empty?), etc.
generateUML :: FSpec -> String
generateUML fSpec = showUML (fSpec2UML fSpec)
showUML :: UML -> String
showUML uml = unlines $ evalState uml $ UMLState 0 Map.empty [] []
fSpec2UML :: FSpec -> UML
fSpec2UML fSpec =
do { packageId0 <- mkUnlabeledId "TopPackage"
; packageId1 <- mkUnlabeledId "PackageClasses"
; packageId2 <- mkUnlabeledId "PackageReqs"
; diagramId <- mkUnlabeledId "Diagram"
; _ <- mapM (mkLabeledId "Datatype") datatypeNames
; _ <- mapM (mkLabeledId "Class") classNames
; datatypesUML <- mapM genUMLDatatype datatypeNames
; classesUML <- mapM genUMLClass (classes classDiag)
; assocsUML <- mapM genUMLAssociation (assocs classDiag)
; requirementsUML <- mapM genUMLRequirement (requirements fSpec)
; diagramElements <- genDiagramElements
; customProfileElements <- genCustomProfileElements
; customReqElements <- genCustomReqElements fSpec packageId2
; return $ [ ""
, ""
, ""
-- WHY is the exporter not something like `Ampersand` (in the string below)?
-- BECAUSE then for some reason the importer doesn't show the properties of the requirements.
, " "
, " "
, " " ] ++
[ " " ] ++
concat datatypesUML ++
concat classesUML ++
concat assocsUML ++
[ " " ] ++
[ " " ] ++
concat requirementsUML ++
[ " " ] ++
[ " " ] ++
customProfileElements ++
[ " "
, " "
, " "] ++
[ " "]++
[ " "]++
[ " "]++
[ " "]++
[ " "]++
[ " "]++
[ " "]++
[ " "]++
customReqElements ++
[ " "
, " "
, " "
, " "
, " "
, " " ] ++
diagramElements ++
[ " "
, " "
, " "
, " "
, "" ]
}
where classDiag = cdAnalysis fSpec
contextName = cdName classDiag
allConcs = ooCpts classDiag
classNames = map name (classes classDiag)
datatypeNames = map name allConcs >- classNames
genUMLRequirement :: Req -> UML
genUMLRequirement req =
do { reqLId <- mkUnlabeledId "Req"
; addReqToState (reqLId, req)
; return $ [ " " ]
}
genUMLDatatype :: String -> UML
genUMLDatatype nm =
do { datatypeId <- refLabeledId nm
; addToDiagram datatypeId
; return [ " " ]
}
genUMLClass :: Class -> UML
genUMLClass cl =
do { classId <- refLabeledId (clName cl)
; addToDiagram classId
; attributesUML <- mapM genUMAttribute (clAtts cl)
; return $ [ " "] ++
concat attributesUML ++
[ " "]
}
genUMAttribute :: CdAttribute -> UML
genUMAttribute (OOAttr nm attrType optional) =
do { attrId <- mkUnlabeledId "Attr"
; lIntId <- mkUnlabeledId "Int"
; uIntId <- mkUnlabeledId "Int"
; classId <- refLabeledId attrType
; return [ " "
, " "
, " "
, " "
, " "]
}
genUMLAssociation :: Association -> UML
genUMLAssociation ass =
do { assocId <- mkUnlabeledId "Assoc"
; lMemberAndOwnedEnd <- genMemberAndOwnedEnd (asslhm ass) assocId (assSrc ass)
; rMemberAndOwnedEnd <- genMemberAndOwnedEnd (assrhm ass) assocId (assTgt ass)
; return $
[ " "
] ++
lMemberAndOwnedEnd ++
rMemberAndOwnedEnd ++
[ " "
]
}
where genMemberAndOwnedEnd (Mult minVal maxVal) assocId type' =
do { endId <- mkUnlabeledId "MemberEnd"
; typeId <- refLabeledId type'
; lIntId <- mkUnlabeledId "Int"
; uIntId <- mkUnlabeledId "Int"
; return
[ " "
, " "
, " "
, " "
, case maxVal of
MaxOne -> " "
MaxMany -> " "
, " "
]
}
genDiagramElements :: UML
genDiagramElements =
do { elementIds <- gets diagramEltIds
; return [ " " | elementId <- elementIds ]
}
genCustomProfileElements :: UML
genCustomProfileElements =
do { reqVals <- gets reqValues
; return [reqUML req | req <- reverse reqVals]
}
where
reqUML :: ReqValue2 -> String
reqUML (xmiId, req) = intercalate "\n"
( [" "]++
[tagUML xmiId count puprtxt reftxt | (count, (puprtxt, reftxt)) <- zip [0::Int ..] [(aMarkup2String ReST (explMarkup p), intercalate ";" (explRefIds p)) | p <- reqPurposes req]]
)
tagUML xmiId nr value reftxt = intercalate "\n"
[ " "
, " "
]
where keyMeaning = "Meaning"++show nr
keyRef = "Reference"++show nr
genCustomReqElements :: FSpec -> String -> UML
genCustomReqElements fSpec parentPackageId =
do { reqVals <- gets reqValues
; return [reqUML req | req <- reverse reqVals]
}
where
reqUML :: ReqValue2 -> String
reqUML (xmiId, req) = intercalate "\n"
([ " "
, " "
, " "
, " "]++
[ " " | (nr ,p) <- zip ("" : map show [1::Int ..]) ([aMarkup2String ReST (explMarkup p) | p <- reqPurposes req]) ]++
[ " "
, " "
])
-- Requirements
data Req = Req { reqId :: String
-- , reqRef :: String
, reqOrig :: Either Rule Declaration
, reqPurposes :: [Purpose]
}
instance Meaning Req where
meaning l r = case reqOrig r of
Right rul -> meaning l rul
Left dcl -> meaning l dcl
requirements :: FSpec -> [Req]
requirements fSpec
= [decl2req d | d <- vrels fSpec]
++[rule2req r | r <- vrules fSpec]
where
decl2req d = Req { reqId = name d
, reqOrig = Right d
, reqPurposes = purposesDefinedIn fSpec (fsLang fSpec) d
}
rule2req r = Req { reqId = name r
, reqOrig = Left r
, reqPurposes = purposesDefinedIn fSpec (fsLang fSpec) r
}
-- State and Monad
data UMLState = UMLState { idCounter :: Int
, labelIdMap :: Map.Map String String
, diagramEltIds :: [String]
, reqValues :: [ReqValue2]
}
type StateUML a = State UMLState a
type UML = StateUML [String]
type ReqValue2 = ( String -- the xmi-id
, Req
)
addToDiagram :: String -> StateUML ()
addToDiagram elementId =
modify $ \state' -> state' { diagramEltIds = elementId : diagramEltIds state'}
addReqToState :: ReqValue2 -> StateUML ()
addReqToState reqVal =
modify $ \state' -> state' { reqValues = reqVal : reqValues state'}
mkUnlabeledId :: String -> StateUML String
mkUnlabeledId tag =
do { idC <- gets idCounter
; modify $ \state' -> state' { idCounter = idCounter state' + 1}
; let unlabeledId = tag++"ID_"++show idC
; return unlabeledId
}
refLabeledId :: String -> StateUML String
refLabeledId label =
do { lidMap <- gets labelIdMap
; case Map.lookup label lidMap of
Just lid -> return lid
Nothing -> fatal 147 $ "Requesting non-existent label "++label
}
mkLabeledId :: String -> String -> StateUML ()
mkLabeledId tag label =
do { let classId = tag++"ID_"++label
; modify $ \state' -> state' { labelIdMap = Map.insert label classId (labelIdMap state') }
}