All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaxx.runtime.swing.AbstractActionThread Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-6
Show newest version
package jaxx.runtime.swing;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.util.StringUtil;

import javax.swing.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * An abstract action thread to consume actions in a non Swing event thread.
 * 

* Implements the method {@code onActionXXX(ActionWorker)} to hook on action * status. *

* To consume an action, use the method {@link #addAction(String, Runnable)}. *

* TODO Make this multi-action enable * * @author tchemit * @since 2.0 */ public abstract class AbstractActionThread extends Thread { /** Logger */ private static final Log log = LogFactory.getLog(AbstractActionThread.class); /** l'état du thread si annulé */ private boolean canceled; /** * un lock pour permettre la suspension et la reprise du thread lors du mode * interactif. */ private final Object LOCK = new Object(); /** current worker to execute action */ protected ActionWorker worker; /** the listener of running action */ protected final PropertyChangeListener workerListener; /** State of a running action */ enum ActionStatus { OK, CANCEL, FAIL } /** Action worker to execute a incoming action. */ public static class ActionWorker extends SwingWorker { protected final String actionLabel; protected final Runnable target; protected ActionStatus status; protected Exception error; protected long startTime; protected long endTime; public ActionWorker(String actionLabel, Runnable target) { this.target = target; this.actionLabel = actionLabel; } @Override protected Void doInBackground() throws Exception { startTime = System.nanoTime(); if (log.isDebugEnabled()) { log.debug("Action [" + getActionLabel() + "] is starting..."); } try { target.run(); } catch (Exception e) { error = e; } finally { if (log.isDebugEnabled()) { log.debug("Action [" + getActionLabel() + "] is ending..."); } } return null; } public boolean isFailed() { return (isDone() || isCancelled()) && error != null; } public Exception getError() { return error; } public ActionStatus getStatus() { return status; } public String getActionLabel() { return actionLabel; } @Override protected void done() { super.done(); endTime = System.nanoTime(); if (error != null) { status = ActionStatus.FAIL; } else if (isCancelled()) { status = ActionStatus.CANCEL; } else { status = ActionStatus.OK; } if (log.isDebugEnabled()) { log.debug("Action [" + getActionLabel() + "] ends with status : " + status + " in " + getTime()); } } public String getTime() { return StringUtil.convertTime(endTime - startTime); } public long getStartTime() { return startTime; } public long getEndTime() { return endTime; } } protected AbstractActionThread(String name) { super(name); workerListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (log.isDebugEnabled()) { log.debug("action " + evt.getSource() + " property " + evt.getPropertyName() + " changed <" + 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 ActionStatus status = source.getStatus(); if (log.isDebugEnabled()) { log.debug("Action [" + source.getActionLabel() + "] status = " + status); } try { switch (status) { case OK: onActionEnd(source); break; case CANCEL: onActionCancel(source); break; case FAIL: onActionFail(source); break; } } finally { // release thread setWaiting(false); } } } } }; } /** * 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 mFound = null; for (Method m : klass.getDeclaredMethods()) { if (!methodName.equals(m.getName())) { continue; } //same method name Class[] types = m.getParameterTypes(); if (arguments.length != types.length) { continue; } // same number arguments mFound = m; break; } if (mFound == null) { throw new IllegalArgumentException( "could not find method " + methodName + " on type " + klass.getName()); } //TODO Test arguments are on good type... final Method targetMethod = mFound; targetMethod.setAccessible(true); Runnable result; // create runnable proxy result = (Runnable) Proxy.newProxyInstance( getClass().getClassLoader(), new Class[]{Runnable.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) { String methodName = method.getName(); if ("run".equals(methodName)) { 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 (methodName.equals("toString")) { return toString(); } if (methodName.equals("equals")) { return equals(args[0]); } if (methodName.equals("hashCode")) { return hashCode(); } return null; } } ); return result; } /** * 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) { if (worker != null && !worker.isDone()) { // on ne peut traiter qu'une seule opération à la fois throw new IllegalStateException( "can not add a operation when thread is busy, or has" + " another operation to be done"); } if (action instanceof ActionWorker) { worker = (ActionWorker) action; } else { worker = new ActionWorker(actionLabel, action); } // on libere le thread pour qu'il execute l'opération setWaiting(false); return worker; } /** * Hook when a action is about to start. * * @param source the action worker containing the action to perform */ public abstract void onActionStart(ActionWorker source); /** * Hook when a action has failed. * * @param source the action worker containing the action to perform */ public abstract void onActionFail(ActionWorker source); /** * Hook when a action has been canceled. * * @param source the action worker containing the action to perform */ public abstract void onActionCancel(ActionWorker source); /** * Hook when a action has end with no failure or cancel. * * @param source the action worker containing the action to perform */ public abstract void onActionEnd(ActionWorker source); @Override public void run() { if (log.isInfoEnabled()) { log.info("starting... " + this); } try { while (!canceled) { if (canceled) { // une annulation a été demandé // donc même si une opération est demandée, on ne la traite // pas break; } // en attente qu'une opération // le block est bloqué jusqu'à arrivée d'une opération // ou une demande d'annulation setWaiting(true); // le thread a repris la main, donc plus en attente log.trace("no more waiting " + this); if (!canceled) { // une opération a été demandée try { // le thread écoute les modifications de l'action worker.addPropertyChangeListener(workerListener); // démarrage de l'opération dans un worker worker.execute(); // le thread est bloqué jusqu'à la fin de l'opération // ou une demande d'annulation setWaiting(true); // le thread reprend la main des que l'operation // est terminée ou a été annulée, on passera alors // dans la méthode onPropertyChanged } finally { if (worker != null) { // le thread n'écoute plus l'action car elle est terminée // ou annulée worker.removePropertyChangeListener(workerListener); // suppression de l'action worker = null; } } } } } catch (Exception e) { throw new RuntimeException(e); } finally { unlockThread(); if (log.isInfoEnabled()) { log.info(this + " will close..."); } close(); } } /** * Cancel the thread, this will release any lock of the tread. *

* As a side effect, this will close the thread. */ public void cancel() { log.info("cancel " + this); canceled = true; // on rend la main au thread setWaiting(false); } /** La méthode pour nettoyer le thread, a la fermeture. */ protected void close() { // par defaut, on ne fait rien log.info(this); } /** * Mutates the waiting state of the thread. *

* If parameter {@code waiting} is to {@code true}, then will lock the * thread, otherwise will unlock the thread. * * @param waiting {@code true} if a lock is required */ protected void setWaiting(boolean waiting) { if (waiting && !canceled) { // locking thread try { lockThread(); } catch (InterruptedException ex) { log.error(ex.getMessage(), ex); canceled = true; } } if (!waiting) { // release lock unlockThread(); } } /** * To lock the thread. * * @throws InterruptedException if locking was interruped */ protected void lockThread() throws InterruptedException { synchronized (LOCK) { log.trace(this); // lock LOCK.wait(); } } /** To unlock the thread. */ protected void unlockThread() { synchronized (LOCK) { log.trace(this); // unlock LOCK.notify(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy