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.
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.
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.
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
Leave a Reply
Want to join the discussion?Feel free to contribute!