ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

BlackMamba: A Swing Case Study
Pages: 1, 2, 3, 4, 5

Asynchronous Processing

With the enormous power offered by Threads comes a vexing problem. It is not easy for these methods to return results because the invoker has already returned. The only solution is to have an object on which the Thread can invoke callback methods to convey the results of the asynchronous execution.



This presents an interesting design problem: Class Worker1 submits jobs to the ThreadPool class. For the ThreadPool to convey the results of the execution, it invokes a done(boolean) method on Worker1. The done() method returns false to indicate failure and true for success. Sometime later another class Worker2 decides to use asynchronous processing. For ThreadPool to invoke a callback method on Worker2 say, completed(boolean), it has to differentiate between Worker1 and Worker2.

If another class Worker3 comes up in the future, then the list of methods ThreadPool should invoke has to change again. ThreadPool is now subject to constant change and hence is not a stable class. It requires frequent changes and compilations. To alleviate this problem, we will use an interface called Callback, which will be implemented by all users of ThreadPool. This way ThreadPool becomes stable.

The principle employed here is called Dependency Inversion (DIP), which advocates a dependence on Abstractions and not Concretions. Interfaces and Abstract classes are usually stable; implementations and subclasses usually are not.

public interface Callback
{
    public void call();
}

Figure 2
Figure 2. Too much direct dependence on Concrete classes.

Figure 3
Figure 3. Dependence on a more stable Interface resulting from Inversion.

The next problem is to find a package in which to place the Callback interface. blackmamba.ui.control.mail.Fetch submits the mail to blackmamba.helpers.mail.MailProcessor for asynchronous processing. MailProcessor adds the processed mail to blackmamba.helpers.ui.CustomTableModel. When a new mail is added to the model, the view has to be updated. And we have decided at the very beginning that view classes can only be accessed from control classes. So, CustomTableModel must notify blackmamba.ui.control.mail.Mails when a new mail is added so that Mails can make changes to the UI, such as incrementing and displaying the number of mails that have arrived. Our first choice would be to place Callback in blackmamba.ui.mail.control. Let's look at the dependency here: blackmamba.ui.control.mail.Fetch -> blackmamba.helpers.mail.MailProcessor -> blackmamba.helpers.ui.CustomTableModel -> blackmamba.ui.control.mail.Callback <- blackmamba.ui.control.mail.Mails.

The arrow indicates the direction of dependency. There is a cyclic dependency here, which starts and ends at the blackmamba.ui.control.mail package. Because of the cycle, the packages involved in the cycle are forced to be released and reused together. The only way to break the cycle is to move Callback to another package blackmamba.helpers.Callback. A tool like Compuware's OptimalAdvisor can be used to explore package design, dependencies, and usages.

There will always be certain utility packages like blackmamba.helpers and blackmamba.data that get tangled up in cycles. The effort required to enforce DIP on these classes is just not always justifiable. So it is better to just let them be. Figures 4 and 5 show the resulting acyclic dependencies in the sub-packages of blackmamba.ui and blackmamba respectively. The numbers on the arrows describe the number of dependencies between the packages. There are 126 dependencies from helpers to data and 4 in the opposite direction.

Figure 4
Figure 4. Acyclic dependencies in the blackmamba.ui package.

Figure 5
Figure 5. Acyclic dependencies in the blackmamba package.

Everytime the user clicks the Fetch button, new mails are read from the server. These mails are read by Thread2 from the ThreadPool and the UI is updated in a way very similar to the Connect class. This Thread sends the mails to the MailProcessor, which stores them in an internal Queue. MailProcessor uses Thread1 from the ThreadPool to pick up mails from the Queue and classify them. Everytime a mail is processed, it is added to the appropriate MailList, which as you'll notice, is implemented by the CustomTableModel. The interface is used here because MailProcessor does not have to know about other methods in the CustomTableModel.

public class Fetch
{
    public void init(MambaFrm frm, MailsPnl pnl, 
                                      boolean all)
    {
        ...

        final Runnable target = new Runnable()
        {
            public void run()
            {
                fetch();
            }
        };
        ...
        ...
        LiteThreadPool threadPool = 
                       Setup.getLiteThreadPool();
        PoolThread thread1 = 
                         threadPool.getThread1();
        thread1.execute(target);
        ...
    }
        
    ...
    protected void fetch()
    {
        ...
        MailProcessor mailProcessor = 
                         Setup.getMailProcessor();
        ...
        mailProcessor.process(mailDetails);
        ...
    }
}

public class MailProcessor
{
    private MailList mails;
    private MailList possibleSpamMails;
    private MailList spamMails;
    ...
    ...
    
    public void classify(MailDetails mail, 
                            boolean addToMailList)
    {
        ...
        switch (mail.getSenderStatus())
        {
            case MailDetails.SENDER_KNOWN :
                ...
                possibleSpamMails.addMail(mail);
                break;
            case MailDetails.SENDER_UNKNOWN :
                ...
                possibleSpamMails.addMail(mail);
                break;
            case MailDetails.SENDER_SPAMMER :
                ...
                spamMails.addMail(mail);
                break;
        }
        ...
    }
} 

public class CustomTableModel extends 
            AbstractTableModel implements MailList
{
    private Callback onMailAdd;
    ...
    public void addMail(MailDetails mailDetails)
    {
        ...
        fireTableRowsInserted(index, index);

        onMailAdd.call();
    }
}

I generally use different Callbacks for different outcomes such as onSuccess and onFailure. The Callback interface can be refactored to send a List of Exceptions or messages as a parameter in the call(...) method. If the parameter is null then it can be assumed to have succeeded.

The code below shows how the main Mails class has separate Callbacks for each category of mails. They are invoked by their respective CustomTableModels as seen in the code above, whenever a mail gets added or deleted.

public class Mails
{
    private final Callback onMailAdd = 
    new Callback()
    {
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                //Update Statistics here.
                ...
            }
        };

        public void call()
        {
            SwingUtilities.invokeLater(runnable);
        }
    };

    private final Callback onPossSpamMailAdd = 
    ...
    ...
    private final Callback onSpamMailAdd =
    ...
    ...
    
    protected void prepareMailTables(
                                  boolean refresh)
    {
       ...

       //Mails table
       mailsTableModel = 
                  new CustomTableModel(onMailAdd);
       ...
        
       //Hookup MailProcessor and CustomTableModel.
       //CustomTableModel implements MailList 
       //interface.
       //MailProcessor adds processed mail to the 
       //Model using only the interface.
       MailProcessor mailProcessor = 
                         Setup.getMailProcessor();
        ...
       mailProcessor.setMails(mailsTableModel);
       mailProcessor.setPossibleSpamMails(
                              possSpamTableModel);
       mailProcessor.setSpamMails(
                                  spamTableModel);
    }
}

Pages: 1, 2, 3, 4, 5

Next Pagearrow