EA Mapping tool the next steps

Since my last post EA mapping EA Mapping Tool => Who wants it? there has been quite some response.

There are at least three parties that are seriously committed in joining, and a number of parties that are interested but for any number of reasons aren’t able to commit to anything just yet.

So the next logical step in the process is start building a requirements list. I’ve started with list of basis requirements listed below. Every sponsor now will now be given the opportunity to review those requirements and add some of their own.

If your organisation is still in doubt maybe this list of requirements will help you to get them convinced of the usefulness of this add-in. There is still time to join in as a sponsor.

The requirements

I’ve divided the requirements into three categories: base, GUI and exchange. Below there’s a detailed list of these requirements.

Base Requirements

RQ001 The tool should be able to map an attribute to one or more attributes or associations
RQ002 The tool should be able to map one association to one or more associations or Attributes
RQ003 For each mapping between (possibly) multiple attributes and association the tool should be able to document a mapping logic
RQ004 Mapping logic should be re-usable between mappings
RQ005 The tool should be able to use either RefGUID tagged values or “link to element features” or a combination thereof to make the link between attributes.
RQ006 A mapping should be able to depend on the “path” of the attribute. Contract.Seller.Name may be mapped to another attribute then Contract.Buyer.Name, although both are Party.Name.
RQ007 All mapping informatio is stored inside an Enterprise Architect model

GUI Requirements

RQ010 The tool should represent one model at one side and the other model at the other side,showing the links in a visual obvious way
RQ011 If a mapping logic is applied this should be visually apparent.
RQ012 The mapping logic should be easily accessible from the GUI
RQ013 It should be possible create or change mappings in a very intuitive way, such as drag and drop.
RQ014 The tool should be able to filter only the mapped items from both data model in order to not clutter the user interface with unused items.
RQ015 The GUI should be entirely in Enteprise Architect and the main mapping window should use the “diagram” space of EA.

Mockup

Visually the GUI should look somewhat like this

mapping tool

Exchange requirements

RQ020 The tool should be able to import mappings from a CVS file
RQ021 The tool should be able to export mappings to a CSV file

Download

Of course all of these requirements where modeled in EA and you can download the initial requirement model below.

[ecwid_product store_id=”6824265″ product_id=”71957048″]

Join us

If you feel these requirements are a good fit to your requirements for a mapping tools send me a message at geert@bellekens.com and you may still join this community driven initiative.

EA Mapping tool => Who wants it?

I’m thinking about developing a mapping tool add-in for EA.
The idea is that you can manage a mapping between two types of (data) models. For example between your logical data model and your message model, or between your logical data model and your database model etc…

I already have a number of add-ins and scripts that somehow create traceability between models on element, attribute and association level, but I don’t have a tool yet to visualize and manage those traces.

To give you an idea the GUI of the thing could look similar to this:

mapping tool
Functions could include
– ability to add notes to each mapping
– ability to export the mapping to excel
– drag-n-drop to create mappings
– …
Now it seems to me like there could be many parties interested in such a tool.
I would like to develop this as a free and open source add-in (as I do for all my add-ins) but I’m still looking for sponsors.
My initial rough estimate for the development of such a tool is 15 man days.

I already have two parties that might be interested, but the more sponsors I can find the lower the price would get for each individual party.

So if you are interested in such a tool please let me know: geert@bellekens.com and we might be able to work something out.

The Bellekens EA Toolpack combines EA-Matic, EA Navigator and ECDM Message Composer

The three free and open source add-ins for Enterprise Architect : EA Navigator, EA-Matic and ECDM Message Composer have been combined in a convenient package called Bellekens EA Toolpack.

Bellekens EA Toolpack

Downloading any of the add-ins from the site now gets you an installer for the Bellekens EA Toolpack. With this installer you can install all three add-ins, or only the ones you require.

Bellekens EA Toolpack installer

The main reason for this change is the shared EA Addin Framework. Because all three add-ins use the same framework, there was a risk at version conflicts when two add-ins were installed on the same machine because each add-in installed its own version of the framework. With the new installer the framework gets installed only once thus avoiding any possible version conflicts which are known as the DLL Hell.

An added benefit of the Bellekens EA Toolpack is the fact that is now installs the applications for all users on the machine, making it easier to distribute in a corporate organisation.

Because the Bellekens EA Toolpack is a different application from Windows point of view you’ll have to uninstall any versions of EA Navigator, EA-Matic or ECDM Message Composer you might have installed previously. Uninstalling is the easiest from the add/remove programs feature in the control panel.

See Also

Download

[ecwid_product store_id=”6824265″ product_id=”48049803″]

Navigate from and to Composite Diagrams with EA Navigator

With EA Navigator version 2.4.12.0 you can now navigate from and to composite diagrams of an element.

Recently I’ve been working a lot with BPMN models. BPMN models make extensive use of the composite diagram mechanism in EA to drill-down on the processes and sub-processes.

In a classical UML model we usually place the composite diagram under the element, in which case finding the composite diagram, or finding the element(s) that have a diagram as composite diagram, is trivial. They are at the same location in the project browser.BPMN_Library_Model

In most BPMN models however there’s separation between model and library where the library contains the elements, and the model the diagrams. In such a setup it suddenly becomes much harder to find the composite diagram (find in all diagram, then double click the element) and almost impossible to find the element(s) that have a specific diagram as composite diagram.

 

In other words drilling down is relatively easy, but drilling up becomes nearly impossible.

