/*
 This file is part of the software framework JStatCom, 
 a library for building applications for data based analysis.

 Copyright (C) 2000-2005  Markus Kraetzig

 This library 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 2.1 of the
 License, or (at your option) any later version.

 This library 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
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 USA
 */

package com.jstatcom.engine;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;

import org.apache.log4j.Logger;

import EDU.oswego.cs.dl.util.concurrent.Executor;
import EDU.oswego.cs.dl.util.concurrent.QueuedExecutor;

import com.jstatcom.component.CompSettings;
import com.jstatcom.component.OutHolder;
import com.jstatcom.component.StdMessages;
import com.jstatcom.component.SystemOutHolder;
import com.jstatcom.model.JSCData;
import com.jstatcom.model.SymbolTable;
import com.jstatcom.project.OutputData;
import com.jstatcom.project.OutputList;

/**
 * This class encapsulates procedure calls to carry out computational tasks in
 * an extra thread. It can optionally be executed in a single worker thread by
 * invoking the thread with execute. Concrete subclasses must
 * implement runCode with the respective algorithm for the
 * computation. Subclasses can also overwrite finalCode for
 * output formatting or other tasks that should be carried out if
 * runCode finished successfully. All relevant data should be
 * given as arguments in the constructor.
 * 

* A procedure call object can be invoked with the respective arguments from a * GUI or a non GUI object. It helps to separate the program logic from the user * interface part. *

* All events that are fired are synchronized with the Swing * EvenDispatchingThread if isGui()==true. * * @author Markus Kraetzig */ public abstract class PCall extends Thread { /** * Whether this class runs in a graphical environment. Default is * true. */ private boolean gui = true; private static final Logger log = Logger.getLogger(PCall.class); protected volatile boolean cancelled = false; private static final QueuedExecutor executorThread = new QueuedExecutor(); private List callerList = null; /** * Typesave enum for event types. Internal use only. */ protected abstract static class EvtTypes { private final String name; private static final EvtTypes SUCESS = new EvtTypes("SUCESS") { public void fire(PCallListener l, PCall pc) { l.success(); } }; private static final EvtTypes FINISHED = new EvtTypes("FINISHED") { public void fire(PCallListener l, PCall pc) { l.finished(pc); } }; private static final EvtTypes STARTED = new EvtTypes("STARTED") { public void fire(PCallListener l, PCall pc) { l.started(pc); } }; private static final EvtTypes QUEUED = new EvtTypes("QUEUED") { public void fire(PCallListener l, PCall pc) { l.queued(pc); } }; private EvtTypes(String name) { this.name = name; } public String toString() { return name; } /** * Notifies a listener by calling a method of PCallListener * according to this type. * * @param l * the listener to be notified * @param pc * reference to the source call object */ abstract void fire(PCallListener l, PCall pc); } private boolean printDate = true; private EventListenerList listenerList = new EventListenerList(); private long startTime = 0; private boolean success = true; private OutHolder outHolder = new SystemOutHolder(); private SymbolTable symbolTable = null; /** * Holds output from computation that should be appended to the output * holder. The contents of that buffer cleared when the call is started. The * buffer can be used to hold formatted output that is displayed via the * OutHolder that is set. The output should be formatted in * the finalCode method. */ protected final StringBuffer output = new StringBuffer(); public PCall() { super(); } /** * Registers listener l such that it will receive notifications. * * @param l * listener to be added */ public final void addPCallListener(PCallListener l) { if (l == null) return; listenerList.add(PCallListener.class, l); } /** * This method is executed before runCode. The default * implementation is emtpy and can be overwritten by subclasses. *

* Usage note:
* Overwriting this method is only needed if initialization code that can be * shared by various subclasses is put in a superclass without changing the * runCode implementation of subclasses. This could for * example be library load routines. */ protected void preCode() { } /** * This method is executed when runCode has finished * successfully. The default implementation is emtpy and should be * overwritten by subclasses. Usually it is used to assemble an output * buffer that is then printed to an result area. */ protected void finalCode() { } /** * Dispatches events to listeners with the help of the listener list. * * @param evtType * the event type */ private void firePCallEvent(final EvtTypes evtType) { if (listenerList == null || evtType == null) return; if (!gui || SwingUtilities.isEventDispatchThread()) { // guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) if (listeners[i] == PCallListener.class) evtType.fire((PCallListener) listeners[i + 1], this); if (getPCallControl() != null) evtType.fire(getPCallControl(), this); } else try { SwingUtilities.invokeAndWait(new Thread() { public void run() { firePCallEvent(evtType); } }); } catch (Throwable ex) { log.error("Event calling failed.", ex); } } /** * Gets the output holder component. The default is * SystemOutHolder. * * @return a component used to assemble output from this call */ public OutHolder getOutHolder() { return outHolder; } /** * Gets the system time at thread start. * * @return System.currentTimeMillis() when the call was * started */ public long getStartTime() { return startTime; } /** * Gets the currently used symbol table. * * @return a symbol table */ public SymbolTable getSymbolTable() { return symbolTable; } /** * Gets whether the call finished successfully. * * @return true if runCode was successful */ public synchronized boolean isSuccess() { return success; } /** * Unregisters listener l so that it will no longer be notified. * * @param l * listener to be removed */ public void removePCallListener(PCallListener l) { if (l == null) return; listenerList.remove(PCallListener.class, l); } /** * Template method that calls preCode,runCode * and finalCode in this order. It handles any exceptions * that occur in these methods. The method has the following structure: *

    *
  1. calls started of all registered * PCallListener objects *
  2. clears getOutput buffer *
  3. calls runCode *
  4. calls finalCode if no exception was thrown and no * interruption occured *
  5. output buffer is appended to outHolder if no error * occured (optional) *
  6. if an error occured, messages are written to a log4j logger and * outHolder and a dialog is shown (optional) *
  7. if interrupted, messages are written to a logger and * outHolder *
  8. if successfull and not interrupted, then success and * finished methods of PCallListener objects * are called, otherwise only finished *
* * The behaviour of this function can be adjusted by overwriting * runCode and finalCode as well as by * setting: * * * The output should be assembled in the finalCode method of * subclasses by using the getOutput buffer. * */ public final void run() { firePCallEvent(EvtTypes.STARTED); startTime = System.currentTimeMillis(); Throwable runEx = null; success = true; cancelled = false; try { output.delete(0, output.length()); preCode(); runCode(); if (!cancelled) finalCode(); } catch (Throwable ex) { // catches ERRORS and EXCEPTIONS runEx = ex; // only indicate error if it was not interrupted if (!cancelled) success = false; } finally { try { cleanUpAfterCall(); } catch (Throwable t) { log.error("Error in cleanup code.", t); } if (!success && !cancelled) { if (gui) output.append("\nError in \"" + getName() + "\" call, see log for details.\n\n"); else output.append("\nError in \"" + getName() + "\" call:\n" + runEx.getMessage() + "\n\n"); log.error("failed call: " + getName(), runEx); if (gui) { String msg = "Error running \"" + getName() + "\":\n" + runEx.getMessage(); StdMessages.error(msg, "Procedure Call Error"); } } if (cancelled) { String msg = "\"" + getName() + "\" call was interrupted, see log for details.\n"; output.append(msg); if (runEx != null) msg = msg + runEx.getMessage() + "\n\n"; else msg = msg + "\n"; log.info(msg); } String dateString = "*** " + CompSettings.getDateString() + " ***\n"; if (getOutHolder() != null && output.toString().trim().length() > 0) { if (printDate) getOutHolder().append(dateString + output.toString()); else getOutHolder().append(output.toString()); } // success and no interruption if (success && !cancelled) { firePCallEvent(EvtTypes.SUCESS); firePCallEvent(EvtTypes.FINISHED); // adds output to tree if (gui && output.length() > 0) OutputList.getInstance().addOutput( new OutputData(getName(), dateString + output.toString())); // if success call other elements in the same thread if (callerList != null) for (Iterator iter = callerList.iterator(); iter.hasNext();) { PCall element = (PCall) iter.next(); element.run(); } // some problem occured } else firePCallEvent(EvtTypes.FINISHED); } } /** * This method executes the main task of the computation thread. It must be * implemented by subclasses and should contain all engine calls. If this * method finishes successfully, meaning that no exceptions are thrown, then * finalCode is executed. *

* Usage note:
* Errors in the execution should be propagated by throwing an exception * with a descriptive error message. */ protected abstract void runCode(); /** * Sets the output component that the results and other output should be * written to. The default is SystemOutHolder. * * @param outputHolder * write output to */ public void setOutHolder(OutHolder outputHolder) { this.outHolder = outputHolder; } /** * Sets the symbol table to access data objects. Subclasses must implement * and document themselves which variables should be written or read to and * from the symbol table. It is not used in the abstract implementation. * * @param newSymbolTable * a new symbol table * @throws IllegalArgumentException * if (newSymbolTable == null) */ public void setSymbolTable(SymbolTable newSymbolTable) { if (newSymbolTable == null) throw new IllegalArgumentException("Symbol table was null."); symbolTable = newSymbolTable; } /** * Puts this call object in the worker thread queue. The call will be * executed after every other call that has been queued before has finished. * The queuing strategy can be changed by overwriting * getExecutor. *

* Implementation
* Calls execute(Runnable command) from * util.concurrent.QueuedExecutor. */ public void execute() { try { firePCallEvent(EvtTypes.QUEUED); setPriority(Thread.MIN_PRIORITY); getExecutor().execute(this); } catch (InterruptedException ex) { log.error("Execution of " + getName() + " failed.", ex); success = false; firePCallEvent(EvtTypes.FINISHED); } } /** * Interrupts this call and calls the stop method of the * engine. */ public void cancel() { cancelled = true; if (engine() != null) try { engine().stop(); } catch (UnsupportedOperationException e) { cancelled = false; // clear interruption status String s = "Running task for engine\n<" + engine().getClass().getName() + ">\ncannot be stopped."; if (gui) StdMessages.infoGeneral(s); else log.error(s, e); } } /** * Adds another call object that will be executed if this call finished * successfully. All added objects are stored in a list that keeps the * original order. All calls are executed in the same thread as this call. * If one of the added calls produces an error, the remaining calls are * executed nevertheless. *

* The event notification is done whenever a call was finished, meaning that * success and finished are called before the * other elements of the list are started. * * @param pCall * call object that is exectued conditional on this call to * finish without error */ public void add(PCall pCall) { if (callerList == null) callerList = new ArrayList(); callerList.add(pCall); } /** * Removes an element from the list of calls that are executed if this call * finishes ok. Nothing happens if pCall was not added * before. * * @param pCall * the call object to remove */ public void remove(PCall pCall) { if (callerList == null) return; callerList.remove(pCall); } /** * Gets whether date is printed in output. * * @return true if printed */ public boolean isPrintDate() { return printDate; } /** * Sets whether date is printed in output, the default is true. * * @param b * true if printed */ public void setPrintDate(boolean b) { printDate = b; } /** * Overwrite protection method calls super method. */ public final int hashCode() { return super.hashCode(); } /** * Overwrite protection method calls super method. */ public final boolean equals(Object o) { return super.equals(o); } /** * Gets the Engine instance that is used by this call for the * convenience implementations of the adapter methods to the engine * interface. * * @return a instance of Engine */ public abstract Engine engine(); /** * Adapter to Engine.call that uses engine(). * * @param procName * procedure to be executed by this engine * @param args * the data to be used as input * @param retData * the return types * @see com.jstatcom.engine.Engine */ public void call(String procName, JSCData[] args, JSCData[] retData) { engine().call(procName, args, retData); if (cancelled) throw new RuntimeException(getName() + " was interrupted."); } /** * Adapter to Engine.load that uses engine(). * * @param module * the name of the module to load * @param loadType * the type of module to load * @param args * module load arguments if needed */ public void load(String module, LoadTypes loadType, JSCData[] args) { engine().load(module, loadType, args); if (cancelled) throw new RuntimeException(getName() + " was interrupted."); } /** * Gets a special listener that, for example, can be used to display status * information about a running call. This implementation returns a default * component that is shared among all instances of this class and that * displays the time, name, and engine of a running call. Subclasses can * overwrite this method to provide their own shared listeners. *

* Usage Note:
* To use a different control, it makes sense to subclass and overwrite this * abstract class with another abstract class providing the new * implementation with a different shared listener. Then all concrete * classes should subclass the newly created class. This is useful if all * calls share a common control component. * * @return PCallListener for special purposes */ protected PCallListener getPCallControl() { return DefaultPCallControl.getInstance(); } /** * Gets the executor that is used by the execute method. The * default implementation uses util.concurrent.QueuedExecutor. * Subclasses might overwrite this method to define a different execution * policy. * * @return instance of Executor to be used by * execute */ public Executor getExecutor() { return executorThread; } /** * Gets whether GUI mode is set. * * @return true if in GUI mode, false * otherwise */ public boolean isGui() { return gui; } /** * Sets GUI mode, if true/false the following features are * enabled/disabled: * *

* * Default is true. * * @param gui */ public void setGui(boolean gui) { this.gui = gui; } /** * This method is called when the call has finished, successful or not. It * should be used to implement cleanup code, like deleting temporary files. * The default implementation is empty. * */ protected void cleanUpAfterCall() { } }

Ads help maintain this website.