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...)!

Monday 24 August 2009

Week 34 of 2009

This week I started with a nice Costa coffee (I like Starbucks but their cafe in Colchester has unfriendly staff and poor seating). I should have been nailing down the outstanding issues holding back Build 014 of OOA Tool. Instead, I had a think about some of the annoying design decisions I made when I started creating the model elements that realize the OOA of OOA within OOA Tool.

One of the most annoying (for me) is that I associate a dynamic parent with each model element. On reflection, a static unchanging parent is much safer and entirely acceptable for OOA Tool. You might not think it makes much difference. However, there is an enormous amount of listener code within these model elements allowing derived fields to push their changes out to GUI listeners which update the GUI as and when changes occur. There is also lots of unnecessary parent status checking. I also take advantage of the fact that the parent of a child is cleared when it is removed from the parent. Of course, removing the parent can cause problems with the deleted model elements which may still be in use! Thankfully, programmer deletion is logical rather than physically in Java so I don't need to worry about memory being reclaimed while still in use.

I also associate a changed status with each model element which I recursively clear after a project is loaded or saved. However, I hadn't got around to setting the changed status when non-derived fields are changed. An annoying side-effect of this was that I couldn't check whether any projects had been changed when the application is exited, i.e. a confirmation dialog is always popped up - very annoying.

A cut down outline of the ModelElement class is shown below:


public abstract class ModelElement {
    private ModelElement m_parent;
    private boolean m_changed;

    protected ModelElement() { }

    public ModelElement getParent() { }
    public void setParent(ModelElement parent) { }
    public abstract ModelElement[] getChildren();

    public boolean isChanged() { }
    public void setChanged(boolean changed) { }
    public boolean isChangedAnywhere() { }
    public void setChangedRecursively(boolean changed) { }

    protected void firePropertyChange(String propertyName,
            ModelElement oldValue, ModelElement newValue,
            boolean child) {
        if (oldValue != newValue) {
            m_propertyChangeSupport.firePropertyChangeEvent(
                propertyName, oldValue, newValue);
            if (child) {
                if (oldValue != null) {
                    oldValue.setParent(null);
                }
                if (newValue != null) {
                    newValue.setParent(this);
                }
            }
        }
    }
}

A cut down outline of the new improved ModelElement class is shown below:


public abstract class ModelElement<P extends ModelElement> {
    private P m_parent;
    private boolean m_deleted;
    private boolean m_changed;
    private boolean m_changedAnywhere;

    protected ModelElement(P parent) { }

    public P getParent() { }
    public abstract ModelElement[] getChildren();

    public boolean isDeleted() { }
    public void setDeleted(boolean deleted) { }
    public void setDeletedRecursively(boolean deleted) { }

    public final boolean isChanged() {
        return m_changed;
    }
    public void setChanged(boolean changed) {
        boolean oldChanged = m_changed;
        if (changed != oldChanged) {
            m_changed = changed;
            m_propertyChangeSupport.firePropertyChange(
                CHANGED_PROPERTY, oldChanged, changed);
            ModelElement modelElement = this;
            while (modelElement != null) {
                modelElement.setChangedAnywhere();
                modelElement = modelElement.getParent();
            }
        }
    }
    public final boolean isChangedAnywhere() {
        return m_changedAnywhere;
    }
    public void setChangedAnywhere() {
        boolean changedAnywhere = m_changed;
        if (!changedAnywhere) {
            for (ModelElement child : getChildren()) {
                if (m_child.m_changedAnywhere) {
                    changedAnywhere = true;
                    break;
                }
            }
        }
        boolean oldChangedAnywhere = m_changedAnywhere;
        m_changedAnywhere = changedAnywhere;
        m_propertyChangeSupport.firePropertyChange(
            CHANGED_ANYWHERE_PROPERTY,
            oldChangedAnywhere, changedAnywhere);
    }
    public void setChangedRecursively(boolean changed) {
        setChanged(changed);
        for (ModelElement child : getChildren()) {
            child.setChangedRecursively(changed);
        }
    }

