package org.jactr.modules.pm.common.efferent; /* * default logging */ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.locks.ReentrantReadWriteLock; import javolution.util.FastList; import javolution.util.FastSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.commonreality.agents.IAgent; import org.commonreality.efferent.IEfferentCommand; import org.commonreality.efferent.event.IEfferentCommandListener; import org.commonreality.identifier.IIdentifier; import org.commonreality.message.command.object.IObjectCommand; import org.commonreality.message.request.object.ObjectCommandRequest; import org.commonreality.message.request.object.ObjectDataRequest; import org.commonreality.object.IEfferentObject; import org.commonreality.object.IMutableObject; import org.commonreality.object.delta.DeltaTracker; import org.commonreality.object.delta.FullObjectDelta; import org.commonreality.object.delta.IObjectDelta; import org.commonreality.object.identifier.ISensoryIdentifier; import org.commonreality.object.manager.event.IObjectEvent; /** * abstract class used to manage specific efferent commands.Extending this class * will give you access to callbacks for the state changes of the commands. This * manager allows you to create and submit new commands ( * {@link #newCommand(IEfferentObject, Object...)} request changes ( * {@link #request(DeltaTracker, org.commonreality.efferent.IEfferentCommand.RequestedState)} * , execute and abort ({@link #execute(DeltaTracker)} * {@link #abort(DeltaTracker)}), and upon completion (typically called from * {@link #commandCompleted(IEfferentCommand)} and * {@link #commandAborted(IEfferentCommand)}), the ability to remove the * command. * * @author harrison * @param <C> */ public abstract class EfferentCommandManager<C extends IEfferentCommand> { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(EfferentCommandManager.class); private IAgent _agent; /** * commands that have been accepted */ final private Map<IIdentifier, IndividualCommandManager<C>> _managedCommands; /** * internally requested aborts */ final private Set<IIdentifier> _requestedAbortions; final private Set<IIdentifier> _currentExecutions; /** * most recent request */ final private Map<IIdentifier, FutureCommand> _futureCommands; /** * automatically delete completed commands */ private boolean _autoDeleteEnabled = false; final private ReentrantReadWriteLock _lock = new ReentrantReadWriteLock(); final private IEfferentCommandListener _listener; final private Executor _executor; public EfferentCommandManager(Executor executor) { _futureCommands = new HashMap<IIdentifier, FutureCommand>(); _managedCommands = new HashMap<IIdentifier, IndividualCommandManager<C>>(); _currentExecutions = new HashSet<IIdentifier>(); _requestedAbortions = new HashSet<IIdentifier>(); _executor = executor; _listener = new IEfferentCommandListener() { public void objectsAdded(IObjectEvent<IEfferentCommand, ?> addEvent) { FastList<IEfferentCommand> valid = FastList.newInstance(); try { _lock.readLock().lock(); for (IEfferentCommand command : addEvent.getObjects()) if (isInterestedIn(command)) valid.add(command); } finally { _lock.readLock().unlock(); } /* * fire the events outside of the lock */ for (IEfferentCommand command : valid) update((C) command); FastList.recycle(valid); } public void objectsRemoved(IObjectEvent<IEfferentCommand, ?> removeEvent) { FastList<C> valid = FastList.newInstance(); FastSet<C> abortions = FastSet.newInstance(); try { _lock.readLock().lock(); for (IEfferentCommand command : removeEvent.getObjects()) if (isInterestedIn(command)) { if (_requestedAbortions.contains(command.getIdentifier())) abortions.add((C) command); valid.add((C) command); } } finally { _lock.readLock().unlock(); } /* * signal out of lock */ for (C command : valid) { update(command); setAndRelease(command, null); /* * signal any incomplete abortions.. */ if (abortions.contains(command)) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String .format("%s was aborted on request, delivering late message", command)); commandAborted(command, true); } if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Removed %s", command)); commandRemoved(command); } /* * once more to remove */ try { _lock.writeLock().lock(); for (C command : valid) { IIdentifier id = command.getIdentifier(); _currentExecutions.remove(id); _requestedAbortions.remove(id); _managedCommands.remove(id); } } finally { _lock.writeLock().unlock(); } FastSet.recycle(abortions); FastList.recycle(valid); } public void objectsUpdated(IObjectEvent<IEfferentCommand, ?> updateEvent) { FastList<IEfferentCommand> valid = FastList.newInstance(); try { _lock.readLock().lock(); for (IObjectDelta delta : updateEvent.getDeltas()) { IIdentifier id = delta.getIdentifier(); if (isInterestedIn(id) && delta.getChangedProperties().contains( IEfferentCommand.ACTUAL_STATE)) { IEfferentCommand command = _agent.getEfferentCommandManager() .get(delta.getIdentifier()); if (command != null) valid.add(command); } } } finally { _lock.readLock().unlock(); } for (IEfferentCommand command : valid) update((C) command); FastList.recycle(valid); } }; } protected ReentrantReadWriteLock getLock() { return _lock; } public void install(IAgent agent) { try { _lock.writeLock().lock(); _agent = agent; _managedCommands.clear(); _futureCommands.clear(); } finally { _lock.writeLock().unlock(); } agent.getEfferentCommandManager().addListener(_listener, _executor); } public void uninstall(IAgent agent) { agent.getEfferentCommandManager().removeListener(_listener); try { _lock.writeLock().lock(); _agent = null; _managedCommands.clear(); _futureCommands.clear(); } finally { _lock.writeLock().unlock(); } } protected IndividualCommandManager<C> getIndividualManager( IIdentifier commandId) { try { _lock.readLock().lock(); return _managedCommands.get(commandId); } finally { _lock.readLock().unlock(); } } public IAgent getAgent() { return _agent; } public IEfferentObject getMuscle(IIdentifier muscleId) { return _agent.getEfferentObjectManager().get(muscleId); } public C getCommand(IIdentifier commandId) { return (C) _agent.getEfferentCommandManager().get(commandId); } public boolean isAutoDeleteEnabled() { return _autoDeleteEnabled; } public void setAutoDeleteEnabled(boolean enabled) { _autoDeleteEnabled = enabled; } /** * explicitly request that executing commands are aborted, and issue a remove * for all the other commands. All pending futures will return an * IllegalStateException. No callbacks for active or pending commands will be * called after this. */ public void clear() { FastSet<C> commands = FastSet.newInstance(); try { _lock.writeLock().lock(); /* * abort executing commands */ for (C command : getExecutingCommands(commands)) if (!_requestedAbortions.contains(command.getIdentifier())) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "Requesting abort of executing command %s", command)); DeltaTracker tracker = new DeltaTracker(command); abort(tracker); } /** * remove and set all the futures */ IllegalStateException ise = new IllegalStateException( "Command is invalid since efferent command system has been cleared"); for (IndividualCommandManager<C> manager : _managedCommands.values()) { C command = manager.getCommand(); if (command != null) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Requesting removal of %s", command)); remove(command); setAndRelease(command, ise); } } } finally { FastSet.recycle(commands); _lock.writeLock().unlock(); } } final public boolean isExecuting() { try { _lock.readLock().lock(); return _currentExecutions.size() != 0; } finally { _lock.readLock().unlock(); } } /** * @param container * @return */ final public Set<IIdentifier> getExecutingCommandIds( Set<IIdentifier> container) { if (container == null) container = new HashSet<IIdentifier>(); try { _lock.readLock().lock(); container.addAll(_currentExecutions); } finally { _lock.readLock().unlock(); } return container; } /** * return all the commands that are currently executing * * @param container * @return */ final public Collection<C> getExecutingCommands(Collection<C> container) { if (container == null) container = new FastList<C>(); try { _lock.readLock().lock(); for (IIdentifier id : _currentExecutions) { IndividualCommandManager<C> manager = _managedCommands.get(id); if (manager != null && manager.isRunning()) container.add(manager.getCommand()); } } finally { _lock.readLock().unlock(); } return container; } /** * creates and submits the command, w/o using the future.. * * @param object * @param parameters * @return */ final protected C newCommandInternal(IEfferentObject object, Object... parameters) { IEfferentCommand tmpCommand = createCommand(object, parameters); IIdentifier id = tmpCommand.getIdentifier(); try { _lock.writeLock().lock(); _managedCommands.put(id, new IndividualCommandManager<C>(this, id)); _agent.send(new ObjectDataRequest(_agent.getIdentifier(), object .getIdentifier().getSensor(), Collections .singleton(new FullObjectDelta(tmpCommand)))); _agent.send(new ObjectCommandRequest(_agent.getIdentifier(), object .getIdentifier().getSensor(), IObjectCommand.Type.ADDED, Collections .singleton(id))); } catch (RuntimeException e) { _managedCommands.remove(id); tmpCommand = null; throw e; } finally { _lock.writeLock().unlock(); } return (C) tmpCommand; } /** * create and submit a new command. parameters will be passed on to * {@link #createCommand(IEfferentObject, Object...)} and can be used in * anyway desired. <br> * The future returned will block until an exception occurs, or the command is * accepted or rejected. * * @param object * @param parameters * @return */ final public Future<C> newCommand(IEfferentObject object, Object... parameters) { FutureCommand future = new FutureCommand(); try { _lock.writeLock().lock(); C command = newCommandInternal(object, parameters); if (command != null) _futureCommands.put(command.getIdentifier(), future); else future.setResult(null); } finally { _lock.writeLock().unlock(); } return future; } /** * request that the command be executed * * @param commandChange * @return */ public Future<C> execute(DeltaTracker<? extends IMutableObject> commandChange) { return request(commandChange, IEfferentCommand.RequestedState.START); } /** * request that the command be aborted * * @param commandChange * @return */ public Future<C> abort(DeltaTracker<? extends IMutableObject> commandChange) { _requestedAbortions.add(commandChange.getIdentifier()); return request(commandChange, IEfferentCommand.RequestedState.ABORT); } protected Future<C> request( DeltaTracker<? extends IMutableObject> commandChange, IEfferentCommand.RequestedState requestedState) { IIdentifier id = commandChange.getIdentifier(); FutureCommand future = new FutureCommand(); commandChange.setProperty(IEfferentCommand.REQUESTED_STATE, requestedState); try { _lock.writeLock().lock(); _futureCommands.put(id, future); send(commandChange); } finally { _lock.writeLock().unlock(); } return future; } protected void send(DeltaTracker<? extends IMutableObject> commandUpdate) { IIdentifier id = commandUpdate.getIdentifier(); _agent.send(new ObjectDataRequest(_agent.getIdentifier(), ((ISensoryIdentifier) id).getSensor(), Collections .singleton(commandUpdate.getDelta()))); _agent.send(new ObjectCommandRequest(_agent.getIdentifier(), ((ISensoryIdentifier) id).getSensor(), IObjectCommand.Type.UPDATED, Collections.singleton(id))); } /** * request removal * * @param command */ final public void remove(C command) { _agent.send(new ObjectCommandRequest(_agent.getIdentifier(), command .getIdentifier().getSensor(), IObjectCommand.Type.REMOVED, Collections .singleton((IIdentifier) command.getIdentifier()))); } /** * will find the future associated with this command, set its return value, * release and remove it from the collection of futures * * @param command */ private void setAndRelease(C command, Throwable exception) { FutureCommand future = null; try { _lock.writeLock().lock(); future = _futureCommands.remove(command.getIdentifier()); } finally { _lock.writeLock().unlock(); } if (future != null) if (exception == null) future.setResult(command); else future.setException(exception); } protected boolean isInterestedIn(IEfferentCommand command) { return isInterestedIn(command.getIdentifier()); } /** * tests the identifier to see if it is pending, future or managed * * @param identifier * @return */ private boolean isInterestedIn(IIdentifier identifier) { try { _lock.readLock().lock(); return _managedCommands.containsKey(identifier); } finally { _lock.readLock().unlock(); } } private void update(C command) { boolean processed = true; boolean removalCandidate = false; boolean wasRequested = false; IIdentifier commandIdentifier = command.getIdentifier(); if (LOGGER.isDebugEnabled()) LOGGER.debug(commandIdentifier + " : " + command.getActualState()); boolean shouldSignal = false; try { _lock.writeLock().lock(); IndividualCommandManager<C> manager = _managedCommands .get(commandIdentifier); if (shouldSignal = manager.update(command)) switch (command.getActualState()) { case ACCEPTED: break; case REJECTED: break; case RUNNING: _currentExecutions.add(commandIdentifier); break; case ABORTED: wasRequested = _requestedAbortions.remove(commandIdentifier); _currentExecutions.remove(commandIdentifier); removalCandidate = true; break; case COMPLETED: _currentExecutions.remove(commandIdentifier); removalCandidate = true; break; case UNKNOWN: break; default: if (LOGGER.isWarnEnabled()) LOGGER.warn("No clue how to handle command " + command + "." + command.getActualState()); processed = false; break; } } finally { _lock.writeLock().unlock(); } if (processed) { setAndRelease(command, null); /* * signal */ if (shouldSignal) switch (command.getActualState()) { case ACCEPTED: commandAccepted(command); break; case REJECTED: commandRejected(command); break; case RUNNING: commandRunning(command); break; case COMPLETED: commandCompleted(command); break; case ABORTED: commandAborted(command, wasRequested); break; case UNKNOWN: break; } } if (removalCandidate && isAutoDeleteEnabled()) remove(command); } /** * create the command using the provided efferent object, from which a * template should be extracted and instantiated * * @param object * @return */ abstract protected C createCommand(IEfferentObject object, Object... parameters); /** * call back executed on the CR executor * * @param command */ abstract protected void commandAccepted(C command); /** * call back executed on the CR executor * * @param command */ abstract protected void commandRejected(C command); /** * call back executed on the CR executor * * @param command */ abstract protected void commandRunning(C command); /** * call back executed on the CR executor * * @param command */ abstract protected void commandAborted(C command, boolean wasRequested); /** * call back executed on the CR executor * * @param command */ abstract protected void commandCompleted(C command); /** * call back executed on the CR executor * * @param command */ protected void commandRemoved(C command) { } private class FutureCommand extends FutureTask<C> { public FutureCommand() { super(new Runnable() { public void run() { } }, null); } public void setResult(C command) { set(command); } @Override public void setException(Throwable thrown) { super.setException(thrown); } } }