package rescuecore2.components; import rescuecore2.connection.Connection; import rescuecore2.connection.ConnectionListener; import rescuecore2.connection.ConnectionException; import rescuecore2.messages.Message; import rescuecore2.messages.Command; import rescuecore2.messages.control.KASense; import rescuecore2.messages.control.AKConnect; import rescuecore2.messages.control.AKAcknowledge; import rescuecore2.messages.control.KAConnectOK; import rescuecore2.messages.control.KAConnectError; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; import rescuecore2.worldmodel.WorldModel; import rescuecore2.worldmodel.ChangeSet; import rescuecore2.config.Config; import java.util.Collection; import java.util.concurrent.CountDownLatch; /** Abstract base class for agent implementations. @param <T> The subclass of WorldModel that this agent understands. @param <E> The subclass of Entity that this agent wants to control. */ public abstract class AbstractAgent<T extends WorldModel<? extends Entity>, E extends Entity> extends AbstractComponent<T> implements Agent { /** The ID of the entity controlled by this agent. */ private EntityID entityID; /** Create a new AbstractAgent. */ protected AbstractAgent() { config = new Config(); } @Override public final void postConnect(Connection c, EntityID agentID, Collection<Entity> entities, Config kernelConfig) { this.entityID = agentID; super.postConnect(c, entities, kernelConfig); } @Override public EntityID getID() { return entityID; } @Override public void connect(Connection connection, RequestIDGenerator generator, Config config) throws ConnectionException, ComponentConnectionException, InterruptedException { this.config = config; int requestID = generator.generateRequestID(); AKConnect connect = new AKConnect(requestID, 1, getName(), getRequestedEntityURNs()); CountDownLatch latch = new CountDownLatch(1); AgentConnectionListener l = new AgentConnectionListener(requestID, latch); connection.addConnectionListener(l); connection.sendMessage(connect); // Wait for a reply latch.await(); l.testSuccess(); } @Override protected void postConnect() { super.postConnect(); } @Override protected String getPreferredNDC() { if (me() != null) { return me().toString(); } return null; } /** Notification that a timestep has started. @param time The timestep. @param changes The set of changes observed this timestep. @param heard The set of communication messages this agent heard. */ protected abstract void think(int time, ChangeSet changes, Collection<Command> heard); /** Process an incoming sense message. The default implementation updates the world model and calls {@link #think}. Subclasses should generally not override this method but instead implement the {@link #think} method. @param sense The sense message. */ protected void processSense(KASense sense) { model.merge(sense.getChangeSet()); Collection<Command> heard = sense.getHearing(); think(sense.getTime(), sense.getChangeSet(), heard); } /** Get the entity controlled by this agent. @return The entity controlled by this agent. */ @SuppressWarnings("unchecked") protected E me() { if (entityID == null) { return null; } if (model == null) { return null; } return (E)model.getEntity(entityID); } @Override protected void processMessage(Message msg) { if (msg instanceof KASense) { KASense sense = (KASense)msg; if (entityID.equals(sense.getAgentID())) { processSense(sense); } } else { super.processMessage(msg); } } private class AgentConnectionListener implements ConnectionListener { private int requestID; private CountDownLatch latch; private ComponentConnectionException failureReason; public AgentConnectionListener(int requestID, CountDownLatch latch) { this.requestID = requestID; this.latch = latch; failureReason = null; } @Override public void messageReceived(Connection c, Message msg) { if (msg instanceof KAConnectOK) { handleConnectOK(c, (KAConnectOK)msg); } if (msg instanceof KAConnectError) { handleConnectError(c, (KAConnectError)msg); } } private void handleConnectOK(Connection c, KAConnectOK ok) { if (ok.getRequestID() == requestID) { c.removeConnectionListener(this); postConnect(c, ok.getAgentID(), ok.getEntities(), ok.getConfig()); try { c.sendMessage(new AKAcknowledge(requestID, ok.getAgentID())); } catch (ConnectionException e) { failureReason = new ComponentConnectionException(e); } latch.countDown(); } } private void handleConnectError(Connection c, KAConnectError error) { if (error.getRequestID() == requestID) { c.removeConnectionListener(this); failureReason = new ComponentConnectionException(error.getReason()); latch.countDown(); } } /** Check if the connection succeeded and throw an exception if is has not. */ void testSuccess() throws ComponentConnectionException { if (failureReason != null) { throw failureReason; } } } }