ProgressWorker provides a fluent API for constructing background task workers for a variety of task types.

Note
ProgressWorker instances can not be reused. Tasks, on the other hand, can be made stateful and reusable if required.

1. Task

// A non-progress aware task, producing no result
ProgressWorker.Task task = () -> {
  // Perform the task
};

ProgressWorker.builder()
        .task(task)
        .onException(exception ->
                Dialogs.exception()
                        .owner(applicationFrame)
                        .show(exception))
        .execute();

2. TaskHandler

// TaskHandler encapsulates the task and its handlers in a single class.
// Handler interface methods are called first, followed by
// any handlers added via the builder, in the order they were added.
// This enables a layered approach where the handler interface
// handles model-level concerns (logging, state updates) while
// builder handlers handle UI-level concerns (displaying dialogs).
ProgressWorker.TaskHandler task = new TaskHandler() {

  @Override
  public void execute() throws Exception {
    // Perform the task
  }

  // Called first on exception: log the error (model-level)
  @Override
  public void onException(Exception exception) {
    LOG.log(Level.WARNING, exception.getMessage());
  }
};

ProgressWorker.builder()
        .task(task)
        // Called after the handler's onException: display the error (UI-level)
        .onException(exception -> Dialogs.exception()
                .owner(applicationFrame)
                .show(exception))
        .execute();

3. ResultTask

// A non-progress aware task, producing a result
ProgressWorker.ResultTask<String> task = () -> {
  // Perform the task
  return "Result";
};

ProgressWorker.builder()
        .task(task)
        .onResult(result ->
                showMessageDialog(applicationFrame, result))
        .onException(exception ->
                Dialogs.exception()
                        .owner(applicationFrame)
                        .show(exception))
        .execute();

4. ResultTaskHandler

// ResultTaskHandler encapsulates a result-producing task and its handlers.
// The handler's onResult and onException are called first (model-level),
// then the builder's handlers are called after (UI-level).
ResultTaskHandler<String> task = new ResultTaskHandler<String>() {

  @Override
  public String execute() throws Exception {
    // Perform the task
    return "Result";
  }

  // Called first on success: log the result (model-level)
  @Override
  public void onResult(String result) {
    LOG.log(Level.INFO, result);
  }

  // Called first on exception: log the error (model-level)
  @Override
  public void onException(Exception exception) {
    LOG.log(Level.WARNING, exception.getMessage());
  }
};

ProgressWorker.builder()
        .task(task)
        // Called after the handler's onResult: display the result (UI-level)
        .onResult(result -> showMessageDialog(applicationFrame, result))
        // Called after the handler's onException: display the error (UI-level)
        .onException(exception -> Dialogs.exception()
                .owner(applicationFrame)
                .show(exception))
        .execute();

5. ProgressTask

// A progress aware task, producing no result
ProgressWorker.ProgressTask<String> task = progressReporter -> {
  // Perform the task
  progressReporter.report(42);
  progressReporter.publish("Message");
};

ProgressWorker.builder()
        .task(task)
        .onProgress(progress ->
                System.out.println("Progress: " + progress))
        .onPublish(message ->
                showMessageDialog(applicationFrame, message))
        .onException(exception ->
                Dialogs.exception()
                        .owner(applicationFrame)
                        .show(exception))
        .execute();

6. ProgressTaskHandler

// ProgressTaskHandler encapsulates a progress-aware task and its handlers.
// The handler's methods are called first (model-level),
// then the builder's handlers are called after (UI-level).
ProgressTaskHandler<String> task = new ProgressTaskHandler<String>() {

  @Override
  public void execute(ProgressReporter<String> progressReporter) throws Exception {
    // Perform the task
    for (int i = 0; i < maximum(); i++) {
      progressReporter.report(i);
      progressReporter.publish("Message " + i);
    }
  }

  @Override
  public void onProgress(int progress) {
    System.out.println("Progress: " + progress);
  }

  @Override
  public void onPublish(List<String> message) {
    displayMessage(message);
  }

  // Called first on exception: log the error (model-level)
  @Override
  public void onException(Exception exception) {
    LOG.log(Level.WARNING, exception.getMessage());
  }
};

ProgressWorker.builder()
        .task(task)
        // Called after the handler's onException: display the error (UI-level)
        .onException(exception -> Dialogs.exception()
                .owner(applicationFrame)
                .show(exception))
        .execute();

7. ProgressResultTask

// A reusable, cancellable task, producing a result.
// Displays a progress bar in a dialog while running.
var task = new DemoProgressResultTask();

ProgressWorker.builder()
        .task(task.prepare(142))
        .execute();
static final class DemoProgressResultTask implements ProgressResultTaskHandler<Integer, String> {

  private final JProgressBar progressBar = progressBar()
          .indeterminate(false)
          .stringPainted(true)
          .string("")
          .build();
  // Indicates whether the task has been cancelled
  private final AtomicBoolean cancelled = new AtomicBoolean();
  // A Control for setting the cancelled state
  private final Control cancel = Control.builder()
          .command(() -> cancelled.set(true))
          .caption("Cancel")
          .mnemonic('C')
          .build();
  // A panel containing the progress bar and cancel button
  private final JPanel progressPanel = borderLayoutPanel()
          .center(progressBar)
          .east(button()
                  .control(cancel))
          .build();
  // The dialog displaying the progress panel
  private final JDialog dialog = Dialogs.builder()
          .component(progressPanel)
          .owner(applicationFrame)
          // Trigger the cancel control with the Escape key
          .keyEvent(KeyEvents.builder()
                  .keyCode(VK_ESCAPE)
                  .action(cancel))
          // Prevent the dialog from closing on Escape
          .disposeOnEscape(false)
          .build();

  private int taskSize;

  @Override
  public Integer execute(ProgressReporter<String> progressReporter) throws Exception {
    List<Integer> result = new ArrayList<>();
    for (int i = 0; i < taskSize; i++) {
      Thread.sleep(50);
      if (cancelled.get()) {
        throw new CancelException();
      }
      result.add(i);
      reportProgress(progressReporter, i);
    }

    return result.stream()
            .mapToInt(Integer::intValue)
            .sum();
  }

  @Override
  public int maximum() {
    return taskSize;
  }

  @Override
  public void onStarted() {
    dialog.setVisible(true);
  }

  @Override
  public void onProgress(int progress) {
    progressBar.setValue(progress);
  }

  @Override
  public void onPublish(List<String> strings) {
    progressBar.setString(strings.get(0));
  }

  @Override
  public void onDone() {
    dialog.setVisible(false);
  }

  @Override
  public void onCancelled() {
    showMessageDialog(applicationFrame, "Cancelled");
  }

  @Override
  public void onException(Exception exception) {
    Dialogs.exception()
            .owner(applicationFrame)
            .show(exception);
  }

  @Override
  public void onResult(Integer result) {
    showMessageDialog(applicationFrame, "Result : " + result);
  }

  // Makes this task reusable by resetting the internal state
  private DemoProgressResultTask prepare(int taskSize) {
    this.taskSize = taskSize;
    progressBar.getModel().setMaximum(taskSize);
    cancelled.set(false);

    return this;
  }

  private void reportProgress(ProgressReporter<String> reporter, int progress) {
    reporter.report(progress);
    if (progress < taskSize * 0.5) {
      reporter.publish("Going strong");
    }
    else if (progress > taskSize * 0.5 && progress < taskSize * 0.85) {
      reporter.publish("Half way there");
    }
    else if (progress > taskSize * 0.85) {
      reporter.publish("Almost done");
    }
  }
}