Thursday, 3 January 2013

MVC Basics

Sailing Club Results

Introduction

A long time ago I wrote a Java application for managing the results for my sailing club (dinghy sailing). It's harder than it looks as they have a handicap system and then the way they aggregate the results to produce an overall winner isn't straight forward either. Unfortunately they changed the rules a couple of years ago which blew my software to hell so I have been using spreadsheets. It is presently summer and so I'm on holidays and I thought I would do some work on my old application to see if I could bring it back. I learned a few things along the way and hence my post.

What Changed

With the numbers at the club dwindling, a couple of years ago they decided to implement an 'Open' class where boats that otherwise would have nobody (or few people) to compete with compete against boats of a different class using rating system. So essentially you can have a 420 competing against a moth and a 12 foot skiff even though they didn't sail the same course and clearly are boats with very different characteristics.

Instead of just having the small fleets in this open fleet, the idea is everybody goes in it. The classes that have sufficient numbers also are in a competition of their own so their results effectively contribute to multiple competitions. My club (as well as many others) have a general series where your handicap results are counted towards an overall result but also have a 'championship' series which is based on one race a month and instead the scratch results count towards the overall result.

This makes life really complicated as your result could then contribute to

  • The point score competition for your class 
  • The championship competition for your class 
  • If you are part of a division (like Radial Lasers which are a division of Lasers) then your championship result could also count towards the division competition. 
  • The fleet point score competition
  • The fleet championship competition 
So effectively your result could be counted towards 5 different competition and treated (handicap and rating wise) in 3 different ways! You can see how this got hard!

Application

The application is a Java GUI based on the SWT GUI toolkit that underlies Eclipse.

The data is stored in a HSQL database and I used hibernate and JPA annotations to retrieve the models.

The GUI is best described as an explorer style application with a tree on the left and a panel on the right that changes depending on what you select. The tree has nodes for registered boats and then lists each boat (under its boat class and division) below that. The races are grouped by date (so all races on a single day are a tree node) and then there is a leaf node for each race.

Problem

Well the initial problem was the data model needed a major re-think. I had each race contributing to one or more competitions. A fleet model described a set of boat classes that competed in a race and the results were then counted in each competition.

The new scheme is more complicated as one set of results could contribute to multiple races AND each race could count towards multiple competitions.

I began re-working the data model and discovered the implications were so far reaching I needed to effectively start with a clean slate and re-add the code back in as I went. This is when I started looking at the code again with a more critical eye.

GUI

The GUI was (at best) knocked together. I was learning Java, learning SWT and trying to make it work MVC style. The latter failed and I found myself reverting to my MFC ways and implementing lots of business logic in the GUIs. This consequently made the whole thing impossible to unit-test and so I was testing it manually. Consequently the app was fragile.

My plan is to re-write it to make greater use of Model-View classes and to separate the controller aspects of the GUI from the view.

RaceInfoTree

The RaceInfoTree is effectively already a MVC but some of the roles have gotten a bid muddled. The RaceInfoTree displays and manages the display of the race data as a tree of nodes.

At the heart of this is a RaceInfoTreeNode class which is then sub-classed by each node. The RaceInfoTreeNode is effectively a View-Model that provides the information required by the tree GUI component to render each tree node.

public abstract class RaceInfoTreeNode
{
    private RaceInfoTreeNode parent;
    protected List<RaceInfoTreeNode> children;

    public RaceInfoTreeNode(RaceInfoTreeNode    parent)
    {
        this.parent = parent;
        children = new ArrayList<RaceInfoTreeNode>();

        if (parent != null)
        {
            parent.addChild(this);
        }
    }

    public RaceInfoTreeNode getParent()
    {
        return parent;
    }

    public List<RaceInfoTreeNode> getChildren()
    {
        return children;
    }

    public void addChild(RaceInfoTreeNode node)
    {
        children.add(node);
    }

    public void removeChild(RaceInfoTreeNode node)
    {
        children.remove(node);
    }

    public boolean hasChildren()
    {
        return children.size() != 0;
    }

    public abstract String getLabel();
}

Then there are sub-classes for each node type that holds a pointer to a model class held by that node. For example there is a BoatClassNode that holds a BoatClassModel which hold information about a class of sailing boat (such as Laser or 420, Flying-11 etc).

Originally each node had a method for customizing the menu for that node with actions that could be performed. The trouble with this is it drags a SWT dependency through all this code and is really a controller issue. I removed this and created a controller that did this by switching on the node class.

There is a RaceInfoTreeBuilder that builds the node tree from the DB initially. Originally this took an EntityManager and used the DAO classes to extract the data. I moved all the DAO code into a series of service classes and now the EntityManger is located by the service. The service is fronted with an interface so now I can test this stuff without a DB.

The RaceInfoTreeBuilder got a bit overgrown with methods for adding nodes to the tree. For example if you add a race via the GUI the builder would get called to locate the appropriate node and add the race node. This all got moved to a RaceInfoTreeController as it makes sense in that context.

The RaceInfoTree which is the class that extends TreeViewer and hooks this together with a sub-class of the ContentProvider and LabelProvider also started to grow a few warts. When an action was performed I needed to know what had been selected. This then grew into code for casting this to the right node sub-class and then code for adding new nodes in the right place. This all belongs in the controller so that's where it is all going.

Next

There is still much more to do - updating the dialogs to use View-Models, getting the right-hand side view panels working again with the new tree and so on. Plus I plan to add lots more unit-testing along the way.

Unfortunately my holidays are running out...