Chapter 5. Inside the subsystems

Table of Contents

5.1. Model
5.1.1. Factories
5.1.2. Helpers
5.1.3. The model event pump
5.1.4. How to work against the model
5.1.5. How do I...?
5.2. Critics and other cognitive tools
5.2.1. Main classes
5.2.2. How do I ...?
5.2.3. org.argouml.cognitive.critics.* class diagram
5.3. Diagrams
5.3.1. Multi editor pane
5.3.2. How do I add a new element to a diagram?
5.3.3. How to add a new Fig
5.4. Property panels
5.4.1. Adding the property panel
5.5. Persistence
5.6. Reverse Engineering Subsystem
5.7. Code Generation Subsystem
5.8. Java - Code generations and Reverse Engineering
5.8.1. How do I ...?
5.8.2. Which sources are involved?
5.8.3. How is the grammar of the target language implemented?
5.8.4. Which model/diagram elements are generated?
5.8.5. Which layout algorithm is used?
5.9. Other languages
5.10. The GUI Framework
5.11. Application
5.11.1. What is loaded/initialized?
5.11.2. Details pane
5.12. Help System
5.13. Internationalization
5.13.1. Organizing translators
5.13.2. Ambitions for localization
5.13.3. How do I ...?
5.14. Logging
5.14.1. What to Log in ArgoUML
5.14.2. How to Create Log Entries...
5.14.3. How to Enable Logging...
5.14.4. How to Customize Logging...
5.14.5. References
5.15. JRE with utils
5.16. To do items
5.17. Explorer
5.17.1. Requirements
5.17.2. Public APIs and SPIs
5.17.3. Details of the Explorer Implementation
5.17.4. How do I ...?
5.18. Module loader
5.18.1. What the ModuleLoader does
5.18.2. Design of the new Module Loader
5.19. OCL
[Warning]Warning

This chapter is currently under rework with new subsystem organization.

Things that are not actually in place are: TargetManager

...

5.1. Model

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.

5.1.1. Factories

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!

5.1.2. Helpers

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!

5.1.3. The model event pump

5.1.3.1. Introduction

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:

  1. The model factories, responsible for creation and deletion of model elements

  2. The model helpers, responsible for utility functions to manipulate the model elements and

  3. 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 PropertyChangeEvents.

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.

5.1.3.2. Public API

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? ”.

5.1.3.2.1.  How do I register a listener for a certain type event

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".

5.1.3.2.2. How do I remove a listener for a certain event

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.

5.1.3.2.3.  Hey, I saw some other methods for adding and removing?

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.

5.1.3.3. Tips

  1. 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.

  2. 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.

5.1.3.4. Possible investigation points and improvements

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?

5.1.4. How to work against the model

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

WhatNS-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.

5.1.5. How do I...?

  • ...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 buildXXXX(MModelelement toattach1, ...) 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.

    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.