Navigate_Composite_diagramThis problem is now solved by the EA Navigator.  From the Business Process I can now easily navigate to the composite diagram, but more importantly, I can navigate back up to any elements that have this diagram set as their composite diagram.

 

More Information

More information about the EA Navigator add-in for Enterprise Architect can be found here:

Configure your own default line styles in Enterprise Architect with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

With this EA-Matic script you can configure your own default line-styles depending on the type of connector.

In Enterprise Architect you can choose from no less then 9 different line style for the connectors.

Line Styles

Unfortunately you can only choose from the first three to be the default line style for new connectors.

line style default options

Additionally you can also specify the default for Generalization to be Tree Style.

For me that is not enough. I have my own habits when making UML diagrams. For most connector types I use Orthogonal – Square, but not for dependencies, use case relations and note links. For those I like the Direct style. The last exception are the control flow, state flow and object flows, for which I use Orthogonal – Rounded.

LineStylesDiagram

This EA-Matic script gives me the control I want. Not only does it set the default line style for new connectors, with the context menu I can reset all connectors on a diagram to my own default styles.

Free download

[ecwid_product store_id=”6824265″ product_id=”48249914″]

The code

In order to define your own preferred style you can edit the indicated part in the script

'*********EDIT BETWEEN HERE*************
' set here the menu name
menuDefaultLines = "&Set default linestyles"

' set here the default style to be used
defaultStyle = lsOrthogonalSquareTree

' set there the style to be used for each type of connector
function determineStyle(connector)
    dim connectorType
    connectorType = connector.Type
    select case connectorType
        case "ControlFlow", "StateFlow","ObjectFlow","InformationFlow"
            determineStyle = lsOrthogonalRoundedTree
        case "Generalization", "Realization", "Realisation"
            determineStyle = lsTreeVerticalTree
        case "UseCase", "Dependency","NoteLink"
            determineStyle = lsDirectMode
        case else
            determineStyle = defaultStyle
    end select
end function
'************AND HERE****************

This script uses 3 EA events. The first one is the EA_OnPostNewConnector which gets triggered by EA the moment you create  a new connector.

'the event called by EA
function EA_OnPostNewConnector(Info)
    'get the connector id from the Info
    dim connectorID
    connectorID = Info.Get("ConnectorID")
    dim connector 
    set connector = Repository.GetConnectorByID(connectorID)
    'get the current diagram
    dim diagram
    set diagram = Repository.GetCurrentDiagram()
    if not diagram is nothing then
        'first save the diagram
        Repository.SaveDiagram diagram.DiagramID
        'get the diagramlink for the connector
        dim diagramLink
        set diagramLink = getdiagramLinkForConnector(connector, diagram)
        if not diagramLink is nothing then
            'set the connectorstyle
            setConnectorStyle diagramLink, determineStyle(connector)
            'save the diagramlink
            diagramLink.Update
            'reload the diagram to show the link style
            Repository.ReloadDiagram diagram.DiagramID
        end if
    end if
end function

The next even we use is the EA_GetMenuItems so we can show a menu option in the context menu of a diagram.

'Tell EA what the menu options should be
function EA_GetMenuItems(MenuLocation, MenuName)
    if MenuName = "" and MenuLocation = "Diagram" then
        'Menu Header
        EA_GetMenuItems = menuDefaultLines
    end if 
end function

The last EA event is the EA_MenuClick, triggered when the user actually clicks on the menu item.

'react to user clicking a menu option
function EA_MenuClick(MenuLocation, MenuName, ItemName)
    if ItemName = menuDefaultLines then
        dim diagram 
        dim diagramLink
        dim connector
        dim dirty
        dirty = false
        set diagram = Repository.GetCurrentDiagram
        'save the diagram first
        Repository.SaveDiagram diagram.DiagramID
        'then loop all diagramLinks
        if not diagram is nothing then
            for each diagramLink in diagram.DiagramLinks
                set connector = Repository.GetConnectorByID(diagramLink.ConnectorID)
                if not connector is nothing then
                    'set the connectorstyle
                    setConnectorStyle diagramLink, determineStyle(connector)
                    'save the diagramlink
                    diagramLink.Update
                    dirty = true
                end if
            next
            'reload the diagram if we changed something
            if dirty then
                'reload the diagram to show the link style
                Repository.ReloadDiagram diagram.DiagramID
            end if
        end if
    end if
end function

Then we have a small helper function to get the diagramLink object from the diagram

'gets the diagram link object
function getdiagramLinkForConnector(connector, diagram)
    dim diagramLink 
    set getdiagramLinkForConnector = nothing
    for each diagramLink in diagram.DiagramLinks
        if diagramLink.ConnectorID = connector.ConnectorID then
            set getdiagramLinkForConnector = diagramLink
            exit for
        end if
    next
end function

And last but not least the method that will actually set the connector style.

