Table of Contents
![]() | Warning |
---|---|
This chapter is currently under rework with new subsystem organization. Things that are not actually in place are: TargetManager |
...
Purpose - to provide the data structures that keep track of the element in the UML model. This comes with a complete set of methods to modify the model and register interest in changes to the model.
The Model is located in org.argouml.model
.
The Model is a Model subsystem. See Section 4.4, “Model subsystems”.
This is currently implemented using NSUML internally to implement the UML model. The plan is to replace NSUML with some JMI compliant model instead (probably MDR), and for that reason all APIs to the Model subsystem using NSUML objects are to be replaced by interfaces without NSUML object and eventually removed.
ArgoUML uses several factories and helper classes to manipulate the NSUML model. The NSUML model itself does not define enough 'business' logic to be directly used and the factories and helper classes provide a set of interfaces that wraps all functions of NSUML. Per section of chapter 2 of the UML 1.3 specification there is one factory and one helper.
The factories contain all methods that deal with creating and building model elements. The helpers contain all utility methods needed to manipulate the model elements.
Both helpers and factories (and the Facade and ModelEventPump) are interfaces that are fetched through static methods in the Model object. Each implementation must provide objects for each of these interfaces.
In the NSUML implementation factories also contain delete methods but they are only used internally within the Model subsystem.
The factories contain in most cases a create method for each model element.
Example: createClass
resides in CoreFactory
-interface.
Besides that, there are several build methods to build classes.
The build methods have a signature like
public Object buildMODELELEMENTNAME(params);
.
Each build method verifies the wellformedness rules as defined in the UML spec 1.3. The reason for this is that NS-UML does not enforce the wellformedness rules even though non-well-formed UML can lead to non-well-formed XMI which leads to saving/loading issues and all kinds of illegal states of ArgoUML.
If you want to create an element you shall use the build or create methods in the factories. You are strongly advised to use a build method or, if there is none that suits your needs, to write a new one reusing the already existing build methods and utility methods in the helpers. The reason for this is that the event listeners for the newly created model element are setup correctly.
Question: Am I allowed to call the factories from any thread? Answer: The current checks are not written to allow for multiple threads so don't!
The helpers contain all utility methods for manipulating model elements.
For example, they contain methods to get all model elements
of a certain class out of the model
(see getAllModelelementsOfKind
in
ModelManagementHelper
).
To find a utility method you need to know where it is.
As a rule of thumb, a utility method for some model element
is defined in the helper that corresponds with the section
in the UML specification.
For example, all utility methods for manipulating classes
are defined in CoreHelper
.
There are a few exceptions to this rule, mainly if the utility method deals with two model elements that correspond to different sections in the UML specification. Then you have to look in both corresponding helpers and you will probably find what you are searching for.
Question: Am I allowed to call the helpers from any thread? Answer: The current checks are not written to allow for multiple threads so don't!
Late 2002, the ArgoUML community decided for the introduction of a clean interface between the NSUML model and the rest of ArgoUML. This interface consists of three parts:
The model factories, responsible for creation and deletion of model elements
The model helpers, responsible for utility functions to manipulate the model elements and
The model event pump, responsible for sending model events to the rest of ArgoUML.
The model factories and the model helpers are described in Section 5.1.1, “Factories” and Section 5.1.2, “Helpers” respectively.
In the beginning of 2003, in the work to replace NSUML, the need for this
interface not to use any NSUML classes was seen.
The ModelFacade
was introduced to wrap
model factories,
model helpers, and
direct calls to NSUML
but not the model event pump.
In April 2004 a ModelEventPump
-interface
was introduced to wrap
the UmlModelEventPump
using PropertyChangeEvent
s.
The model event pump is the gateway between the model elements and the rest of ArgoUML. Events fired by the model elements are caught by the pump and then 'pumped' to those listeners interested in them. The main advantage of this model is that the registration of listeners is concentrated in one place (see picture *). This makes it easier to change the interface between the model and the rest of ArgoUML.
Besides this, there are some improvements to the performance of the pump made in comparison to the situation without the pump. The main improvement is that you can register for just one type of event and not for all events fired by some model element. In this respect the pump works as a filter.
The model event pump will replace all other event mechanisms for model events in the future. These mechanisms (like UMLChangeDispatch and ThirdPartyEventlisteners for those who are interested) are DEPRECATED. Do not use them therefore and do not use classes that use them.
You might wonder: how does this all work? Well, very simple in fact.
A model event (from now on a Event) has a name that uniquely identifies the type of the event. In most cases the name of the Event is equal to the name of the property that was changed in the model. In fact, there is even a 1-1 relationship between the type of Event and the property changed in the model. Therefore most listeners that need Events are only interested in one type of Event since they are only interested in the status of 1 property.
TODO: What thread will I receive my event in? What locks will be held by the Model while I receive my event i.e. is there something I cannot do from the event thread?
In the case described above (the most common one) you only have to subscribe with the pump for that type of event. This is explained in section Section 5.1.3.2.1, “ How do I register a listener for a certain type event ” and Section 5.1.3.2.2, “How do I remove a listener for a certain event”
Besides the case that you are interested in only one type of event (or a set of types), there are occasions that you are interested in all events fired by a certain model element or even for all events fired by a certain type of model element. For these cases, the pump has functionality too. This is described in section Section 5.1.3.2.3, “ Hey, I saw some other methods for adding and removing? ”.
This is really very simple. Use the model
addModelEventListener(PropertyChangeListener listener, Object modelelement, String eventName)
like this:
Model.getPump().addModelEventListener(this, modelelementIAmInterestedIn, "IamInterestedInThisEventnameType");
Now your object this gets only the Events
fired by modelElementIAmInterestedIn that have the name
"IamInterestedInThisEventnameType".
This is the opposite of registering a listener. It all works with the method
removeModelEventListener(PropertyChangeListener listener, Object modelElement, String eventName)
on the ModelEventPump
like this:
Model.getPump().removeModelEventListener(this, modelelementIAmInterestedIn, "IamInterestedInThisEventnameType");
Now your object is not registered any more for this event type.
Yes there are some other method for adding and removing. You can add a listener that is interested in ALL events fired by a certain model elements. This works with the method:
addModelEventListener(PropertyChangeListener listener, Object modelelement)
As you can see no names of events you can register for here.
Furthermore, you can add a listener that is interested in several types of events but coming from 1 model element. This is a convenience method for not having to call the methods explained in section Section 5.1.3.2.1, “ How do I register a listener for a certain type event ” more than once. It works via:
addModelEventListener(PropertyChangeListener listener, Object modelelement, String[] eventNames)
You can pass the method an array of strings with event names in which your listener is interested.
Thirdly there is a very powerful method to register your listener to ALL events fired by a ALL model elements of a certain class. You can understand that using this method can have severe performance impacts. Therefore use it with care. The method is:
addClassModelEventListener(PropertyChangeListener listener, Object modelClass)
There are also methods that allow you to register only for one type of event fired by all model elements of a certain class and to register for a set of types of events fired by all mod elements of a certain class.
Of course you can remove your listeners from the event pump. This works with methods starting with remove instead of add.
Don't forget to remove your listener from the event pump if it's not interested in some event any more.
If you do not remove it, that's gonna cost performance and it will give you a hard time to debug all the logical bugs you see in your listener.
When you implement your listener, it is wise to NOT DO the following:
propertyChanged(MElementEvent event) { // do my thing for event type 1 // do my thing for event type 2 // etc. }
This will cause the things that need to be done for event type 1 to be fired when event type 2 do arrive.
This still happens at a lot of places in the code of ArgoUML, most notably in the modelChanged method of the children of FigEdgeModelElement.
Should we use our own event types?
Should we replace
the MElementListener
with PropertyChangeListener
and
MElementEvent
with PropertyChangeEvent
?
One reason we havn't done so yet is that it involves a lot of work and
testing.
Change the implementation of the Event pump itself? Not the API but the implementation!
At the moment the event pump does not use the AWT Event Thread for dispatching events. This can make ArgoUML slow (in the perception of the user).
Use the standard
data structure that Swing uses for event registration
(i.e. javax.swing.EventListenerList
).
Would this be an improvement?
NS-UML is used within the Model subsystem to keep all data structures in place. Eventually we will change that to JMI/MDR that is newer and better and will take us into UML 1.4, UML 1.5 and UML 2.0. Working directly against NS-UML or JMI/MDR will make changes in the NS-UML or JMI/MDR affect large portions of the code. For this reason we have in the Model subsystem, a set of classes that lay between the NS-UML and JMI/MDR that hides the APIs of NS-UML or JMI/MDR between something that will not change while moving between them. This is the API classes of the Model subsystem i.e. Factories, Helpers, Event Pump (where to register for changes).
Here follows a list of how different things are done for the purpose
of making the transition easy.
Everything within ArgoUML should access the Model subsystem through the
interfaces in the org.argouml.model
package.
The NS-UML or JMI/MDR and whatever other implementation we could eventually
come up with would provide the implementation of those interfaces.
Table 5.1. How to work against the model
What | NS-UML (use only within Model subsystem) | JMI/MDR (use only within Model subsystem) | Model subsystem |
---|---|---|---|
Test that an Object o has a certain type |
o instanceof Mmodelelementtype → boolean
| ???CLASSNAME???.isInstanceOf(RefObject toTest, String className) → boolean |
Model.getFacade().isAmodelelementtype (o) → boolean
|
Get a single valued model element from an Object o |
((Mmodelelementtype ) o).getproperty ()
→ model element
| ((RefFeatured) obj).refGetValue(String propName) → ???Type??? |
Model.getFacade().getproperty (o) → Object
|
Get a multi valued property from an Object o |
((Mmodelelementtype ) o).getproperty ()
→ Collection
| ((RefFeatured) obj).refGetValue(String propName) → Collection |
Model.getFacade().getproperty (o)
→ Iterator or Collection (total confusion!)
|
Create a new model element of type Type: |
MFactory.getDefaultFactory().createType ()
|
???CLASSNAME???.creatInstance(String "Type ",
List argument)
→ RefObject
|
Model.getModelElementDomain? Factory.buildmodelelementtype (args)
or
Model.getModelElementDomain? Factory.createmodelelementtype ()
to create them completely empty.
|
Delete a model element |
Model.getUmlFactory().delete(object )
| ||
Register for notification that a model element Object o has changed: | ((MBase) o).addMElementListener(MElementListener el) | ((MDRChangeSource) obj).addChangeListener(???) | Model.getPump().addModelEventListener((PropertyChangeListener) li, Object o, String[] eventnames) |
Register for notification on all model elements of a certain type Type: | Not possible! | ((MDRChangeSource) obj.refClass()).addChangeListener(???) | Model.getPump().addModelEventListener((PropertyChangeListener) li, (Object) Model.getMetaTypes().getMODELELEMENTTYPE(), String[] eventnames) |
How do I get the model as XMI on the stream Stream: | (new XMIWriter(MModel m, Writer Stream)).gen() | new XMIWriter(???) | Handled by the Persistence subsystem. |
...add a new model element?
Make a parameterless build method for your NSUML model element in one of
the UML Factories (for instance CoreFactory
).
Stick to the UML 1.3 spec to choose the correct Factory.
The package structure under org.argouml.model.uml
follows the chapters in the UML spec so get it and read it!
In the build method, create a new model element using the appropriate
create method in the factory.
The build method e.g. is a wrapper around the create method.
For all elements there are already create methods (thanks Thierry).
For some elements there are already build methods.
If you need one of these elements, use the build method before you
barge into building new ones.
Initialize all things you need in the build method as far as they
don't need other model elements.
In the UML spec you can read which elements you need to initialize.
See for example buildAttribute()
for an example.
If you need to attach other already existing model elements to your
model element make a
build
method in the factory where you made the build method.
Don't ever call the create methods directly.
If we use the build methods we will always have initialized
model elements which will make a difference concerning save/load issues
for example.
XXXX
(MModelelement
toattach1, ...)
Now you probably also need to create a Property Panel and a Fig object (See Section 5.3.3.5, “Creating a new Fig (explanation 2)”).
...create a new create method?
Create it in the correct factory.
...create a new utility method?
Create it in the correct helper.