I’m using SwingWorker
My code contains several abstractions to handle the flow correctly
- AbstractOperationsManager – contains common code for each Operations manager
- Initializer – a concrete implementation of operation manager in the flow
- JProgressBarConsumer – a wrapper for abstracting routines related to UI from operations manager implementation
- OperationObserver – handling operation event updates
- OperationBroker – a mediator between OperationManager and OperationObserver
package org.app.core.api;
import java.util.Iterator;
import ...
public abstract class AbstractOperationManager extends SwingWorker<Void, Void> {
private OperationBroker broker;
private final Logger log = LoggerFactory.getLogger(AbstractOperationManager.class);
private final List<UIConsumer> uiconsumer;
private volatile boolean cancelled;
public AbstractOperationManager() {
super();
this.uiconsumer = new LinkedList<>();
this.cancelled = false;
}
public abstract void init(OperationBroker broker);
public void addOperationsBroker(OperationBroker broker) {
this.broker = broker;
}
public void addUIConsumer(UIConsumer consumer) {
this.uiconsumer.add(consumer);
}
/**
* customized method to inform observers of implicit cancellation of
* operations because of either exception or for intentional purposes
*
* @return boolean
*/
public boolean isOperationCancelled() {
return cancelled;
}
@Override
protected void process(List<Void> chunks) {
while (true) {
OperationEvent latestE = this.broker.getObserver().getLatestEvent();
if (latestE.getOperationType().equals(Operation.OPERATION_TYPE.TERMINATE)) {
OperationTerminationEvent ote = (OperationTerminationEvent) latestE;
if (ote.isExecThrown()) {
//log for exception
cancel(true);
cancelled = true;
}
break;
} else if (latestE.getOperationType().equals(Operation.OPERATION_TYPE.LOG)) {
//perform logging operations
} else {
SwingUtilities.invokeLater(()-> this.uiconsumer
.forEach(consumer-> consumer
.consumeEvent(latestE)));
}
}
}
@Override
protected Void doInBackground() throws Exception {
publish();
broker.operate();
return null;
}
}
The key execution point which results in updating the GUI is the iterator in uiconsumer
. It’s an abstraction for a specific UI component to handle its flow and in this case, JProgressBar
.
One reason for adding a bogus publish()
call is to trigger the chunk processing and it is the broker who performs the operations and supplies the updates for the individual operations carried out.
package org.app.util;
import org.app.core.api.OperationEvent;
import org.app.core.api.OperationObserver;
public class SystemOperationObserver implements OperationObserver {
private OperationEvent event;
@Override
public void notifyObserver(OperationEvent event) {
try {
eventQueue.put(event);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
@Override
public int queuedCount() {
return eventQueue.size();
}
@Override
public OperationEvent getLatestEvent() {
OperationEvent event = null;
try {
event = eventQueue.take();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
return event;
}
}
OperationObserver is the event supplier which contains a LinkedBlockingQueue<T>
for carrying out updates from several threads in OperationManager. Each and every operation holds access to this observer
package org.app.views.components;
import java.util.concurrent.TimeUnit;
import ...
public class JProgressBarConsumer implements UIConsumer {
private JProgressBar progress;
private Predicate<OperationEvent> predicate;
public JProgressBarConsumer(JProgressBar progress) {
this.progress = progress;
this.progress.setStringPainted(true);
this.progress.setValue(0);
}
private boolean filterEvent(OperationEvent event) {
if (this.predicate != null) {
return this.predicate.test(event);
}
return true;
}
@Override
public void on(Predicate<OperationEvent> eventFilter) {
this.predicate = eventFilter;
}
@Override
public void consumeEvent(OperationEvent event) {
boolean shouldContinue = filterEvent(event);
if (shouldContinue) {
int currentProgress = this.progress.getValue();
int percentage = (int) event.getPercentage() + currentProgress;
this.progress.setValue(percentage);
this.progress.repaint();
try {
TimeUnit.MILLISECONDS.sleep(60);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
package org.app.util;
import java.util.concurrent.CountDownLatch;
import ...
public class Initializer extends AbstractOperationManager {
private CountDownLatch hook;
public Initializer() {
super();
hook = new CountDownLatch(1);
}
@Override
public void init(OperationBroker broker) {
addOperationsBroker(broker);
}
public CountDownLatch getHook() {
return hook;
}
@Override
protected void done() {
boolean isOpcancelled = this.isOperationCancelled();
if (!isOpcancelled) {
hook.countDown();
}
}
}
Scenario
The code should update the JProgressBar visually but what it really does is update only the last event progress (which the value is 100) and ignore the rest.
One reason could be that there should be a greater interval between UI events consumed by the JProgressBarConsumer
TimeUnit.MILLISECONDS.sleep(60);
Another reason could be the code block the EDT between the operations because of LinkedBlockingQueue
behavior until the Latch release the control
Questions
- What could really be the cause of not updating JPogressBar?
- The
cancel(true)
in AbstractOperationsManager isn’t working properly (It’s a problem withFuture
of course but please shed some light on that) and had to introduce a breaker manually. I did tons of research to understand theFuture
behavior and its cancel process and still no luck.