'actually sets the connector style
function setConnectorStyle(diagramLink, connectorStyle)
    'split the style into its parts
    dim styleparts
    dim styleString
    styleString = diagramLink.Style
    styleparts = Split(styleString,";")
    dim stylePart
    dim mode
    dim modeIndex
    modeIndex = -1
    dim tree
    dim treeIndex
    treeIndex = -1
    mode = ""
    tree = ""
    dim i
    'find if Mode and Tree are already defined
    for i = 0 to Ubound(styleparts) -1 
        stylePart = styleparts(i)
        if Instr(stylepart,"Mode=") > 0 then
            modeIndex = i
        elseif Instr(stylepart,"TREE=") > 0 then
            treeIndex = i
        end if
    next
    'these connectorstyles use mode=3 and the tree
    if  connectorStyle = lsTreeVerticalTree or _
        connectorStyle = lsTreeHorizontalTree or _
        connectorStyle = lsLateralHorizontalTree or _
        connectorStyle = lsLateralVerticalTree or _
        connectorStyle = lsOrthogonalSquareTree or _
        connectorStyle = lsOrthogonalRoundedTree then
        mode = "3"
        tree = connectorStyle
    else
        mode = connectorStyle
    end if
    'set the mode value
    if modeIndex >= 0 then
        styleparts(modeIndex) = "Mode=" & mode
        diagramLink.Style = join(styleparts,";")
    else
        diagramLink.Style = "Mode=" & mode& ";"& diagramLink.Style
    end if
    'set the tree value
    if treeIndex >= 0 then
        if len(tree) > 0 then
            styleparts(treeIndex) = "TREE=" & tree
            diagramLink.Style = join(styleparts,";")
        else
            'remove tree part
            diagramLink.Style = replace(diagramLink.Style,styleparts(treeIndex)&";" , "")
        end if
    else
        diagramLink.Style = diagramLink.Style & "TREE=" & tree & ";"
    end if
end function

Automatically synchronize overrides in Enterprise Architect with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example EA-Matic script keeps the signature of the overriding operations in sync with the signature of the overridden operation.

This is especially useful when you are modelling an interface and a number of classes that realize this interface. EA has this neat feature Overrides and Implementations accessed by Ctrl-Shift-O to copy operations from an interface or superclass to the realizing/specializing class.

EA-Matic Overides and Implmentations

This works great when you have created a new operation, or added a new realizing class, but once you change the signature of your interface operation you are on your own. You need to track down all operations that once overrode your interface operation and change their signature accordingly. Not a pretty task.

EA-Matic Overides

This script ensures that the signature of the overrides is kept in sync with that of the overridden operation. Whenever you change the signature it alerts the user to the fact that there are overrides and asks if they need to be synchronized.

EA-Matic Synchronize

Usage of Enterprise Architect Add-in Framework

This script also illustrates how you can use the Enterprise Architect Add-in Framework within your script.

This extensive open source framework is the basis for the add-ins EA-Matic and EA Navigator, and offers a much more functional interface to the model then the standard EA API.

This code inializes the model object with the current Repository

'gets a new instance of the EAAddinFramework and initializes it with the EA.Repository
function getEAAddingFrameworkModel()
    'Initialize the EAAddinFramework model
    dim model
    set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
    model.initialize(Repository)
    set getEAAddingFrameworkModel = model
end function

It is then further used to get the operation based on the GUID passed as parameter.

set operation = model.getOperationByGUID(GUID)

The model object also can be used to search elements based on an sql query

dim descendants
dim getdescendantsQuery
getdescendantsQuery = "select c.Start_Object_ID as Object_ID from (t_object o " _
				& "inner join t_connector c on c.End_Object_ID = o.Object_ID) " _
				& "where "_
				& "(c.[Connector_Type] like 'Generali_ation' "_
				& "or c.[Connector_Type] like 'Reali_ation' )"_
				& "and o.Object_ID = " & element.id
set descendants = model.toArrayList(model.getElementWrappersByQuery(getdescendantsQuery))

Note that we need to use model.toArrayList to convert the C# List<> that cannot be used by VBScript to an ArrayList that can be used.

The code

Download the complete script: EA-Matic Sychronize overrides. This script requires EA-Matic version 1.0.12.2 or higher to function.

There are two events that we use. EA_OnNotifyContextChanged to react to remember the overrides of the operation before it is changed, and EA_OnContextItemModified to update the overrides one the operation has been changed.

'Event Called when a new element is selected in the context. We use this operation to keep the id of the selected operation and a list of its overrides
'Because now is the only moment we are able to find it's overrides. Once changed we cannot find the overrides anymore because then they already
'have a different signature
function EA_OnContextItemChanged(GUID, ot)
	'we only want to do something when the selected element is an operation
	if ot = otMethod then
		'get the model
		dim model
		set model = getEAAddingFrameworkModel()
		'get the operation
		dim operation
		set operation = model.getOperationByGUID(GUID)
		'remember the operationID
		operationID = operation.id
		'remember the overrides
		set overrides = getOverrides(operation, model)
		Repository.WriteOutput "EA-Matic", overrides.Count & " overrides found for: " & operation.name,0
	end if
end function

'Event called when an element is changed. Unfortunately EA doesn't call it for an operation, only for the owner so we have to work with that.
function EA_OnNotifyContextItemModified(GUID, ot)
	'we only want to do something when the selected element is an operation
	if ot = otElement then
		'get the operation
		'Here we use the EA API object directly as most set methods are not implemented in EA Addin Framework
		dim wrappedOperation
		set wrappedOperation = Repository.GetMethodByID(operationID)
		dim modifiedElement
		set modifiedElement = Repository.GetElementByGuid(GUID)
		if not wrappedOperation is Nothing and not modifiedElement is Nothing then
			'check to be sure we have the same operation
			if modifiedElement.ElementID = wrappedOperation.ParentID AND overrides.Count > 0 then
				dim synchronizeYes
				synchronizeYes = MsgBox("Found " & overrides.Count & " override(s) for operation "& modifiedElement.Name & "." & wrappedOperation.Name & vbNewLine & "Synchronize?" _
										,vbYesNo or vbQuestion or vbDefaultButton1, "Synchronize overrides?")
				if synchronizeYes = vbYes then
					synchronizeOverrides wrappedOperation
					'log to output

					Repository.WriteOutput "EA-Matic", "Operation: " & wrappedOperation.name &" synchronized" ,0
				end if
				'reset operationID to avoid doing it all again
				operationID = 0
			end if
		end if
	end if
