org.nuiton.jaxx.runtime.swing.application.ActionExecutor Maven / Gradle / Ivy
/*
* #%L
* JAXX :: Runtime
* %%
* Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package org.nuiton.jaxx.runtime.swing.application;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuiton.jaxx.runtime.swing.application.event.ActionExecutorEvent;
import org.nuiton.jaxx.runtime.swing.application.event.ActionExecutorListener;
import org.nuiton.jaxx.runtime.util.ReflectUtil;
import javax.swing.SwingWorker;
import javax.swing.event.EventListenerList;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
/**
* Executor of {@link ActionWorker}.
*
* @author Tony Chemit - [email protected]
* @since 2.1
*/
public class ActionExecutor {
private static final Logger log = LogManager.getLogger(ActionExecutor.class);
/**
* Cuurrent tasks.
*/
protected final Set> tasks;
/**
* The listener of running action.
*/
protected final PropertyChangeListener workerListener;
/**
* Event listener list.
*/
private final transient EventListenerList listenerList;
public ActionExecutor() {
this.tasks = new HashSet<>();
this.listenerList = new EventListenerList();
this.workerListener = evt -> {
log.debug(String.format("action %s property %s changed <%s - %s>", evt.getSource(), evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()));
if ("state".equals(evt.getPropertyName())) {
ActionWorker, ?> source = (ActionWorker, ?>) evt.getSource();
SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue();
if (state == SwingWorker.StateValue.STARTED) {
// starting new action
onActionStart(source);
return;
}
if (state == SwingWorker.StateValue.DONE) {
// on rend la main au thread pour qu'il attende une prochaine operation
ActionWorker.ActionStatus status = source.getStatus();
log.debug(String.format("Action [%s] status = %s", source.getActionLabel(), status));
try {
switch (status) {
case OK:
onActionEnd(source);
break;
case CANCEL:
onActionCancel(source);
break;
case FAIL:
onActionFail(source);
break;
}
} finally {
tasks.remove(source);
onAfterAction(source);
}
}
}
};
}
/**
* Add an new action to perform.
*
* @param actionLabel the name of the action to perform
* @param action the action to perform
* @return the worker that will launch the action
*/
public ActionWorker, ?> addAction(String actionLabel, Runnable action) {
ActionWorker, ?> worker;
if (action instanceof ActionWorker) {
worker = (ActionWorker, ?>) action;
} else {
worker = new ActionWorker<>(actionLabel, action);
}
worker.addPropertyChangeListener(workerListener);
tasks.add(worker);
executeWorker(actionLabel, worker);
return worker;
}
/**
* Add an new worker to perform.
*
* @param worker the worker to run
*/
public void addAction(ActionWorker, ?> worker) {
addAction(worker.getActionLabel(), worker);
}
/**
* Hook when a action is about to start.
*
* @param source the action worker containing the action to perform
*/
public void onActionStart(ActionWorker, ?> source) {
fireActionStart(source);
}
/**
* Hook when a action has failed.
*
* @param source the action worker containing the action to perform
*/
public void onActionFail(ActionWorker, ?> source) {
fireActionFail(source);
}
/**
* Hook when a action has been canceled.
*
* @param source the action worker containing the action to perform
*/
public void onActionCancel(ActionWorker, ?> source) {
fireActionCancel(source);
}
/**
* Hook when a action has end with no failure or cancel.
*
* @param source the action worker containing the action to perform
*/
public void onActionEnd(ActionWorker, ?> source) {
fireActionEnd(source);
}
/**
* Hook after action is consumed.
*
* @param source the action worker containing the action to perform
*/
public void onAfterAction(ActionWorker, ?> source) {
fireActionDone(source);
}
/**
* Ask the thread to stop.
*
* It will finish all incoming files (but will not accept more tasks).
*
* Note: The method does not return until all tasks are not
* consumed.
*/
public void terminatesAndWaits() {
log.debug(String.format("Executor %s is terminating...", this));
// ask executor to terminate
for (ActionWorker, ?> task : tasks) {
task.cancel(true);
}
log.debug(String.format("Executor %s was terminated at %s", this, new Date()));
}
public void addActionExecutorListener(ActionExecutorListener listener) {
log.info(String.format("adding listener %s", listener));
listenerList.add(ActionExecutorListener.class, listener);
}
public void removeActionExecutorListener(ActionExecutorListener listener) {
log.info(String.format("removing listener %s", listener));
listenerList.remove(ActionExecutorListener.class, listener);
}
protected ActionExecutorListener[] getActionExecutorListener() {
return listenerList.getListeners(ActionExecutorListener.class);
}
protected void fireActionStart(ActionWorker, ?> source) {
fireAction(source, ActionExecutorListener::actionStart);
}
protected void fireActionFail(ActionWorker, ?> source) {
fireAction(source, ActionExecutorListener::actionFail);
}
protected void fireActionCancel(ActionWorker, ?> source) {
fireAction(source, ActionExecutorListener::actionCancel);
}
protected void fireActionEnd(ActionWorker, ?> source) {
fireAction(source, ActionExecutorListener::actionEnd);
}
protected void fireActionDone(ActionWorker, ?> worker) {
fireAction(worker, ActionExecutorListener::actionDone);
}
protected void fireAction(ActionWorker, ?> worker, BiConsumer function) {
ActionExecutorEvent evt = new ActionExecutorEvent(this, worker);
for (ActionExecutorListener listener : getActionExecutorListener()) {
function.accept(listener, evt);
if (evt.isConsumed()) {
break;
}
}
}
public int getNbActions() {
return getTasks().size();
}
public Set> getTasks() {
return tasks;
}
/**
* Creates a runnable instance (via a Proxy) to a method given by his name
* ({@code methodName}) to invoke on {@code methodcontainer} with given
* {@code arguments}.
*
* This is a great feature to create runnable code with a real context.
*
* @param methodContainer the container of the method to invoke
* @param methodName the name of the method to invoke
* @param arguments parameters to pass to method to invke.
* @return the proxy instance
*/
public Runnable createRunnable(final Object methodContainer,
String methodName,
final Object... arguments) {
// find method
Class> klass = methodContainer.getClass();
Method targetMethod = ReflectUtil.getDeclaredMethod(klass, methodName, true, arguments);
targetMethod.setAccessible(true);
Runnable result;
// create runnable proxy
result = (Runnable) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class>[]{Runnable.class},
(proxy, method, args) -> {
String methodName1 = method.getName();
if ("run".equals(methodName1)) {
try {
if (log.isDebugEnabled()) {
log.debug("will invoke run method");
}
return targetMethod.invoke(methodContainer, arguments);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"could not invoke on container " +
methodContainer, e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
if (methodName1.equals("toString")) {
return toString();
}
if (methodName1.equals("equals")) {
return equals(args[0]);
}
if (methodName1.equals("hashCode")) {
return hashCode();
}
return null;
}
);
return result;
}
/**
* Execute worker once it was added to tasks.
*
* This method permits to override how to execute it.
*
* @param actionLabel action label
* @param worker worker to execute
*/
protected void executeWorker(String actionLabel, ActionWorker, ?> worker) {
worker.execute();
}
}