    protected void firePropertyChange(String propertyName,
            ModelElement oldValue, ModelElement newValue,
            boolean child, boolean derived) {
        if (oldValue != newValue) {
            m_propertyChangeSupport.firePropertyChangeEvent(
                propertyName, oldValue, newValue);
            if (child) {
                if (oldValue != null && !m_deleted) {
                    oldValue.setDeletedRecursively(true);
                }
                if (newValue != null && !m_deleted) {
                    newValue.setDeletedRecursively(false);
                }
            }
            if (!derived && !m_deleted) {
                setChanged(true);
            }
        }
    }
}

This requires parents to be statically declared using Java generics and requires the parent to be passed to the constructor. The only root element without a parent being a Project in OOA Tool. However, it is still useful to know when a model element is removed so a new deleted status was added. The setting and clearing of this status is done when property changes are fired. I have also been able to clear up some minor memory leaks by making use of the deleted status to add or remove nested listeners in line with the deleted status.

I also added another flag to firePropertyChange() indicating whether a derived change has occurred. This allows us to set changed statuses automatically as a consequence. Since we have a fixed parent now we can also fairly easily determine a changed anywhere status for each model element where anywhere indicates at or below a given model element. The old design required a recursive check to be performed on request to determine whether changed anywhere. OOA Tool now adds a changed (or changed anywhere) overlay icon to tree browser icons when changed or changed anywhere statuses are updated. It can also be determined if any projects have changed when the user exits the application, i.e. no redundant dialog. However, there is a limitation here in that if a change is made and another change follows it which undoes the previous change, the model element will still be flagged as changed. This doesn't occur in any of the editor dialogs since they record the original values and always check the current values against the original values when determining whether a change has occurred.

These changes (80% done now) were for the most part straight forward except for the fact that there are 336 model element classes (744 classes in total)! However, there were a few places where I unnecessarily took advantage of dynamic parents and this code needed to be rewritten.

OK, this was a serious side-track from my current priorities. However, I am very happy with the changed and changed anywhere indicators in the tree browser. I am also much happier that the parent is always set now and its type can be statically determined reducing the need for so many casts.

Monday 17 August 2009

Week 28/29/30/31/32/33 of 2009

OK, back to the Shlaer-Mellor and Executable UML grindstone. I've have a few weeks away from modelling and tooling to clear my head. My highest priority now is to create a stable build to release the many features I have added and changes I have made to OOA Tool. The outstanding issues holding up the release are:

  • reworking OOA of OOA Metamodel 0.01 population logic since I have considerably expanded the Information Model and Data Type subsystems,
  • finishing the outstanding chunks of operation logic including predefined operations on all data types,
  • and reviewing and documenting OOA Interchange Format Version 0.05 since lots of changes have been made (but OOA Tool will still be backwards compatible with Version 0.04).
The next build won't include a working simulator since I'm not happy with all the semantics yet and I don't want people investing time creating Action Language code until I'm happy.

Over the last week, I had a good long look at the subject of object ordering within information models (another diversion!). Complete orderings across child-parent relationships are already handled using Arbitrary ID Attributes and Parent Allocated ID Types (important note to self: finish the technical note on Arbitrary and Ordinal IDs so that people know what I'm talking about!). Partial and complete orderings across objects within Action Language code is handled using an ordered by clause on select statements. However, such orderings are transitory in nature and prone to becoming out of date when an information model changes, e.g. what was once a complete ordering can become a partial ordering without anyone noticing. With regard to complete orderings, the logical place to look in an information model is an identifier since an identifier specifies the minimum information required to define a complete ordering. This lead me to subtype identifiers as either Unordered Identifiers or Ordered Identifiers (see Information Model). Now I'm not going to say too much here since I want to discuss the issues in full in a technical note on Identifiers which I hope to publish soon. However, objects can now define a preferred identifier and an ordered by identifier. If the latter is specified then the object in question is ordered and object instances can be compared using normal relational operators within Action Language code.

This work on ordered identifiers has also allowed me to think how limited but meaningful aggregation and composition relationships could be defined within Executable UML models. Composition relationships could result from child-parent relationships that result directly from Parent Allocated ID Types since those parents have specific duties to manage their children (even though those duties are automatically handled by a software architecture). While aggregation relationships could result from child-parent relationships that result from ordered identifiers. I'm going to add support for hollow and filled diamonds into OOA Tool and see how these ideas work within my example models.

Anyway, I hope to get back to weekly reporting now. If anyone wants to discuss any ideas then don't be shy.