Monday, May 17, 2010

How to code with a hedge trimmer

Something you do often in an Eclipse RCP application (because it's a client in some client-server-architecture) is to access a server and do something with a model object.

For instance, you may have a table view that shows a list of customers, represented by objects of type Customer. (Ok, I know it's a lame example.You're invited to use your imagination, if you'd like to see something more colorful ;-)

One of the tasks then is to load all customers from the server and put them as input on the table view. Thus your code would look something like this:

  CustomerService service = getCustomerService();
  List<Customer> customers = service.loadAllCustomers();
  customersTableViewer.setInput( customers );

Code growing out of proportion... 

Of course, it's never quite that easy. Normally, a service call is a long-running operation, so the least you can do is to show a busy indicator to the user. But that requires that you put your code in a Runnable:

  class LoadAllCustomers implements Runnable {
  
    private List<Customer> serverAccessResult;
  
    @Override
    public void run() {
      CustomerService service = getCustomerService();
      serverAccessResult = service.loadAllCustomers();
    }
    
    List<Customer> getServerAccessResult() {
      return serverAccessResult;
    }
  }

and then you can say:

  LoadAllCustomers op = new LoadAllCustomers();
  BusyIndicator.showWhile( op );
  customersTableViewer.setInput( op.getServerAccessResult() );

(Of course, you could do that in an anonymous implementation of Runnable, but bear with me for a moment, you'll see I've used a slightly more elaborate way for didactic reasons ;-)

Sometimes, however, you may expect the server call to run even longer, and therefore you want to shift it into the background entirely, so that the user can continue working until the server access results have arrived:

  class LoadAllCustomers extends Job {
  
    private List<Customer> serverAccessResult;
  
    LoadAllCustomers() {
      super( "Loading customer data" );
    }
  
    @Override
    public IStatus run( IProgressMonitor monitor ) {
      CustomerService service = getCustomerService();
      serverAccessResult = service.loadAllCustomers();
      return Status.OK_STATUS;
    }
    
    List<Customer> getServerAccessResult() {
      return serverAccessResult;
    }
  }

and then:

  Job job = new LoadAllCustomers();
  job.schedule();

... ah, just a second. What now? We could wait for the job to finish by calling join(), but what use would that be? That would destroy the point of putting something into a background job, wouldn't it? So we need a job-changed listener here:

  LoadAllCustomers op = new LoadAllCustomers();
  op.addJobChangeListener( new JobChangeAdapter() {
    @Override
    public void done( IJobChangeEvent event ) {
      LoadAllCustomers loaderJob = ( LoadAllCustomers )event.getJob();
      customersTableViewer.setInput( loaderJob.getServerAccessResult() );
    }
  } );
  op.schedule();


Looks good? No, still no joy: this listener will be called from a thread outside the UI thread, and this will cause an invalid thread access. So this is getting more complicated-looking by the minute:

  LoadAllCustomers op = new LoadAllCustomers();
  op.addJobChangeListener( new IJobChangeListener() {
    @Override
    public void done( IJobChangeEvent event ) {
      LoadAllCustomers loaderJob = ( LoadAllCustomers )event.getJob();
      Display.getDefault().asyncExec( new Runnable() {
        @Override
        public void run() {
          customersTableViewer.setInput( loaderJob.getServerAccessResult() );
        }
      } );
    }
  } );
  op.schedule();

(Of course, not to mention any error handling you also might want to do.)

To summarize: you basically have the same one or two lines of code, but you sometimes need them as a Runnable, sometimes you need them as a Job, and you may or may not have to put the follow-up operation in the UI thread.

... and cutting it back

Here's how I like to organize this a little.

First step: put the server access code for loading all customers and the follow-up code in their own classes.

  class LoadAllCustomers extends Job implements Runnable {
  
    private List<Customer> serverAccessResult;

    LoadAllCustomers() {
      super( "Loading customer data" );
    }
  
    @Override
    public IStatus run( IProgressMonitor monitor ) {
      run(); 
      return Status.OK_STATUS;
    }
  
    @Override
    public void run() {
      CustomerService service = getCustomerService();
      serverAccessResult = service.loadAllCustomers();
    }
    
    List getServerAccessResult() {
      return serverAccessResult;
    }
  }


  
  class SetInputAfterJobDone extends JobChangeAdapter {

    private final Viewer viewer;
    
    SetInputAfterJobDone( Viewer viewer ) {
      this.viewer = viewer;
    }
  
    @Override
    public void done( IJobChangeEvent event ) {
      LoadAllCustomers loaderJob = ( LoadAllCustomers )event.getJob();
      setInputInUIThread( loaderJob.getServerAccessResult() );
    }

    private void setInputInUIThread( Object input ) {
      Display.getDefault().asyncExec( new Runnable() {
        @Override
        public void run() {
          viewer.setInput( input );
        }
      } );
    }  
  }

(You can see now what I meant by 'didactic reason' a few lines above.)

You can now use your server access code both as Runnable or as Job:

  LoadAllCustomers op = new LoadAllCustomers();
  BusyIndicator.showWhile( op );
  customersTableViewer.setInput( op.getServerAccessResult() );

  LoadAllCustomers op = new LoadAllCustomers();
  op.addJobChangeListener( new SetInputAfterJobDone( customersTableViewer ) );
  op.schedule();

Second, I generally allow a follow-up behavior, if we're running as a job, by adding this method to LoadAllCustomers:

  public Job andThen( IJobChangeListener followUpBehavior ) {
    addJobChangeListener( followUpBehavior );
    return this;
  }

  So that I can write more compactly:

  new LoadAllCustomers().andThen( new SetInputAfterJobDone( customersTableViewer ) ).schedule();

Finally, pull up some of the common stuff in the server access code, so that you can re-use it for all other sorts of server accesses (like saving customers, querying customer details, and so on):

  public abstract class ServerAccess<T> extends Job implements Runnable {

    private T serverAccessResult;

    public ServerAccess( String name ) {
      super( name );
    }

    @Override
    protected IStatus run( IProgressMonitor monitor ) {
      run();
      return Status.OK_STATUS;
    }

    public T getServerAccessResult() {
      return serverAccessResult;
    }

    public ServerAccess<T> andThen( IJobChangeListener followUpBehavior ) {
      addJobChangeListener( followUpBehavior );
      return this;
    }

    protected void setServerAccessResult( T computationResult ) {
        this.serverAccessResult = computationResult;
    }
  }

Subclasses then merely have to implement the run() method with their own server access logic, and set the result. That should usually be a one-liner, and as you've seen, using these server access objects also normally is. (Correspondingly, generalize SetInputAfterJobDone so that it can cope with ServerAccess objects instead of simple Jobs).

So, here's one exercise left for the interested reader:

  new TryItInYourNext( project ).andThen( new GoAndHaveSome( coffee ) ).schedule();

Tell me how it worked :-)

No comments:

Post a Comment