package jadex.micro; import jadex.bridge.ComponentResultListener; import jadex.bridge.ComponentTerminatedException; import jadex.bridge.IArgument; import jadex.bridge.IComponentAdapter; import jadex.bridge.IComponentAdapterFactory; import jadex.bridge.IComponentDescription; import jadex.bridge.IComponentIdentifier; import jadex.bridge.IComponentInstance; import jadex.bridge.IComponentListener; import jadex.bridge.IComponentManagementService; import jadex.bridge.IComponentStep; import jadex.bridge.IExternalAccess; import jadex.bridge.IInternalAccess; import jadex.bridge.IMessageAdapter; import jadex.bridge.IModelInfo; import jadex.commons.ChangeEvent; import jadex.commons.Future; import jadex.commons.IChangeListener; import jadex.commons.IFuture; import jadex.commons.concurrent.DefaultResultListener; import jadex.commons.concurrent.DelegationResultListener; import jadex.commons.concurrent.IResultListener; import jadex.commons.service.IServiceContainer; import jadex.commons.service.IServiceProvider; import jadex.commons.service.SServiceProvider; import jadex.commons.service.clock.ITimer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The micro agent interpreter is the connection between the agent platform * and a user-written micro agent. */ public class MicroAgentInterpreter implements IComponentInstance { //-------- attributes -------- /** The platform adapter for the agent. */ protected IComponentAdapter adapter; /** The micro agent model. */ protected IModelInfo model; /** The micro agent. */ protected MicroAgent microagent; /** The configuration. */ protected String config; /** The arguments. */ protected Map arguments; /** The results. */ protected Map results; /** The parent. */ protected IExternalAccess parent; /** The scheduled steps of the agent. */ protected List steps; /** The change listeners. */ protected List changelisteners; /** The execution history. */ protected List history; /** The service container. */ protected IServiceContainer container; /** The component listeners. */ protected List componentlisteners; /** Flag indicating that no steps may be scheduled any more. */ protected boolean nosteps; //-------- constructors -------- /** * Create a new agent. * @param adapter The adapter. * @param microagent The microagent. */ public MicroAgentInterpreter(IComponentDescription desc, IComponentAdapterFactory factory, final IModelInfo model, Class microclass, final Map arguments, String config, final IExternalAccess parent, final Future inited) { this.model = model; this.config = config; this.arguments = arguments; this.parent = parent; // synchronized because of MicroAgentViewPanel, todo this.steps = Collections.synchronizedList(new ArrayList()); this.adapter = factory.createComponentAdapter(desc, model, this, parent); // Init the arguments with default values. IArgument[] args = model.getArguments(); for(int i=0; i<args.length; i++) { if(args[i].getDefaultValue(this.config)!=null) { if(this.arguments==null) this.arguments = new HashMap(); if(!this.arguments.containsKey(args[i].getName())) { this.arguments.put(args[i].getName(), args[i].getDefaultValue(this.config)); } } } // Init the results with default values. IArgument[] res = model.getResults(); for(int i=0; i<res.length; i++) { if(res[i].getDefaultValue(this.config)!=null) { if(MicroAgentInterpreter.this.results==null) MicroAgentInterpreter.this.results = new HashMap(); MicroAgentInterpreter.this.results.put(res[i].getName(), res[i].getDefaultValue(this.config)); } } try { // microagent = (MicroAgent)model.getMicroAgentClass().newInstance(); microagent = (MicroAgent)microclass.newInstance(); microagent.init(MicroAgentInterpreter.this); // Schedule initial step. addStep(new Object[]{new IComponentStep() { public Object execute(IInternalAccess ia) { microagent.agentCreated(); getServiceContainer().start().addResultListener(createResultListener(new IResultListener() { public void resultAvailable(Object source, Object result) { // Init is now finished. Notify cms. inited.setResult(new Object[]{MicroAgentInterpreter.this, adapter}); addStep(new Object[]{new IComponentStep() { public Object execute(IInternalAccess ia) { microagent.executeBody(); return null; } public String toString() { return "microagent.executeBody()_#"+this.hashCode(); } }, new Future()}); } public void exceptionOccurred(Object source, Exception exception) { inited.setException(exception); } })); return null; } public String toString() { return "microagent.init()_#"+this.hashCode(); } }, new Future()}); } catch(Exception e) { throw new RuntimeException(e); } } //-------- IKernelAgent interface -------- /** * Can be called on the agent thread only. * * Main method to perform agent execution. * Whenever this method is called, the agent performs * one of its scheduled actions. * The platform can provide different execution models for agents * (e.g. thread based, or synchronous). * To avoid idle waiting, the return value can be checked. * The platform guarantees that executeAction() will not be called in parallel. * @return True, when there are more actions waiting to be executed. */ public boolean executeStep() { try { if(!steps.isEmpty()) { Object[] step = removeStep(); String steptext = ""+step[0]; Future future = (Future)step[1]; // Correct to execute them in try catch?! // if(step[0] instanceof ICommand) // { // if(future!=null) // { // try // { // ((ICommand)step[0]).execute(microagent); // future.setResult(null); // } // catch(RuntimeException e) // { // future.setException(e); // throw e; // } // } // else // { // ((ICommand)step[0]).execute(microagent); // } // } // else //if(step[0] instanceof IResultCommand) // { try { Object res = ((IComponentStep)step[0]).execute(microagent); if(res instanceof IFuture) { ((IFuture)res).addResultListener(new DelegationResultListener(future)); } else { future.setResult(res); } } catch(RuntimeException e) { future.setException(e); throw e; } // } addHistoryEntry(steptext); } return !steps.isEmpty(); } catch(ComponentTerminatedException ate) { // Todo: fix microkernel bug. ate.printStackTrace(); return false; } } /** * Can be called concurrently (also during executeAction()). * * Inform the agent that a message has arrived. * Can be called concurrently (also during executeAction()). * @param message The message that arrived. */ public void messageArrived(final IMessageAdapter message) { // System.out.println("msgrec: "+getAgentAdapter().getComponentIdentifier()+" "+message); // IFuture ret = scheduleStep(new ICommand() scheduleStep(new IComponentStep() { public Object execute(IInternalAccess ia) { microagent.messageArrived(Collections.unmodifiableMap(message.getParameterMap()), message.getMessageType()); return null; } public String toString() { return "microagent.messageArrived("+message+")_#"+this.hashCode(); } }); // ret.addResultListener(new DefaultResultListener(adapter.getLogger()) // { // public void resultAvailable(Object source, Object result) // { // } // }); } /** * Can be called concurrently (also during executeAction()). * * Request agent to kill itself. * The agent might perform arbitrary cleanup activities during which executeAction() * will still be called as usual. * Can be called concurrently (also during executeAction()). * @param listener When cleanup of the agent is finished, the listener must be notified. */ public IFuture cleanupComponent() { final Future ret = new Future(); try { getAgentAdapter().invokeLater(new Runnable() { public void run() { // System.out.println("cleanupComponent: "+getAgentAdapter().getComponentIdentifier()); nosteps = true; ComponentTerminatedException ex = new ComponentTerminatedException(getAgentAdapter().getComponentIdentifier()); while(!steps.isEmpty()) { Object[] step = removeStep(); Future future = (Future)step[1]; future.setException(ex); // System.out.println("Cleaning obsolete step: "+getAgentAdapter().getComponentIdentifier()+", "+step[0]); } for(int i=0; i<microagent.timers.size(); i++) { ITimer timer = (ITimer)microagent.timers.get(i); timer.cancel(); } microagent.timers.clear(); if(componentlisteners!=null) { for(int i=0; i<componentlisteners.size(); i++) { IComponentListener lis = (IComponentListener)componentlisteners.get(i); lis.componentTerminating(new ChangeEvent(adapter.getComponentIdentifier())); } } microagent.agentKilled(); if(componentlisteners!=null) { for(int i=0; i<componentlisteners.size(); i++) { IComponentListener lis = (IComponentListener)componentlisteners.get(i); lis.componentTerminated(new ChangeEvent(adapter.getComponentIdentifier())); } } IComponentIdentifier cid = adapter.getComponentIdentifier(); ret.setResult(cid); } public String toString() { return "microagent.agentKilled()_#"+this.hashCode(); } }); } catch(Exception e) { ret.setException(e); } return ret; } /** * Kill the component. */ public IFuture killComponent() { final Future ret = new Future(); SServiceProvider.getService(getServiceProvider(), IComponentManagementService.class).addResultListener(new DefaultResultListener() { public void resultAvailable(Object source, Object result) { ((IComponentManagementService)result).destroyComponent(adapter.getComponentIdentifier()) .addResultListener(new DelegationResultListener(ret)); } }); return ret; } /** * Can be called concurrently (also during executeAction()). * * Get the external access for this agent. * The specific external access interface is kernel specific * and has to be casted to its corresponding incarnation. * @param listener External access is delivered via result listener. */ public IExternalAccess getExternalAccess() { return microagent.getExternalAccess(); // final Future ret = new Future(); // // getAgentAdapter().invokeLater(new Runnable() // { // public void run() // { // Object exta = microagent.getExternalAccess(); // ret.setResult(exta); // } // // public String toString() // { // return "microagent.getExternalAccess()_#"+this.hashCode(); // } // }); // // return ret; } /** * Get the class loader of the agent. * The agent class loader is required to avoid incompatible class issues, * when changing the platform class loader while agents are running. * This may occur e.g. when decoding messages and instantiating parameter values. * @return The agent class loader. */ public ClassLoader getClassLoader() { return model.getClassLoader(); } /** * Get the results. * @return The results map. */ public Map getResults() { return results!=null? Collections.unmodifiableMap(results): Collections.EMPTY_MAP; } /** * Called when a component has been created as a subcomponent of this component. * This event may be ignored, if no special reaction to new or destroyed components is required. * The current subcomponents can be accessed by IComponentAdapter.getSubcomponents(). * @param comp The newly created component. */ public IFuture componentCreated(IComponentDescription desc, IModelInfo model) { return new Future(null); } /** * Called when a subcomponent of this component has been destroyed. * This event may be ignored, if no special reaction to new or destroyed components is required. * The current subcomponents can be accessed by IComponentAdapter.getSubcomponents(). * @param comp The destroyed component. */ public IFuture componentDestroyed(IComponentDescription desc) { return new Future(null); } /** * Test if the component's execution is currently at one of the * given breakpoints. If yes, the component will be suspended by * the platform. * @param breakpoints An array of breakpoints. * @return True, when some breakpoint is triggered. */ public boolean isAtBreakpoint(String[] breakpoints) { return microagent.isAtBreakpoint(breakpoints); } //-------- helpers -------- /** * Get the history mode. */ public boolean isHistoryEnabled() { return history!=null; } /** * Get the history. * @return The history. * / public List getSteps() { return this.steps; }*/ /** * Get the history. * @return The history. */ public List getHistory() { return this.history; } /** * Set the history mode. */ public void setHistoryEnabled(boolean enabled) { // Hack!!! synchronized because of MicroAgentViewPanel. if(enabled && history==null) history = Collections.synchronizedList(new ArrayList()); else if(!enabled && history!=null) history = null; } // /** // * Schedule a step of the agent. // * May safely be called from external threads. // * @param step Code to be executed as a step of the agent. // */ // public IFuture scheduleStep(final ICommand step) // { // final Future ret = new Future(); //// System.out.println("ss: "+getAgentAdapter().getComponentIdentifier()+" "+Thread.currentThread()+" "+step); // try // { // adapter.invokeLater(new Runnable() // { // public void run() // { // addStep(new Object[]{step, ret}); // } // }); // } // catch(Exception e) // { // ret.setException(e); // } // return ret; // } /** * Schedule a step of the agent. * May safely be called from external threads. * @param step Code to be executed as a step of the agent. */ public IFuture scheduleStep(final IComponentStep step) { final Future ret = new Future(); // System.out.println("ss: "+getAgentAdapter().getComponentIdentifier()+" "+Thread.currentThread()+" "+step); try { adapter.invokeLater(new Runnable() { public void run() { addStep(new Object[]{step, ret}); } public String toString() { return "invokeLater("+step+")"; } }); } catch(Exception e) { ret.setException(e); } return ret; } /** * Add a new step. */ protected void addStep(Object[] step) { if(nosteps) { ((Future)step[1]).setException(new ComponentTerminatedException(getAgentAdapter().getComponentIdentifier())); } else { steps.add(step); notifyListeners(new ChangeEvent(this, "addStep", step)); } } /** * Add a new step. */ protected Object[] removeStep() { Object[] ret = (Object[])steps.remove(0); notifyListeners(new ChangeEvent(this, "removeStep", new Integer(0))); return ret; } /** * Add a new step. */ protected void addHistoryEntry(String steptext) { if(history!=null) { history.add(steptext); notifyListeners(new ChangeEvent(this, "addHistoryEntry", steptext)); } } /** * Clear the history. * / public void clearHistory() { if(history!=null) history.clear(); }*/ /** * Add an action from external thread. * The contract of this method is as follows: * The agent ensures the execution of the external action, otherwise * the method will throw a agent terminated sexception. * @param action The action. * / public void invokeLater(Runnable action) { synchronized(ext_entries) { if(ext_forbidden) throw new ComponentTerminatedException("External actions cannot be accepted " + "due to terminated agent state: "+this); { ext_entries.add(action); } } adapter.wakeup(); }*/ /** * Invoke some code with agent behaviour synchronized on the agent. * @param code The code to execute. * The method will block the externally calling thread until the * action has been executed on the agent thread. * If the agent does not accept external actions (because of termination) * the method will directly fail with a runtime exception. * Note: 1.4 compliant code. * Problem: Deadlocks cannot be detected and no exception is thrown. * / public void invokeSynchronized(final Runnable code) { if(isExternalThread()) { // System.err.println("Unsynchronized internal thread."); // Thread.dumpStack(); final boolean[] notified = new boolean[1]; final RuntimeException[] exception = new RuntimeException[1]; // Add external will throw exception if action execution cannot be done. // System.err.println("invokeSynchonized("+code+"): adding"); getAgentAdapter().invokeLater(new Runnable() { public void run() { try { code.run(); } catch(RuntimeException e) { exception[0] = e; } synchronized(notified) { notified.notify(); notified[0] = true; } } public String toString() { return code.toString(); } }); try { // System.err.println("invokeSynchonized("+code+"): waiting"); synchronized(notified) { if(!notified[0]) { notified.wait(); } } // System.err.println("invokeSynchonized("+code+"): returned"); } catch(InterruptedException e) { e.printStackTrace(); } if(exception[0]!=null) throw exception[0]; } else { System.err.println("Method called from internal agent thread."); Thread.dumpStack(); code.run(); } }*/ /** * Check if the external thread is accessing. * @return True, if access is ok. */ public boolean isExternalThread() { return adapter.isExternalThread(); } /** * Get the logger. * @return The logger. * / public Logger getLogger() { return adapter.getLogger(); }*/ /** * Get the agent adapter. * @return The agent adapter. */ public IComponentAdapter getAgentAdapter() { return adapter; } /** * Get the agent model. * @return The model. */ public IModelInfo getAgentModel() { return model; } /** * Get the arguments. * @return The arguments. */ public Map getArguments() { return arguments; } /** * Set a result value. * @param name The result name. * @param value The result value. */ public void setResultValue(String name, Object value) { if(results==null) results = new HashMap(); results.put(name, value); } /** * Get the parent component. * @return The parent (if any). */ public IExternalAccess getParent() { return parent; } /** * Get the configuration. * @return The configuration. */ public String getConfiguration() { return this.config; } /** * Get the service provider. */ public IServiceProvider getServiceProvider() { return getServiceContainer(); } /** * Create the service container. * @return The service container. */ public IServiceContainer getServiceContainer() { if(container==null) { container = microagent.createServiceContainer(); } return container; } /** * Create a result listener which is executed as an agent step. * @param The original listener to be called. * @return The listener. */ public IResultListener createResultListener(IResultListener listener) { return new ComponentResultListener(listener, adapter); } /** * The micro listener for executing listener invocations as an agent step. * / class MicroListener implements IResultListener { protected IResultListener listener; public MicroListener(IResultListener listener) { this.listener = listener; } public void resultAvailable(final Object source, final Object result) { scheduleStep(new Runnable() { public void run() { listener.resultAvailable(source, result); } public String toString() { return "resultAvailable("+result+")_#"+this.hashCode(); } }); } public void exceptionOccurred(final Object source, final Exception exception) { scheduleStep(new Runnable() { public void run() { listener.exceptionOccurred(source, exception); } public String toString() { return "exceptionOccurred("+exception+")_#"+this.hashCode(); } }); } }*/ /** * Add a change listener. * @param listener The listener. */ public void addChangeListener(IChangeListener listener) { if(changelisteners==null) changelisteners = new ArrayList(); changelisteners.add(listener); // Inform new listener of current state. listener.changeOccurred(new ChangeEvent(this, "initialState", new Object[]{ steps.toArray(), history.toArray() })); } /** * Remove a change listener. * @param listener The listener. */ public void removeChangeListener(IChangeListener listener) { if(changelisteners!=null) changelisteners.remove(listener); } /** * Notify the change listeners. */ public void notifyListeners(ChangeEvent event) { if(changelisteners!=null) { for(int i=0; i<changelisteners.size(); i++) { ((IChangeListener)changelisteners.get(i)).changeOccurred(event); } } } /** * Add an component listener. * @param listener The listener. */ public void addComponentListener(IComponentListener listener) { if(componentlisteners==null) componentlisteners = new ArrayList(); componentlisteners.add(listener); } /** * Remove a component listener. * @param listener The listener. */ public void removeComponentListener(IComponentListener listener) { if(componentlisteners!=null) componentlisteners.remove(listener); } }