end function

In OnContextItemChanged we get the overrides by first selecting all operations with the same signature from the model with a query. Then we get all descendants of the owner of the operation (recursively) and filter the operation to only those owned by a descendant.

'gets the overrides of the given operation by first getting all operations with the same signature and then checking if they are owned by a descendant
function getOverrides(operation, model)
	'first get all operations with the exact same signature
	dim overrideQuery
	overrideQuery = "select distinct op2.OperationID from (((t_operation op " & _
					"inner join t_operation op2 on op2.[Name] = op.name) "& _
					"left join t_operationparams opp on op.OperationID = opp.OperationID) "& _
					"left join t_operationparams opp2 on opp2.OperationID = op2.OperationID) "& _
					"where op.OperationID = "& operation.id &" "& _
					"and op2.ea_guid <> op.ea_guid "& _
					"and (op2.TYPE = op.Type OR (op2.TYPE is null AND op.Type is null)) "& _
					"and (op2.Classifier = op.Classifier OR (op2.Classifier is null AND op.Classifier is null)) "& _
					"and (opp.Name = opp2.Name OR (opp.Name is null AND opp2.Name is null)) "& _
					"and (opp.TYPE = opp2.TYPE OR (opp.TYPE is null AND opp2.Type is null)) "& _
					"and (opp.DEFAULT = opp2.DEFAULT OR (opp.DEFAULT is null AND opp2.DEFAULT is null)) "& _
					"and (opp.Kind = opp2.Kind OR (opp.Kind is null AND opp2.Kind is null)) "& _
					"and (opp.Classifier = opp2.Classifier OR (opp.Classifier is null AND opp2.Classifier is null)) "
	dim candidateOverrides
	set candidateOverrides = model.ToArrayList(model.getOperationsByQuery(overrideQuery))
	'then get the descendants of the owner
	dim descendants
	dim descendant
	'first find all elements that either inherit from the owner or realize it
	dim owner
	set owner = model.toObject(operation.owner)
	set descendants = getDescendants(owner, model)
	'then filter the candidates to only those of the descendants
	'loop operations backwards
	dim i
	for i = candidateOverrides.Count -1 to 0 step -1
		dim found
		found = false
		for each descendant in descendants
			if descendant.id = model.toObject(candidateOverrides(i).owner).id then
				'owner is a descendant, operation can stay
				found = true
				exit for
			end if
		next
		'remove operation from non descendants
		if not found then
			candidateOverrides.RemoveAt(i)
		end if
	next
	set getOverrides = candidateOverrides
end function

'gets all descendant of an element. That is all subclasses and classes that Realize the element.
'Works recursively to get them all.
function getDescendants(element, model)
	dim descendants
	dim getdescendantsQuery
	getdescendantsQuery = "select c.Start_Object_ID as Object_ID from (t_object o " _
					& "inner join t_connector c on c.End_Object_ID = o.Object_ID) " _
					& "where "_
					& "(c.[Connector_Type] like 'Generali_ation' "_
					& "or c.[Connector_Type] like 'Reali_ation' )"_
					& "and o.Object_ID = " & element.id
	set descendants = model.toArrayList(model.getElementWrappersByQuery(getdescendantsQuery))
	'get the descendants descendants as well
	dim descendant
	dim descendantsChildren
	for each descendant in descendants
		if IsEmpty(descendantsChildren) then
			set descendantsChildren = getDescendants(descendant, model)
		else
			descendantsChildren.AddRange(getDescendants(descendant, model))
		end if
	next
	'add the descendantsChildren to the descendants
	if not IsEmpty(descendantsChildren) then
		if  descendantsChildren.Count > 0 then
			descendants.AddRange(descendantsChildren)
		end if
	end if
	set getDescendants = descendants
end function

Then when the signature of the operation has been changed we synchronize its signature with that of the with the overrides and we tell EA that the owner of the override has been changed.

'Synchronizes the operation with it's overrides
function synchronizeOverrides(wrappedOperation)
	dim override
	for each override in overrides
		dim wrappedOverride
		set wrappedOverride = override.WrappedOperation
		'synchronize the operation with the override
		synchronizeOperation wrappedOperation, wrappedOverride
		'tell EA something might have changed
		Repository.AdviseElementChange wrappedOverride.ParentID
	next
end function

'Synchronizes the operation with the given override
function synchronizeOperation(wrappedOperation, wrappedOverride)
	dim update
	update = false
	'check name
	if wrappedOverride.Name <> wrappedOperation.Name then
		wrappedOverride.Name = wrappedOperation.Name
		update = true
	end if
	'check return type
	if wrappedOverride.ReturnType <> wrappedOperation.ReturnType then
		wrappedOverride.ReturnType = wrappedOperation.ReturnType
		update = true
	end if
	'check return classifier
	if wrappedOverride.ReturnType <> wrappedOperation.ReturnType then
		wrappedOverride.ReturnType = wrappedOperation.ReturnType
		update = true
	end if
	if update then
		wrappedOverride.Update
	end if
	'check parameters
	synchronizeParameters wrappedOperation, wrappedOverride
end function

'Synchronizes the parameters of the given operatin with that of the overrride
function synchronizeParameters(wrappedOperation, wrappedOverride)
	'first make sure they both have the same number of parameters
	if wrappedOverride.Parameters.Count < wrappedOperation.Parameters.Count then
		'add parameters as required
		dim i
		for i = 0 to wrappedOperation.Parameters.Count - wrappedOverride.Parameters.Count -1
			dim newParameter
			set newParameter = wrappedOverride.Parameters.AddNew("parameter" & i,"")
			newParameter.Update
		next
		wrappedOverride.Parameters.Refresh
	elseif wrappedOverride.Parameters.Count > wrappedOperation.Parameters.Count then
		'remove parameters as required
		for i = wrappedOverride.Parameters.Count -1 to wrappedOperation.Parameters.Count step -1
			wrappedOverride.Parameters.DeleteAt i,false
		next
		wrappedOverride.Parameters.Refresh
	end if
	'make parameters equal
	dim wrappedParameter
	dim overriddenParameter
	dim j
	for j = 0 to wrappedOperation.Parameters.Count -1
		dim parameterUpdated
		parameterUpdated = false
		set wrappedParameter = wrappedOperation.Parameters.GetAt(j)
		set overriddenParameter = wrappedOverride.Parameters.GetAt(j)
		'name
		if overriddenParameter.Name <> wrappedParameter.Name then
			overriddenParameter.Name = wrappedParameter.Name
			parameterUpdated = true
		end if
		'type
		if overriddenParameter.Type <> wrappedParameter.Type then
			overriddenParameter.Type = wrappedParameter.Type
			parameterUpdated = true
		end if
		'default
		if overriddenParameter.Default <> wrappedParameter.Default then
			overriddenParameter.Default = wrappedParameter.Default
			parameterUpdated = true
		end if
		'kind
		if overriddenParameter.Kind <> wrappedParameter.Kind then
			overriddenParameter.Kind = wrappedParameter.Kind
			parameterUpdated = true
		end if
		'classifier
		if overriddenParameter.ClassifierID <> wrappedParameter.ClassifierID then
			overriddenParameter.ClassifierID = wrappedParameter.ClassifierID
			parameterUpdated = true
		end if
		'update the parameter if it was changed
		if parameterUpdated then
			overriddenParameter.Update
		end if
	next
end function

Synchronize object and classifier name in Enterprise Architect with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example EA-Matic script keeps the names of objects synchronized with the name of their classifier.

When modelling with objects the name of the object is often left empty because it doesn’t have any significance at that point. One of the downsides of this practice is that in things like the traceability view, or in the links view the object shows up without a name, which of course doesn’t help much.

EA-Matic synchronize classifier

One of the solutions for this problem is to give the object the same name as its classifier. But that is a manual tedious process, and in case the classifier changes name you have the change the name of all the instances.

EA-Matic synchronize classifier_after

The script ensures that the name of the instances is always the same as the name of the classifier.

Usage of Enterprise Architect Add-in Framework

This script also illustrates how you can use the Enterprise Architect Add-in Framework within your script.

This extensive open source framework is the basis for the add-ins EA-Matic and EA Navigator, and offers a much more functional interface to the model then the standard EA API.

This code inializes the model object with the current Repository

'gets a new instance of the EAAddinFramework and initializes it with the EA.Repository
function getEAAddingFrameworkModel()
    'Initialize the EAAddinFramework model
    dim model
    set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
    model.initialize(Repository)
    set getEAAddingFrameworkModel = model
end function

It is then further used to get the element based on the GUID passed as parameter

set element = model.getElementWrapperByGUID(GUID)

The model element can be used to search elements based on an sql query

 query = "select o.Object_ID from t_object o where o.classifier =" & element.id
 dim objects
 set objects = model.toArrayList(model.getElementWrappersByQuery(query))

Note that we need to use model.toArrayList to convert the C# List<> that cannot be used by VBScript to an ArrayList that can be used.

The code

Download the complete script: EA-Matic Sychronize object names

There are two events that we use. EA_OnNotifyContextItemModified to react to a name change of either the object or the classifier and EA_OnPostNewElement to change the name of a new object immediately after it has been created.

function EA_OnNotifyContextItemModified(GUID, ot)
    dim model
    set model = getEAAddingFrameworkModel()
    'only do something when the changed object is an element
    if ot = otElement then
        dim element
        set element = model.getElementWrapperByGUID(GUID)
        synchronizeObjectNames element, model
    end if
end function

function EA_OnPostNewElement(Info)
    'Get the model
    dim model
    set model = getEAAddingFrameworkModel()
    'get the elementID from Info
    dim elementID
    elementID = Info.Get("ElementID")
    'get the element being deleted
    dim element
    set element = model.getElementWrapperByID(elementID)
    synchronizeObjectNames element, model
end function

These two events then use the function synchronizeObjectNames to make sure the name of the object is the same as the name of the classifier

function synchronizeObjectNames(element, model)
    'first check if this is an object
    if element.WrappedElement.Type = "Object" AND element.WrappedElement.ClassifierID > 0 then
        dim classifier
        set classifier = model.getElementWrapperByID(element.WrappedElement.ClassifierID)
        if not classifier is nothing AND classifier.name <> element.name then
            element.name = classifier.name
            element.save
        end if
    else
        'get all objects having this element as their classifier
        dim query
        query = "select o.Object_ID from t_object o where o.classifier =" & element.id
        dim objects
        set objects = model.toArrayList(model.getElementWrappersByQuery(query))
        'loop objects
        dim obj
        for each obj in objects
            'rename the object if the name is different from the classifiers name
            if obj.name <> element.name then
                obj.name = element.name
                obj.save
            end if
        next
    end if
