Accumulative Runnable in Swing Java

204 Views Asked by At

As part of learning SwingWorker I was going thru the source code where I noticed something called AccumulativeRunnable. From the comment of the AccumulativeRunnable class definition I understand when it is to be used, but when I check the example code few questions came in my mind.

Below is the comment and example code in AccumulativeRunnable abstract class.

An abstract class (AccumulativeRunnable) to be used in the cases where we need to perform some actions on an appendable set of data.
The set of data might be appended after the is sent for the execution. Usually such Runnables are sent to the EDT.

Usage example: Say we want to implement JLabel.setText(String text) which sends string to the JLabel.setTextImpl(String text) on the EDT. In the event JLabel.setText is called rapidly many times off the EDT we will get many updates on the EDT but only the last one is important. (Every next updates overrides the previous one.) We might want to implement this in a way that only the last update is delivered.

  AccumulativeRunnable<String> doSetTextImpl =
  new  AccumulativeRunnable<String>()} {

      protected void run(List<String> args)} {
          //set to the last string being passed
          setTextImpl(args.get(args.size() - 1));
      }
  }

  void setText(String text) {
      //add text and send for the execution if needed.
      doSetTextImpl.add(text);
  }

Questions

  1. The abstract class AccumulativeRunnable implements Runnable. That means AccumulativeRunnable class should implement the run method right?. But I could see just protected abstract void run(List<T> args);. How this could be an implementation of the Runnable interface.
  2. Why the add() method of AccumulativeRunnable class is synchronized ?. Can someone explain this with an easy example or with the example I provided above.
  3. When the arguments inside the add() method of AccumulativeRunnable class will be null ? Can someone explain this with an easy example or with the example I provided above.
  4. How the add() method receives an array (T... args) ?. Can someone explain this with an easy example or with the example I provided above.
  5. In the above example when we call doSetTextImpl.add(text); it calls the add() method of AccumulativeRunnable class. But how this internally calling the run() method?. I mean who is calling our implemented run() method internally.

Entire code in AccumulativeRunnable:

public abstract class AccumulativeRunnable<T> implements Runnable {
    private List<T> arguments = null;

    /**
     * Equivalent to {@code Runnable.run} method with the
     * accumulated arguments to process.
     *
     * @param args accumulated argumets to process.
     */
    protected abstract void run(List<T> args);

    /**
     * {@inheritDoc}
     *
     * <p>
     * This implementation calls {@code run(List<T> args)} mehtod
     * with the list of accumulated arguments.
     */
    public final void run() {
        run(flush());
    }

    /**
     * appends arguments and sends this {@code Runnable} for the
     * execution if needed.
     * <p>
     * This implementation uses {@see #submit} to send this
     * {@code Runnable} for execution.
     * @param args the arguments to accumulate
     */
    @SafeVarargs
    @SuppressWarnings("varargs") // Copying args is safe
    public final synchronized void add(T... args) {
        boolean isSubmitted = true;
        if (arguments == null) {
            isSubmitted = false;
            arguments = new ArrayList<T>();
        }
        Collections.addAll(arguments, args);
        if (!isSubmitted) {
            submit();
        }
    }

    /**
     * Sends this {@code Runnable} for the execution
     *
     * <p>
     * This method is to be executed only from {@code add} method.
     *
     * <p>
     * This implementation uses {@code SwingWorker.invokeLater}.
     */
    protected void submit() {
        SwingUtilities.invokeLater(this);
    }

    /**
     * Returns accumulated arguments and flashes the arguments storage.
     *
     * @return accumulated arguments
     */
    private synchronized List<T> flush() {
        List<T> list = arguments;
        arguments = null;
        return list;
    }
}
1

There are 1 best solutions below

0
Nightara On
  1. The answer is the following implementation of Runnable.run(). From the Compiler's view, run(List<T>) has nothing to do with the method declared by the interface, it's simply a different method with the (coincidentally) same name.

    public final void run() { run(flush()); }

  2. In graphical environments, you have a lot of concurrency, and synchronized prevents the method to be called from two threads at the same time, otherwise you would create a so-called race condition, in which the "faster" thread's update to the list is simply lost. In this specific case this race condition could occur if synchronized was missing from add(T...) and two threads were trying to add the first element to the list at the same time.

  3. Before the first element has been added via add(T). arguments is the list of all operations that have to be executed. If you create a new AccumulativeRunnable<T>, the arguments attribute will be null (See line 2) until the first element has been added.

  4. T... is called a "varargs argument". This is basically just syntactical sugar, and allows you to call add in any of the following ways (For more information feel free to read this):

    1. add(firstObject). This will internally convert the one object you supplied into an array of type T with only one element (firstObject).

    2. add(firstObject, secondObject, thirdObject) and so on, with any number of arguments. All these arguments will be packed into a single array and supplied to the function.

    3. add(objectArray) with objectArray being an actual array of type T. In this case, the internal variable arguments will simply reference the supplied array.

  5. The answer is written in the quote you supplied:

    Usually such Runnables are sent to the EDT.

    EDT = Event Dispatch Thread, a "hidden" thread somewhere deep inside the Swing framework that deals with all the button clicks etc. Things that might trigger the run() method are e.g. a call to frame.paint() (Or however that method is called, I'm using JFX, so I'm not an expert on Swing), a button click, a mouse movement, etc.