package jadex.bdi.runtime.impl;
import jadex.bdi.model.OAVBDIMetaModel;
import jadex.bdi.runtime.IPlanExecutor;
import jadex.bdi.runtime.Plan;
import jadex.bdi.runtime.interpreter.BDIInterpreter;
import jadex.bdi.runtime.interpreter.OAVBDIRuntimeModel;
import jadex.bdi.runtime.interpreter.PlanRules;
import jadex.commons.SReflect;
import jadex.commons.collection.SCollection;
import jadex.commons.concurrent.IThreadPool;
import jadex.rules.state.IOAVState;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
/**
* A plan executor for plans that run on their own thread
* and therefore may perform blocking wait operations.
* Plan bodies have to inherit from @link{Plan}.
*/
// todo: move somewhere else (impl???).
public class JavaStandardPlanExecutor implements IPlanExecutor, Serializable
{
//-------- constants --------
public static final String MAX_PLANSTEP_TIME = "max_planstep_time";
//-------- attributes --------
/** The bdi interpreter. */
//protected BDIInterpreter interpreter;
/** The maximum execution time per plan step in millis. */
// protected Number maxexetime;
/** The pool for the planinstances -> execution tasks. */
protected Map tasks;
/** The threadpool. */
protected IThreadPool threadpool;
//-------- constructor --------
/**
* Create a new threadbased plan executor.
*/
public JavaStandardPlanExecutor(IThreadPool threadpool)
{
this.threadpool = threadpool;
this.tasks = Collections.synchronizedMap(SCollection.createHashMap());
}
//-------- IPlanExecutor interface --------
/**
* Create the body of a plan.
* @param rplan The rplan.
* @return The created body.
* May throw any kind of exception, when the body creation fails
*/
public Object createPlanBody(BDIInterpreter interpreter, Object rcapability, Object rplan) throws Exception
{
// Create plan body object.
// Hack!!! Not an elegant way by using a static hashtable!
// Needed for passing the rplan to the abstract plan instance.
String refname= ""+Thread.currentThread()+"_"+Thread.currentThread().hashCode();
AbstractPlan.planinit.put(refname, new Object[]{interpreter, rplan, rcapability});
IOAVState state = interpreter.getState();
Object mplan = state.getAttributeValue(rplan, OAVBDIRuntimeModel.element_has_model);
Object mbody = state.getAttributeValue(mplan, OAVBDIMetaModel.plan_has_body);
// Class clazz = (Class)interpreter.getState().getAttributeValue(mbody, OAVBDIMetaModel.body_has_class);
String clname = (String)state.getAttributeValue(mbody, OAVBDIMetaModel.body_has_impl);
if(clname==null)
throw new RuntimeException("Classname must not be null: "+state.getAttributeValue(state.getAttributeValue(rplan, OAVBDIRuntimeModel.element_has_model), OAVBDIMetaModel.modelelement_has_name));
Class clazz = SReflect.findClass(clname, OAVBDIMetaModel.getImports(interpreter.getState(), interpreter.getState().getAttributeValue
(rcapability, OAVBDIRuntimeModel.element_has_model)), state.getTypeModel().getClassLoader());
Object body = null;
if(clazz!=null)
{
try
{
body = clazz.newInstance();
if(!(body instanceof Plan))
throw new RuntimeException("User plan has wrong baseclass. Expected jadex.bdi.runtime.Plan for standard plan.");
interpreter.getState().setAttributeValue(rplan, OAVBDIRuntimeModel.plan_has_body, body);
}
catch(Exception e)
{
// Use only RuntimeException from below
e.printStackTrace();
}
}
AbstractPlan.planinit.remove(refname);
if(body==null)
throw new RuntimeException("Plan body could not be created: "+clazz);
return body;
}
/**
* Execute a step of a plan.
* Executing a step should cause the latest event to be handled.
* Will be called by the scheduler for every event to be handled.
* May throw any kind of exception, when the plan execution fails
* @return True, if the plan step was interrupted (interrupted flag).
*/
public boolean executeStep(BDIInterpreter interpreter, Object rcapability, Object rplan, String steptype) throws Exception
{
// Get or create new a thread for the plan instance info.
boolean newthread = false;
PlanExecutionTask task = (PlanExecutionTask)tasks.get(rplan);
if(task==null)
{
task = new PlanExecutionTask(interpreter, rcapability, rplan);
tasks.put(rplan, task);
newthread = true;
}
Object monitor = task.getMonitor();
// Lock the pool monitor and start it.
// Because it needs its monitor to run, it starts
// not until the scheduler has called wait().
// System.out.println("plan step started: "+rplan+" "+interpreter.getState()
// .getAttributeValue(interpreter.getState().getAttributeValue(rplan,
// OAVBDIRuntimeModel.element_has_model), OAVBDIMetaModel.modelelement_has_name));
synchronized(monitor)
{
task.setStepType(steptype);
task.setState(PlanExecutionTask.STATE_RUNNING);
if(newthread)
{
// It must be avoided that the new thread
// immediately starts. Therefore its first
// instruction is synchronized(monitor){}
// System.out.println("execute: "+rplan);
threadpool.execute(task);
}
else
{
// System.out.println("notify: "+rplan);
monitor.notify();
}
try
{
// Wait causes to free the monitor
// and awakens the plan thread which needs
// the monitor to execute
// System.out.println("wait: "+rplan);
if(getMaxExecutionTime(interpreter)==0)
monitor.wait();
else
monitor.wait(getMaxExecutionTime(interpreter));
// System.out.println("resumed: "+rplan);
}
catch(InterruptedException e)
{
// Shouldn't happen (agent thread shouldn't be interrupted)
System.err.println("Warning, agent thread was interrupted");
e.printStackTrace(System.err);
}
catch(Throwable e)
{
e.printStackTrace();
}
if(PlanExecutionTask.STATE_RUNNING.equals(task.getState()))
{
// todo:
// agent.getLogger().warning(" plan step is running longer than maximum " +
// "execution time, plan will be terminated: "+agent+" "+task);
// task.getPlan().getRootGoal().fail(null);
// Todo: wait for plan termination
// (otherwise there are two threads running at once).
}
// }
// task.lock.unlock();
// if(task.getState().equals(PlanExecutionTask.STATE_TERMINATED))
// {
// System.out.println("plan step finished: "+rplan+" "+interpreter.getState()
// .getAttributeValue(interpreter.getState().getAttributeValue(rplan,
// OAVBDIRuntimeModel.element_has_model), OAVBDIMetaModel.modelelement_has_name));
//plan.setCleanupFinished(true);
if(task.getThrowable() instanceof Exception)
throw (Exception)task.getThrowable();
else if(task.getThrowable()!=null)
throw new RuntimeException(task.getThrowable());
}
return task.getState().equals(PlanExecutionTask.STATE_INTERRUPTED);
}
/**
* Execute a step of a plan.
* Executing a step should cause the latest event to be handled.
*
* Will be called by the scheduler for every event to be handled.
* May throw any kind of exception, when the plan execution fails
* @return True, if plan was interrupted (micro plan step).
*/
public boolean executePlanStep(BDIInterpreter interpreter, Object rcapability, Object rplan) throws Exception
{
return executeStep(interpreter, rcapability, rplan, OAVBDIRuntimeModel.PLANLIFECYCLESTATE_BODY);
}
/**
* Execute a step of the plans passed() code.
* This method is called, after the plan has finished
* successfully (i.e. without exception).
*
* Will be called by the scheduler for the first time and
* every subsequent event to be handled.
* May throw any kind of exception, when the execution fails
* @return True, if execution was interrupted (micro plan step).
*/
public boolean executePassedStep(BDIInterpreter interpreter, Object rplan) throws Exception
{
assert tasks.containsKey(rplan);
return executeStep(interpreter, null, rplan, OAVBDIRuntimeModel.PLANLIFECYCLESTATE_PASSED);
}
/**
* Execute a step of the plans failed() code.
* This method is called, when the plan has failed
* (i.e. due to an exception occurring in the plan body).
*
* Will be called by the scheduler for the first time and
* every subsequent event to be handled.
* May throw any kind of exception, when the execution fails
* @return True, if execution was interrupted (micro plan step).
*/
public boolean executeFailedStep(BDIInterpreter interpreter, Object rplan) throws Exception
{
assert tasks.containsKey(rplan);
return executeStep(interpreter, null, rplan, OAVBDIRuntimeModel.PLANLIFECYCLESTATE_FAILED);
}
/**
* Execute a step of the plans aborted() code.
* This method is called, when the plan is terminated
* from the outside (e.g. when the corresponding goal is
* dropped or the context condition of the plan becomes invalid)
*
* Will be called by the scheduler for the first time and
* every subsequent event to be handled.
* May throw any kind of exception, when the execution fails
* @return True, if execution was interrupted (micro plan step).
*/
public boolean executeAbortedStep(BDIInterpreter interpreter, Object rplan) throws Exception
{
assert tasks.containsKey(rplan);
return executeStep(interpreter, null, rplan, OAVBDIRuntimeModel.PLANLIFECYCLESTATE_ABORTED);
}
/**
* Interrupt a plan step during execution.
* The plan is requested to stop the execution to allow
* consequences of performed plan actions (like belief changes)
* taking place. If the method is not implemented the
* plan step will be NOT be interrupted.
*/
public void interruptPlanStep(Object rplan)
{
PlanExecutionTask task = (PlanExecutionTask)tasks.get(rplan);
assert task!=null;
// assert task.getExecutionThread()==Thread.currentThread() : rplan+", "+Thread.currentThread();
task.giveBackControl(PlanExecutionTask.STATE_INTERRUPTED, OAVBDIRuntimeModel.PLANPROCESSINGTATE_READY);
// System.out.println("interruptPlanStep: Setting plan to ready: "
// +task.interpreter.getAgentAdapter().getComponentIdentifier().getLocalName()
// +", "+rplan);
}
/**
* Called on termination of a plan.
* Free all associated ressources, stop threads, etc.
*/
public void cleanup(BDIInterpreter interpreter, Object rplan)
{
PlanExecutionTask task = (PlanExecutionTask)tasks.get(rplan);
if(task!=null)
{
Object monitor = task.getMonitor();
// Because plan thread needs its monitor to run, it starts
// not until the executor has called wait().
synchronized(monitor)
{
task.setState(PlanExecutionTask.STATE_RUNNING);
task.setTerminate(true);
monitor.notify();
try
{
// Wait causes to free the monitor
// and awakens the plan thread which needs
// the monitor to execute
if(getMaxExecutionTime(interpreter)==0)
monitor.wait();
else
monitor.wait(getMaxExecutionTime(interpreter));
}
catch(InterruptedException e)
{
// Shouldn't happen (agent thread shouldn't be interrupted)
System.err.println("Warning, agent thread was interrupted");
e.printStackTrace(System.err);
}
if(PlanExecutionTask.STATE_RUNNING.equals(task.getState()))
{
// agent.getLogger().warning(" plan step is running longer than maximum " +
// "execution time, plan will be terminated: "+agent+" "+task);
// todo
//task.getPlan().getRootGoal().fail(null);
// Todo: wait for plan termination
// (otherwise there are two threads running at once).
}
}
}
}
/**
* Get the monitor of a plan.
* @return The monitor.
*/
public Object getMonitor(Object rplan)
{
PlanExecutionTask task = (PlanExecutionTask)tasks.get(rplan);
return task==null? null: task.getMonitor();
}
/**
* Get the executing thread of a plan.
* @param rplan The plan.
* @return The executing thread (if any).
* /
public Thread getExecutionThread(Object rplan)
{
PlanExecutionTask task = (PlanExecutionTask)tasks.get(rplan);
return task==null? null: task.getExecutionThread();
}*/
/**
* Called from a plan.
* Registers the plan to wait for a event.
* Blocks plan when body method is finished.
* Note: This method cannot be synchronized, because when
* a thread comes in and waits it still owns the
* BDIAgent lock.
* @param rplan The planinstance.
* @param wa The wait abstraction.
* /
public IREvent eventWaitFor(Object rplan, WaitAbstraction wa)
{
// todo!
// if(agent.isAtomic())
// throw new RuntimeException("WaitFor not allowed in atomic block.");
Object rbody = state.getAttributeValue(rplan, OAVBDIRuntimeModel.plan_has_body);
if(rbody==null)
throw new RuntimeException("Plan body nulls. waitFor() calls from plan constructors not allowed.");
// Set wait filter settings in plan.
rplan.waitFor(wa);
IREvent ret = null;
boolean failure = false;
PlanExecutionTask task = (PlanExecutionTask)tasks.get(rplan);
if(task.getExecutionThread()==Thread.currentThread())
{
// Transfer execution to agent thread and wait until the plan is scheduled again.
task.giveBackControl(PlanExecutionTask.STATE_WAITING);
// When timout event occurred, throw as TimeoutException.
ret = rplan.getLatestEvent();
if(ret instanceof StandardEvent && IStandardEvent.TYPE_TIMEOUT.equals(ret.getType())
&& ((StandardEvent)ret).getException()!=null)
{
throw ((StandardEvent)ret).getException();
}
}
else
{
failure = true;
}
//System.out.println(":::"+myid+" "+this);
// if(failure)
// agent.getLogger().log(Level.SEVERE, "ThreadedWaitFor error, not plan thread: "+Thread.currentThread());
//assert ret!=null: rplan.getName();
return ret;
}*/
/**
* Called from a plan.
* Registers the plan to wait for a event.
* Blocks plan when body method is finished.
* Note: This method cannot be synchronized, because when
* a thread comes in and waits it still owns the
* BDIAgent lock.
* @param rplan The planinstance.
* @param wa The wait abstraction.
*/
public void eventWaitFor(BDIInterpreter interpreter, Object rplan)
{
if(interpreter.isAtomic())
throw new RuntimeException("WaitFor not allowed in atomic block.");
Object rbody = interpreter.getState().getAttributeValue(rplan, OAVBDIRuntimeModel.plan_has_body);
if(rbody==null)
throw new RuntimeException("Plan body nulls. waitFor() calls from plan constructors not allowed.");
// Set wait filter settings in plan.
// rplan.waitFor(wa);
// IREvent ret = null;
boolean failure = false;
PlanExecutionTask task = (PlanExecutionTask)tasks.get(rplan);
if(task.getExecutionThread()==Thread.currentThread())
{
// Transfer execution to agent thread and wait until the plan is scheduled again.
task.giveBackControl(PlanExecutionTask.STATE_WAITING, OAVBDIRuntimeModel.PLANPROCESSINGTATE_WAITING);
// When timout event occurred, throw as TimeoutException.
// ret = rplan.getLatestEvent();
// if(ret instanceof StandardEvent && IStandardEvent.TYPE_TIMEOUT.equals(ret.getType())
// && ((StandardEvent)ret).getException()!=null)
// {
// throw ((StandardEvent)ret).getException();
// }
}
else
{
failure = true;
}
//System.out.println(":::"+myid+" "+this);
if(failure)
throw new RuntimeException("ThreadedWaitFor error, not plan thread: "+Thread.currentThread());
// agent.getLogger().log(Level.SEVERE, "ThreadedWaitFor error, not plan thread: "+Thread.currentThread());
// return ret;
}
/**
* Get the maximum execution time.
* 0 indicates no maximum execution time.
* @return The max execution time.
*/
protected long getMaxExecutionTime(BDIInterpreter interpreter)
{
Number max = (Number)interpreter.getState().getAttributeValue(interpreter.getAgent(), OAVBDIRuntimeModel.capability_has_properties, MAX_PLANSTEP_TIME);
return max!=null? max.longValue(): 0;
// if(maxexetime==null)
// {
// maxexetime = (Number)interpreter.getState().getAttributeValue(interpreter.getAgent(), OAVBDIRuntimeModel.capability_has_properties, MAX_PLANSTEP_TIME);
//// maxexetime = (Number)component.getPropertybase().getProperty(MAX_PLANSTEP_TIME);
// if(maxexetime==null)
// maxexetime = new Long(0);
// }
// return maxexetime.longValue();
}
//-------- The thread for a plan instance ---------
/**
* The task for executing a plan instance. Will
* be executed in its own thread.
*/
protected class PlanExecutionTask implements Runnable
{
//-------- constants --------
public static final String STATE_RUNNING = "running";
public static final String STATE_WAITING = "waiting";
public static final String STATE_INTERRUPTED = "interrupted";
public static final String STATE_TERMINATED = "terminated";
//-------- attributes --------
/** The interpreter. */
protected BDIInterpreter interpreter;
/** The capability. */
protected Object rcapability;
/** The plan. */
protected Object rplan;
/** The thread to wakeup. */
protected final Object monitor;
/** The plan thread's state. */
protected String exestate;
/** The plan step type to execute (body/passed/failed/aborted). */
protected String steptype;
/** The execution result (when a problem occurred). */
protected Throwable throwable;
/** The thread executing this task. */
protected Thread thread;
/** Flag indicating that the plan should terminate immediately (set from agent thread). */
protected boolean terminate;
//-------- constructors --------
/**
* Create a new plan exeution thread.
* @param rplan The plan instance info.
*/
public PlanExecutionTask(BDIInterpreter interpreter, Object rcapability, Object rplan)
{
// if(rcapability==null)
// {
// System.out.println("plan already finished: "
// +interpreter.getAgentAdapter().getComponentIdentifier().getLocalName()
// +", "+rplan);
// }
assert rcapability!=null;
this.interpreter = interpreter;
this.rcapability = rcapability;
this.rplan = rplan;
this.monitor = new Object();
}
//-------- methods --------
/**
* The thread method.
*/
public void run()
{
// System.out.println("start: "+rplan);
// When the thread is new it has to wait till the
// scheduler is finished (called wait).
// The plan is not allowed to hold the monitor for
// the whole plan execution because this would not
// allow the scheduler (agent) thread to wakeup
// whenever the planstep execution time of the plan
// thread exceeds.
// Save the execution thread for this task.
this.thread = Thread.currentThread();
interpreter.setPlanThread(thread);
ClassLoader oldcl = thread.getContextClassLoader();
assert interpreter.getState().getTypeModel().getClassLoader()!=null;
thread.setContextClassLoader(interpreter.getState().getTypeModel().getClassLoader());
// Execute the plan (interrupted by pause() calls).
// While the plan is executing its plan steps
// it does not hold the monitor! Therefore another
// task can grab the monitor and call notify on
// it. This wakes up the agent thread.
Plan pi = null;
boolean aborted = false; // Body is aborted, continue to aborted() method.
boolean interrupted = false; // Plan is interrupted, exit plan thread.
try
{
Object tmp = interpreter.getState().getAttributeValue(rplan, OAVBDIRuntimeModel.plan_has_body);
if(tmp==null)
tmp = createPlanBody(interpreter, rcapability, rplan);
pi = (Plan)tmp;
pi.body();
}
catch(BodyAborted e)
{
// The body method has been interrupted by the plan step action for abort.
aborted = true;
}
catch(PlanTerminated e)
{
// Plan is interrupted (e.g. due to excessive step length)
// -> ignore and cleanup.
interrupted = true;
}
catch(Throwable t)
{
// Throwable in plan thread will be rethrown in agent thread.
this.throwable = t;
// t.printStackTrace();
}
// Skip plan cleanup code, when plan is interrupted.
if(!interrupted)
{
// Wait until scheduler calls plan cleanup.
// Abort can only happen when plan is not running. Is aborted externally e.g.
// when rootgoal is dropped. Therefore is abort case no further giveBackControl
// is necessary.
if(!aborted)
{
PlanRules.endPlanPart(interpreter.getState(), rcapability, rplan, false);
// Hack!!! Should not change state?
interpreter.getState().setAttributeValue(rplan, OAVBDIRuntimeModel.plan_has_lifecyclestate,
this.throwable==null? OAVBDIRuntimeModel.PLANLIFECYCLESTATE_PASSED : OAVBDIRuntimeModel.PLANLIFECYCLESTATE_FAILED);
// if(throwable!=null)
// throwable.printStackTrace();
giveBackControl(STATE_WAITING, OAVBDIRuntimeModel.PLANPROCESSINGTATE_READY);
// System.out.println("PlanExecutionTask.STATE_WAITING: Setting plan to ready: "
// +interpreter.getAgentAdapter().getComponentIdentifier().getLocalName()
// +", "+rplan);
}
// Execute cleanup code.
throwable = null;
try
{
if(steptype.equals(OAVBDIRuntimeModel.PLANLIFECYCLESTATE_PASSED))
{
pi.passed();
}
else if(steptype.equals(OAVBDIRuntimeModel.PLANLIFECYCLESTATE_FAILED))
{
// Check if body has been created (can be null when body creation fails).
if(pi!=null)
{
pi.failed();
}
}
else if(steptype.equals(OAVBDIRuntimeModel.PLANLIFECYCLESTATE_ABORTED))
{
pi.aborted();
}
}
catch(PlanTerminated e)
{
// Plan is interrupted (e.g. due to excessive step length)
// -> ignore and cleanup.
}
catch(Throwable t)
{
// Throwable in plan thread will be rethrown in agent thread.
this.throwable = t;
}
}
PlanRules.endPlanPart(interpreter.getState(), rcapability, rplan, true);
// Set plan processing state.
interpreter.getState().setAttributeValue(rplan, OAVBDIRuntimeModel.plan_has_processingstate, OAVBDIRuntimeModel.PLANPROCESSINGTATE_FINISHED);
// Cleanup the plan execution thread.
tasks.remove(rplan);
exestate = PlanExecutionTask.STATE_TERMINATED;
// Finally, transfer execution back to agent thread.
synchronized(monitor)
{
// System.out.println("finish1: "+rplan);
interpreter.setPlanThread(null);
thread.setContextClassLoader(oldcl);
monitor.notify();
// System.out.println("finish2: "+rplan);
}
}
/**
* Get the pool monitor.
* @return The monitor.
*/
public Object getMonitor()
{
return monitor;
}
/**
* Interrupt the plan execution.
* Stop and notify the scheduler.
* Continues when monitor is notified from the scheduler again.
*/
public void giveBackControl(String exestate, String procstate)
{
//System.out.println("waiting for lock: "+n);
synchronized(monitor)
{
// Remember current step type (might get overwritten from agent thread).
String planstate = steptype;
// Set processing state to "waiting"
interpreter.getState().setAttributeValue(rplan, OAVBDIRuntimeModel.plan_has_processingstate, procstate);
// Transfer execution from plan thread to agent thread using notify/wait pair.
this.exestate = exestate;
monitor.notify();
try
{
interpreter.setPlanThread(null);
// System.out.println("givebackcontrol: "+rplan);
monitor.wait();
}
catch(InterruptedException e)
{
// Shouldn't happen (plan thread shouldn't be interrupted)
System.err.println("Warning, plan thread was interrupted: "+rplan);
e.printStackTrace(System.err);
}
// Execution continues when the executors executeStep() transfers
// execution from agent thread to plan thread (using another notify/wait pair).
interpreter.setPlanThread(thread);
// When plan must be terminated unconditionally stop execution.
if(terminate)
{
// System.out.println("terminate plan: "+rplan);
throw new PlanTerminated();
}
// When planstate has changed from body to aborted, leave body method
// by throwing an error, which is catched by plan execution task.
else if(planstate.equals(OAVBDIRuntimeModel.PLANLIFECYCLESTATE_BODY)
&& interpreter.getState().getAttributeValue(rplan, OAVBDIRuntimeModel
.plan_has_lifecyclestate).equals(OAVBDIRuntimeModel.PLANLIFECYCLESTATE_ABORTED))
{
// System.out.println("abort plan: "+rplan);
throw new BodyAborted();
}
// else
// {
// System.out.println("continue plan: "+rplan+", "+planstate+", "
// +interpreter.getState().getAttributeValue(rplan, OAVBDIRuntimeModel.plan_has_lifecyclestate));
// }
}
}
/**
* Get the plan.
* @return The plan.
*/
public Object getPlan()
{
return this.rplan;
}
/**
* Get the plan thread state.
* @return The plan thread state.
*/
public String getState()
{
return this.exestate;
}
/**
* Set the plan thread state.
* @param state The plan thread state.
*/
public void setState(String exestate)
{
this.exestate = exestate;
}
/**
* Set the plan step type.
* @param type The plan step type (body/passed/failed/aborted).
*/
public void setStepType(String type)
{
this.steptype = type;
}
/**
* Get the execution result.
* @return The execution result.
*/
public Throwable getThrowable()
{
return throwable;
}
/**
* Get the execution thread.
* @return thread The thread.
*/
public Thread getExecutionThread()
{
return this.thread;
}
/**
* Set the terminate flag.
*/
public void setTerminate(boolean terminate)
{
this.terminate = terminate;
}
/**
* Create a string representation of this element.
*/
public String toString()
{
return "PlanExecutionTask("+rplan+")";
}
}
/**
* An error thrown to abort the execution of the plan body.
*/
public static class BodyAborted extends ThreadDeath
{
// public BodyAborted()
// {
// System.err.print(this+": ");
// Thread.dumpStack();
// }
//
// public String toString()
// {
// return super.toString()+"@"+hashCode();
// }
}
/**
* An error allowing the agent to terminate the execution of a plan.
*/
public static class PlanTerminated extends ThreadDeath
{
}
// todo remove me
// public static int n;
}