Monday, 31 August 2009

Week 35 of 2009

Most of this week I have been finishing the changes that I started last week - the last 20% always takes longer than you expect! I have also been resolving some imported shape problems in Object Model diagrams (OIMs, OCMs and OAMs). Skip over the next few paragraphs for details.

One of the problems I discovered (in that last 20%!) was how responsive deleted model elements need to be while a project is being loaded. One of the changes I made was to stop deleted model element listeners from doing anything except awaiting a request to clear their deleted status. This reduces unnecessary derived calculations and stops listeners from expanding their listening scope until after their deleted status is cleared. The problem that I discovered was that some derived fields must be calculated so that cross references during loading can be resolved prior to those model elements having their deleted status cleared.

A massively cut down version of the Identifier class is shown below to highlight current listener design and the problem I mention above:


public class Identifier extends ModelElement<OOAObject>
        implements OrderedElement<OOAObject> {

    public static final String IDENTIFIER_ID_PROPERTY = "identifierID";

    public IdentifierListener implements PropertyChangeListener {
        public IdentifierListener() {
            checkDeleted();
        }

        protected void checkDeleted() {
            if (isDeleted()) {
                for (Attribute attribute : m_identifyingAttributes) {
                    attribute.removePropertyChangeListener(this);
                }
            } else {
                for (Attribute attribute : m_identifyingAttributes) {
                    attribute.addPropertyChangeListener(this);
                }
                setBrowserLabel();
            }
        }

        public void propertyChange(PropertyChangeEvent event) {
            Object source = event.getSource();
            String propertyName = event.getPropertyName();
            if (source == Identifier.this) {
                if (propertyName == DELETED_PROPERTY) {
                    checkDeleted();
                } else if (propertyName == ORDER_PROPERTY) {
                    setIdentifierID();
                } else if (isDeleted()) {
                    // Do nothing else if deleted
                } else if (propertyName == IDENTIFIER_ID_PROPERTY) {
                    setBrowserLabel();
                } else if (propertyName == IDENTIFYING_ATTRIBUTE_PROPERTY) {
                    Object oldValue = event.getOldValue();
                    if (oldValue instanceof Attribute) {
                        ((Attribute) oldValue)
                            .removePropertyChangeListener(this);
                    }
                    Object newValue = event.getNewValue();
                    if (newValue instanceof Attribute) {
                        ((Attribute) newValue)
                            .addPropertyChangeListener(this);
                    }
                    setBrowserLabel();
                }
            } else if (source instanceof Attribute) {
                if (propertyName == Attribute.NAME_PROPERTY
                        || propertyName == Attribute.ORDER_PROPERTY) {
                    setBrowserLabel();
                }
            }
        }
    }

    private int m_order; // Set by parent object
    private String m_identifierID;
    private Identifier[] m_identifyingAttributes;

    public Identifier(OOAObject parent) {
        super(parent);
        m_order = 0;
        m_identifierID = "";
        m_identifyingAttributes = Attribute.EMPTY_ATTRIBUTES;
        addPropertyChangeListener(new IdentifierListener());
    }

    public final String getIdentifierID() {
        return m_identifierID;
    }

    public void setIdentifierID() {
        int order = m_order;
        if (order == 0) {
            return;
        }
        String identifierID = (order == 1) ? "I" : "I" + order;
            // Don't use order if 1 since "I1" looks confusing
        String oldIdentifierID = m_identifierID;
        m_identifierID = identifierID;
        fireDerivedChange(IDENTIFIER_ID_PROPERTY,
                          oldIdentifierID, identifierID);
    }
}
Since identifier ID is used to reference target identifiers within participant mappings then it must be calculated even if identifier is currently deleted. However, the identifier ID calculation which depends on order being set by a parent OOAObject still requires the identifier to have been added to the parent object.

From the example above, you can see that all model elements have a model element instance and a separate listener instance (sometimes more than one). The listener drives all derived calculations and arranges for the spread of itself to other instances of interest. The new deleted status is used to control all such listeners but some derived calculations must continue independent of the deleted status. This is a consequence of using mathematically dependent attributes in preferred identifiers. I'm sure this is going to be an interesting problem to solve when I start creating my model compiler!

I can't remember whether I've already mentioned a name change (that I made some time ago) from Terminator to External Entity. The original Shlaer-Mellor books used both terms and I adopted the term Terminator (short and sweet!). However, other tool vendors all adopted the term External Entity instead. I later introduced an Entity supertype object above Terminator and Object. I finally decided to bite the bullet and replace all use of the term Terminator with External Entity. Both iUML and BridgePoint still use the term External Entity as a kind of Actor. However, OOA Tool simply uses Actor instead of External Entity when Executable UML notation is selected.

The problem with imported shapes is that imported objects (in OIMs), imported state models (in OCMs/OAMs) and external entities (in OCMs/OAMs) without links to other shapes on those diagrams are not reimported when a project is loaded. The original justification being that those shapes must be redundant. However, I now reimport those shapes if they exist in the XML project file. I have also added some extra actions to the OCM and OAM diagram editor to allow users to add existing external entities to those diagrams and remove them from specific diagrams. Obviously, external entities which have links to other shapes can't be removed. Once the next build is released I will have to update the various technical notes discussing OIMs, OCMs and OAMs.

A related issue is Subsystem Model diagrams (SCMs and SAMs) which don't currently show external entities at all. However, I think I will allow users to add external entities allowing subsystem communications/accesses to those external entities to be shown. I debated this issue with myself when I first coded the editors for those diagrams and I decided to stick with what is shown in the original Shlaer-Mellor books. However, I like the idea of being able to see subsystem communications/accesses to external entities and I think I will take advantage of the above change that I made with regard to reimporting shapes during project loading. However, I haven't coded this up yet.

Now, I have some loose ends to sort out and I need to finish off the technical note on identifiers but then I will refocus myself on the outstanding items required to get Build 014 released. I remember the days when I had a team working for me and I could delegate (sweet...)!

No comments: