/*
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:
*
* - calls
started of all registered
* PCallListener objects
* - clears
getOutput buffer
* - calls
runCode
* - calls
finalCode if no exception was thrown and no
* interruption occured
* - output buffer is appended to
outHolder if no error
* occured (optional)
* - if an error occured, messages are written to a log4j logger and
*
outHolder and a dialog is shown (optional)
* - if interrupted, messages are written to a logger and
*
outHolder
* - 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:
*
* - setShowMessage
*
- setPrintDate
*
- setOutHolder
*
*
* 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:
*
*
* - event synchronization with Swing
*
- Swing dialogs are used to display error messages
*
*
* 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.