end function

Self-maintaining diagrams in Enterprise Architect with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example shows how to make diagrams that maintain themselves to make sure they are always up-to-date.

The idea is that we want a diagram that shows all the relations of the owning object. The diagram should always be up-to-date without manual intervention.

EA-Matic auto_diagram

To indicate that this diagram is an automatic diagram we nest it under the class and prefix it with AUTO_ and on the diagram we place the owning object and its related elements

EA-Matic self-maintaining diagram

Usage of Enterprise Architect Add-in Framework

This script also illustrates how you can use the Enterprise Architect Add-in Framework within your script.

This extensive open source framework is the basis for the add-ins EA-Matic and EA Navigator, and offers a much more functional interface to the model then the standard EA API.

This code inializes the model object with the current Repository

'gets a new instance of the EAAddinFramework and initializes it with the EA.Repository
function getEAAddingFrameworkModel()
    'Initialize the EAAddinFramework model
    dim model
    set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
    model.initialize(Repository)
    set getEAAddingFrameworkModel = model
end function

It is then further used to get the ConnectorWrapper based on the ElementID from the Info object

set connector = model.getRelationByID(connectorID)

This connector is then used to access the elements that are connected by this Connector

 'get the related elements
 dim relatedElements
 set relatedElements = model.toArrayList(connector.relatedElements)

Note that we need to use model.toArrayList to convert the C# generic List<> that cannot be used by VBScript to an ArrayList that can be used.

The code

Download the complete script: EA-Matic Self-maintaining diagram

In the first part we use the magic EA-Matic keyword to in order to tell EA-Matic it should use this script.

We also declare the important ID’s of the selected connector so we can compare that with the ID’s of this connector once it has changed.

The first event we use is the EA_OnPostNewConnector. This event will be fired after you create a new connector in EA.
In this event we add the newly related elements to each others auto-diagram, if any.

!INC Local Scripts.EAConstants-VBScript

' EA-Matic
' This script, when used with EA-Matic will maintain auto-updating diagrams for elements.
' A nested diagram with prefix AUTO_ will be considered an auto-updating diagram.
' The diagram will keep track of all elements related to the owner of the auto diagram.
'
' Author:     Geert Bellekens
' EA-Matic: http://bellekens.com/ea-matic/
'
'maintain a reference to the connector in context
dim contextConnectorID
dim oldClientID
dim oldSupplierID

'a new connector has been created. Add the related elements to the auto-diagram
function EA_OnPostNewConnector(Info)
     'get the connector id from the Info
     dim connectorID
     connectorID = Info.Get("ConnectorID")
     dim model
     'get the model
     set model = getEAAddingFrameworkModel()
     dim connector
     set connector = model.getRelationByID(connectorID)
     'get the related elements
     dim relatedElements
     set relatedElements = model.toArrayList(connector.relatedElements)
     'for i = 0 to attributes.Count - 1
     if relatedElements.Count = 2 then
        'once with the first
        addRelatedElementoAutoDiagram relatedElements(0), relatedElements(1), model
        'then with the second
        addRelatedElementoAutoDiagram relatedElements(1), relatedElements(0), model
     end if
end function

'adds the related element to the auto_updatediagrams if any
function addRelatedElementoAutoDiagram(element,relatedElement, model)
    'get the diagram owned by this element
    dim ownedDiagrams
    set ownedDiagrams = model.toArrayList(element.ownedDiagrams)
    for each diagram In ownedDiagrams
        'check the name of the diagram
        if Left(diagram.name,LEN("AUTO_")) = "AUTO_" then
            'add the related element to the diagram
            diagram.addToDiagram(relatedElement)
        end if
    next
end function

The next part deals with the deletion of a connector. Each time a connector is about to be deleted EA fires the EA_OnPreDeleteConnector event.
We remove the connected elements from their respective auto-diagrams, but only if this connector was the last connector between the two elements.

' A connector will be deleted. Remove the elements from the auto-diagram
function EA_OnPreDeleteConnector(Info)
     'get the connector id from the Info
     dim connectorID
     connectorID = Info.Get("ConnectorID")
     dim model
     'get the model
     set model = getEAAddingFrameworkModel()
     dim connector
     set connector = model.getRelationByID(connectorID)
     'get the related elements
     dim relatedElements
     set relatedElements = model.toArrayList(connector.relatedElements)
     'for i = 0 to attributes.Count - 1
     if relatedElements.Count = 2 then
        'we only need to remove the related element if they are not connected anymore after deleting the connector
        'so only if there is only one relationship between the two elements
        if sharedRelationsCount(relatedElements(0), relatedElements(1), model) <= 1 then
            'once with the first
            removeRelatedElemenFromAutoDiagram relatedElements(0), relatedElements(1), model
            'then with the second
            removeRelatedElemenFromAutoDiagram relatedElements(1), relatedElements(0), model
        end if
     end if
end function

'returns the number of relations that connecto both elements
function sharedRelationsCount(elementA, elementB, model)
    'start counting at zero
    sharedRelationsCount = 0
    'get the relationships for both objects
    dim relationsA
    set relationsA = model.toArrayList(elementA.relationships)
    dim relationsB
    set relationsB = model.toArrayList(elementB.relationships)
    for each relationA in relationsA
        for each relationB in relationsB
            'if both relations have the same ID then we have a shared relation
            if relationA.id = relationB.id then
                sharedRelationsCount = sharedRelationsCount +1
            end if
        next
    next
end function

' Removes the related element from the auto update diagram if any.
function removeRelatedElemenFromAutoDiagram(element,relatedElement, model)
    'get the diagram owned by this element
    dim ownedDiagrams
    set ownedDiagrams = model.toArrayList(element.ownedDiagrams)
    for each diagram In ownedDiagrams
        dim diagram
        set diagram = ownedDiagrams(i)
        'check the name of the diagram
        if Left(diagram.name,LEN("AUTO_")) = "AUTO_" then
            'Removing elements from a diagram in unfortunately not implemented in the EAAddinFramework so we'll have to do it in the script
            dim eaDiagram
            set eaDiagram = diagram.wrappedDiagram
            for i = 0 to eaDiagram.DiagramObjects.Count -1
                dim diagramObject
                set diagramObject = eaDiagram.DiagramObjects.GetAt(i)
                if diagramObject.ElementID = relatedElement.id then
                    'remove the diagramObject
                    eaDiagram.DiagramObjects.Delete(i)
                    'refresh the diagram after we changed it
                    diagram.reFresh()
                    'exit the loop we have delete the diagramobject
                    exit for
                end if
            next
        end if
    next
end function

The next part deals with the diagram being opened, or shown. Since we are adding elements automatically, we want to use EA’s automatic layout to make the diagram presentable. The downside of this however is that EA also opens the diagram when doing a layout. So instead of doing an auto-layout directly after editing the diagram, we do one as soon as the user opens the diagram, or switches to an already opened diagram. We use the events EA_OnPostOpenDiagram and EA_OnTabChanged

'autodiagrams are automatically layouted when opened
function EA_OnPostOpenDiagram(DiagramID)
    dim model
    'get the model
    set model = getEAAddingFrameworkModel()
    layoutAutoDiagram DiagramID, model
end function

'autodiagrams are automatically layouted when we tab is switched to them
function EA_OnTabChanged(TabName, DiagramID)
    if  DiagramID > 0 then
        dim model
        'get the model
        set model = getEAAddingFrameworkModel()
        layoutAutoDiagram DiagramID, model
    end if
end function

'layout the auto diagram
function layoutAutoDiagram(diagramID, model)
    dim diagram
    set diagram = model.getDiagramByID(DiagramID)
    'if the diagram is an auto diagram then we do an automatic layout
    if Left(diagram.name,LEN("AUTO_")) = "AUTO_" then
        'auto layout diagram
        dim diagramGUIDXml
        'The project interface needs GUID's in XML format, so we need to convert first.
        diagramGUIDXml = Repository.GetProjectInterface().GUIDtoXML(diagram.wrappedDiagram.DiagramGUID)
        'Then call the layout operation
        Repository.GetProjectInterface().LayoutDiagramEx diagramGUIDXml, lsDiagramDefault, 4, 20 , 20, false
    end if
end function

The only thing left now is to update the diagram in case a user changes a connector, and for instance drags one of the ends to another element. In order to do this we need to record the id’s of both ends of the connector once it is selected in EA_OnContextItemChanged and compare that to the id’s of this same connector after it has been changed in EA_OnNotifyContextItemModified

'keep a reference to the selected connector
function EA_OnContextItemChanged(GUID, ot)
    'we only do something when the context item is a connector
    if ot = otConnector then
        dim model
        'get the model
        set model = getEAAddingFrameworkModel()
        'get the connector
        dim contextConnector
        set contextConnector = model.getRelationByGUID(GUID)
        'MsgBox(TypeName(contextConnector))
        contextConnectorID = contextConnector.id
        oldClientID = contextConnector.WrappedConnector.ClientID
        oldSupplierID = contextConnector.WrappedConnector.SupplierID
    end if
end function

'a connector has changed, we need to update the auto-diagrams
function EA_OnNotifyContextItemModified(GUID, ot)
    'we only do something when the context item is a connector
    if ot = otConnector then
        dim model
        'get the model
        set model = getEAAddingFrameworkModel()
        'get the connector
        dim changedConnector
        set changedConnector = model.getRelationByGUID(GUID)
        'check if we are talking about the same connector
        if changedConnector.WrappedConnector.ConnectorID = contextConnectorID then
            dim supplier
            dim client
            'check the client side
            if changedConnector.WrappedConnector.ClientID <>  oldClientID then
                'get supplier
                set supplier = model.getElementWrapperByID(changedConnector.WrappedConnector.SupplierID)
                'remove old client from supplier and vice versa
                set client = model.getElementWrapperByID(oldClientID)
                if not client is nothing then
                    removeRelatedElemenFromAutoDiagram supplier,client, model
                    removeRelatedElemenFromAutoDiagram client, supplier, model
                end if
                'add new client
                set client = model.getElementWrapperByID(changedConnector.WrappedConnector.ClientID)
                addRelatedElementoAutoDiagram supplier,client, model
            end if
            'check the supplier side
            if changedConnector.WrappedConnector.SupplierID <> oldSupplierID then
                'get client
                set client = model.getElementWrapperByID(changedConnector.WrappedConnector.ClientID)
                'remove old supplier from client and vice versa
                set supplier = model.getElementWrapperByID(oldSupplierID)
                if not supplier is nothing then
                    removeRelatedElemenFromAutoDiagram client,supplier, model
                    removeRelatedElemenFromAutoDiagram supplier,client, model
                end if
                'add new supplier
                set supplier = model.getElementWrapperByID(changedConnector.WrappedConnector.SupplierID)
                addRelatedElementoAutoDiagram client,supplier, model
            end if
        end if
    end if
