Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Automate dependency tracking, Part 2

Automatic dependency tracking discovers dependencies at runtime and updates the user interface

  • Print
  • Feedback
Rube Goldberg, an award-winning cartoonist, drew incredibly complex contraptions that performed simple tasks. He described his machines as "symbols of man's capacity for exerting maximum effort to accomplish minimal results." Indeed, when we maintain software, we often feel trapped in one of his creations, tracing the path of the gloved hand to the egg-laying hen, to the shooting rocket, to the swinging clock pendulum. With automatic dependency tracking, you can break the complex chain of events so that the software updates itself.

In Part 1 of this series, I laid the groundwork for an application based on automatic dependency tracking. The first step was to design and build the information model (IM). The IM records all the system's logic; it is designed such that a client knowledgeable in the problem domain can build, traverse, and apply a solution. In this installment, I will construct a user interface (UI) that automatically discovers dependencies upon the IM and updates itself.

Read the whole series on automatic dependency tracking:



The application that we started in Part 1 is Nebula, a network design tool. Nebula's information model records the connections among various network devices. While building the IM, we inserted dynamic sentries to monitor access and modification of dynamic data. While constructing the UI, we will insert dependent sentries to react to changes in the IM.

We use automatic dependency tracking to simplify application development and maintenance. When we work with a program that discovers dependencies and updates attributes automatically, we can focus on application logic, not housekeeping. The program can also respond to changes in object dependency as we update the program, meaning that as the problem changes, the solution changes. We don't have to go back and revisit old dependencies as we add new features.

To achieve these worthwhile benefits, we must adopt a few disciplines. The following six guidelines will help us construct a user interface based on automatic dependency tracking:

  1. Specify the update method for each dependent attribute in an anonymous inner class
  2. Call a dependent sentry's onGet() method to ensure that a dependent attribute is up to date
  3. Use intermediate layers to project UI-specific information onto the IM
  4. Recycle objects in dependent collections
  5. Model actions that don't cause changes as dependent state
  6. Model actions that do cause changes as events


Specify an update method

To use automatic dependency tracking, you simply express the calculation of each dependent attribute. The sentries determine when each calculation should occur. To express a dependent attribute's calculation, supply a dependent sentry with an update method in the form of an anonymous inner class. The update method gathers all required information, performs the necessary calculations, and records the dependent attribute's new value. As the application developer, you never call the update method directly. The dependent sentry calls the update methods when the dependent attribute needs updating. You pass the update methods into the dependent sentry's constructor as an implementation of the IUpdate interface.

Take, for example, the update method for a graphic hop's bounding rectangle. A graphic hop, represented by the GraphicHop class, displays the IP address of a network interface card or router port near the attached device. To do its job, it must calculate the position on the screen where the text will appear. The text's bounding rectangle is a dependent attribute, as it is calculated based on several pieces of information: the IP address text itself, the associated device's location, and the cable's direction. To calculate the bounding rectangle, we find the attached device's location, go a fixed distance along the cable toward the remote device, and anchor one corner of the rectangle surrounding the text. The update method below performs these calculations:

    private Dependent m_depBounds = new Dependent( new IUpdate()
    {
        public void onUpdate()
        {
            IGraphicsInfo info = getGraphicsInfo();
            // Get the size of the text.
            String strAddress = m_hop.getAddress().toString();
            Dimension dimAddress = info.getStringSize( strAddress, g_font 
);
            info.dispose();
            // Find the location of the device and the angle of the cable.
            Point ptAnchor;
            Point ptRemote;
            if ( m_hop.getDevice() == m_locatedCable.getFrom().getDevice() 
)
            {
                ptAnchor = m_locatedCable.getFrom().getLocation();
                ptRemote = m_locatedCable.getTo().getLocation();
            }
            else
            {
                ptAnchor = m_locatedCable.getTo().getLocation();
                ptRemote = m_locatedCable.getFrom().getLocation();
            }
            Dimension dimCable = new Dimension( ptRemote.x - ptAnchor.x, 
ptRemote.y - ptAnchor.y );
            if ( dimCable.width == 0 && dimCable.height == 0 )
                dimCable.height = -1;
            // Find the point a fixed distance from the device along the 
cable.
            double dFactor = 20.0 /
                Math.sqrt(
                    dimCable.width*dimCable.width +
                    dimCable.height*dimCable.height );
            ptAnchor.x += (double)dimCable.width * dFactor;
            ptAnchor.y += (double)dimCable.height * dFactor;
            // Put one corner of the rectangle on that point.
            m_rectBounds.setLocation( ptAnchor );
            m_rectBounds.setSize( dimAddress );
            if ( dimCable.width < 0 )
                m_rectBounds.translate( -m_rectBounds.width, 0 );
            if ( dimCable.height > 0 )
                m_rectBounds.translate( 0, -m_rectBounds.height );
        }
    } );


Each GraphicHop object creates a Dependent sentry, m_depBounds, and gives it an IUpdate interface implementation. The anonymous inner class implements one method, onUpdate(), which gathers all the required information, calculates the bounding rectangle, and stores the result in m_rectBounds.

Ensure up-to-date dependent attributes

The Dependent sentry m_depBounds invokes the update method whenever the dependent attribute m_rectBounds needs recalculating. Any code that accesses the attribute must therefore call m_depBounds.onGet() before obtaining m_rectBounds's value. Since the update method only invokes if the attribute is out of date, onGet() ensures that the attribute is up to date.

To guarantee that onGet() is called prior to each access of m_rectBounds, the GraphicHop class defines the private method getBounds(), which appears below. Any method that requires the bounding rectangle calls getBounds() rather than accessing m_rectBounds directly, thus ensuring that the dynamic sentry is notified:

  • Print
  • Feedback

Resources