end function

Prevent accidental deletes with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example shows how you can prevent deleting elements that are still being used as a type in either an attribute or parameter.

The script uses the EA_OnPreDeleteElement event to first check whether the element is not still used.

If an attribute using this element as type is found then the element is not deleted and the user is informed with a messagebox.

EA-Matic cannot delete attribute

If it isn’t used as attribute but is used as a type in a parameter or the return type of an operation you get the following messagebox:

EA-Matic cannot delete parameter

Usage of Enterprise Architect Add-in Framework

This script also illustrates how you can use the Enterprise Architect Add-in Framework within your script.

This extensive open source framework is the basis for the add-ins EA-Matic and EA Navigator, and offers a much more functional interface to the model then the standard EA API.

This code inializes the model object with the current Repository

set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
model.initialize(Repository)

It is then further used to get the element based on the ElementID from the Info object

set element = model.getElementWrapperByID(elementID)

The element then gives us access the attributes and parameters using this element by means of

set usingAttributes =  model.toArrayList(element.getUsingAttributes())
set usingParameters = model.toArrayList(element.getUsingParameters())

Note that we need to use model.toArrayList to convert the C# HashSet that cannot be used by VBScript to an ArrayList that can be used.

The code

Download the complete script: EA-Matic Prevent accidental deletes

option explicit
'EA-Matic

function EA_OnPreDeleteElement(Info)
     'Start by setting false
     EA_OnPreDeleteElement = false
     dim usage
     'Initialize the EAAddinFramework model
     dim model
     set model = CreateObject("TSF.UmlToolingFramework.Wrappers.EA.Model")
     model.initialize(Repository)
     'get the elementID from Info
     dim elementID
     elementID = Info.Get("ElementID")
     'get the element being deleted
     dim element
     set element = model.getElementWrapperByID(elementID)
     'Manual override is triggered by the name. If it starts with DELETED_ then the element may be deleted.
     if Left(element.name,LEN("DELETED_")) = "DELETED_" then
        'OK the element may be deleted
        EA_OnPreDeleteElement = true
     else
        dim usingAttributes
        set usingAttributes =  model.toArrayList(element.getUsingAttributes())
        'Check if the element is used as type in attributes
        if usingAttributes.Count = 0 then
            'Check if the element is used as type in a parameter
            dim usingParameters
            set usingParameters = model.toArrayList(element.getUsingParameters())
            if usingParameters.Count = 0 then
                'OK, no attributes or parameters use this element, it may be deleted
            EA_OnPreDeleteElement = true
            else
                usage = "parameter(s)"
            end if
        else
            usage = "attribute(s)"
        end if
     end if
     if EA_OnPredeleteElement = false then
          'NO the element cannot be deleted
          MsgBox "I'm sorry Dave, I'm afraid I can't do that" & vbNewLine _
          & element.name & " is used as type in " & usage , vbExclamation, "Cannot delete element"
     end if
end function

Using the add-in menu with EA-Matic

With EA-Matic you can develop add-ins for Enterprise Architect using the built-in scripting feature of EA.

This example shows how to use the add-in menu to add your own menu and menu option, and react to the user clicking such a menu option.

With this script you can use menu options as a regular add-in would

EA-Matic Menu in Action

Download the complete script: EA-Matic MenuHandling

Step 1: defining the menu

'EA-Matic
'Tell EA what the menu options should be
function EA_GetMenuItems(MenuLocation, MenuName)
	if MenuName = "" then
		'Menu Header
		EA_GetMenuItems = "-&MyAddinMenu"
	else
		if MenuName = "-&MyAddinMenu" then
			'Menu items
			Dim menuItems(1)
			 menuItems(0) = "TreeViewMenu"
			 menuItems(1) = "DiagramMenu"
			 EA_GetMenuItems = menuItems
		 end if
	end if
end function

Step 2: define the menu state

'Define the state of the menu options
function EA_GetMenuState(MenuLocation, MenuName, ItemName, IsEnabled, IsChecked)
	if MenuName = "-&MyAddinMenu" then
		Select Case ItemName
			case "TreeViewMenu"
				if MenuLocation = "TreeView" then
					IsEnabled = true
				else
					IsEnabled = false
				end if
			case "DiagramMenu"
				if MenuLocation = "Diagram" then
					IsEnabled = true
				else
					IsEnabled = false
				end if
		end select
	end if
	'to return out parameter values we should return an array with all parameters
	EA_GetMenuState = Array(MenuLocation, MenuName, ItemName, IsEnabled, IsChecked)
end function

Step 3: React to user clicking a menu option

'react to user clicking a menu option
function EA_MenuClick(MenuLocation, MenuName, ItemName)
	 	if MenuName = "-&MyAddinMenu" then
		Select Case ItemName
			case "TreeViewMenu"
				Dim Package
				Set Package = Repository.GetTreeSelectedPackage()
				MsgBox ("Current Package is: " & Package.Name)
			case "DiagramMenu"
				Dim Diagram
				Set Diagram = Repository.GetCurrentDiagram()
				MsgBox("Current Diagram is: " & Diagram.Name)
		end select
	end if
end function