/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.janusproject.kernel.bic;
import java.util.Collection;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import com.google.common.base.Throwables;
import com.google.common.collect.Queues;
import com.google.inject.Inject;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import io.janusproject.kernel.bic.internaleventdispatching.AgentInternalEventsDispatcher;
import io.janusproject.services.logging.LogService;
import io.janusproject.services.spawn.SpawnService;
import io.sarl.core.AgentSpawned;
import io.sarl.core.Destroy;
import io.sarl.core.Initialize;
import io.sarl.core.Logging;
import io.sarl.lang.core.Address;
import io.sarl.lang.core.Agent;
import io.sarl.lang.core.Event;
import io.sarl.lang.core.EventListener;
import io.sarl.lang.core.Skill;
import io.sarl.lang.util.ClearableReference;
/**
* Janus implementation of an internal skill that provides an event dispatcher to notify the different components/behaviors of an
* agent.
*
* @author $Author: srodriguez$
* @author $Author: ngaud$
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public class InternalEventBusSkill extends BuiltinSkill implements InternalEventBusCapacity {
private static int installationOrder = -1;
/**
* State of the owner.
*/
private final AtomicReference<OwnerState> ownerState = new AtomicReference<>(OwnerState.UNSTARTED);
/**
* Implementation of an EventListener linked to the owner of this skill.
*/
private final AgentEventListener agentAsEventListener;
/**
* Reference to the event dispatcher. It is the mean of routing of the events inside the context of an agent. The agent itself
* and the behaviors are connected to this dispatcher.
*/
@Inject
private AgentInternalEventsDispatcher eventDispatcher;
@Inject
private LogService logger;
@Inject
private SpawnService spawnService;
/**
* Address of the agent in the inner space.
*/
private final Address agentAddressInInnerDefaultSpace;
private ClearableReference<Skill> skillBufferLogging;
/**
* @param agent - reference to the owner of this skill.
* @param addressInInnerDefaultSpace - address of the owner of this skill in its inner default space.
*/
InternalEventBusSkill(Agent agent, Address addressInInnerDefaultSpace) {
super(agent);
this.agentAsEventListener = new AgentEventListener();
this.agentAddressInInnerDefaultSpace = addressInInnerDefaultSpace;
}
/** Replies the Logging skill as fast as possible.
*
* @return the skill
*/
protected final Logging getLoggingSkill() {
if (this.skillBufferLogging == null || this.skillBufferLogging.get() == null) {
this.skillBufferLogging = $getSkill(Logging.class);
}
return $castSkill(Logging.class, this.skillBufferLogging);
}
@Override
public int getInstallationOrder() {
if (installationOrder < 0) {
installationOrder = installationOrder(this);
}
return installationOrder;
}
@Override
protected String attributesToString() {
return super.attributesToString() + ", state = " + getOwnerState() //$NON-NLS-1$
+ ", addressInDefaultspace = " + this.agentAddressInInnerDefaultSpace; //$NON-NLS-1$
}
@Override
public OwnerState getOwnerState() {
return this.ownerState.get();
}
/** Change the owner state.
*
* @param state the state.
*/
void setOwnerState(OwnerState state) {
assert state != null;
this.ownerState.set(state);
}
@Override
public Address getInnerDefaultSpaceAddress() {
return this.agentAddressInInnerDefaultSpace;
}
@Override
protected void install() {
this.eventDispatcher.register(getOwner(), null, null);
}
@Override
protected void uninstall(UninstallationStage stage) {
if (stage == UninstallationStage.POST_DESTROY_EVENT) {
final Destroy event = new Destroy();
event.setSource(getInnerDefaultSpaceAddress());
this.eventDispatcher.unregisterAll((subscriber) -> {
if (subscriber != getOwner()) {
this.eventDispatcher.immediateDispatchTo(subscriber, event);
}
});
}
}
/**
* {@inheritDoc}
* @deprecated see {@link #registerEventListener(Object, boolean, Function1)}.
*/
@Override
@Deprecated
public void registerEventListener(Object listener) {
registerEventListener(listener, true, null);
}
@Override
public void registerEventListener(Object listener, boolean fireInitializeEvent, Function1<? super Event, ? extends Boolean> filter) {
if (fireInitializeEvent) {
final OwnerState state = getOwnerState();
if (state == OwnerState.INITIALIZING || state == OwnerState.ALIVE) {
this.eventDispatcher.register(listener, filter, (subscriber) -> {
final Initialize event = new Initialize(getOwner().getID());
event.setSource(getInnerDefaultSpaceAddress());
this.eventDispatcher.immediateDispatchTo(subscriber, event);
});
} else {
this.eventDispatcher.register(listener, filter, null);
}
} else {
this.eventDispatcher.register(listener, filter, null);
}
}
/**
* {@inheritDoc}
* @deprecated see {@link #unregisterEventListener(Object, boolean)}.
*/
@Override
@Deprecated
public void unregisterEventListener(Object listener) {
unregisterEventListener(listener, true);
}
@Override
public void unregisterEventListener(Object listener, boolean fireDestroyEvent) {
if (fireDestroyEvent) {
final OwnerState state = getOwnerState();
if (state == OwnerState.INITIALIZING || state == OwnerState.ALIVE) {
this.eventDispatcher.unregister(listener, (subscriber) -> {
final Destroy event = new Destroy();
event.setSource(getInnerDefaultSpaceAddress());
this.eventDispatcher.immediateDispatchTo(subscriber, event);
});
} else {
this.eventDispatcher.unregister(listener, null);
}
} else {
this.eventDispatcher.unregister(listener, null);
}
}
/** This function runs the initialization of the agent.
*
* @param event the {@link Initialize} occurrence.
*/
private void runInitializationStage(Event event) {
// Immediate synchronous dispatching of Initialize event
try {
setOwnerState(OwnerState.INITIALIZING);
try {
this.eventDispatcher.immediateDispatch(event);
} finally {
setOwnerState(OwnerState.ALIVE);
}
} catch (Exception e) {
// Log the exception
final Logging loggingCapacity = getLoggingSkill();
if (loggingCapacity != null) {
loggingCapacity.error(Messages.InternalEventBusSkill_3, e);
} else {
final LogRecord record = new LogRecord(Level.SEVERE, Messages.InternalEventBusSkill_3);
record.setThrown(Throwables.getRootCause(e));
this.logger.log(record);
}
// If we have an exception within the agent's initialization, we kill the agent.
setOwnerState(OwnerState.ALIVE);
// Asynchronous kill of the event.
this.agentAsEventListener.killOrMarkAsKilled();
}
}
/** This function runs the destruction of the agent.
*
* @param event the {@link Destroy} occurrence.
*/
private void runDestructionStage(Event event) {
// Immediate synchronous dispatching of Destroy event
try {
setOwnerState(OwnerState.DYING);
try {
this.eventDispatcher.immediateDispatch(event);
} finally {
setOwnerState(OwnerState.DEAD);
}
} catch (Exception e) {
// Log the exception
final Logging loggingCapacity = getLoggingSkill();
if (loggingCapacity != null) {
loggingCapacity.error(Messages.InternalEventBusSkill_4, e);
} else {
final LogRecord record = new LogRecord(Level.SEVERE, Messages.InternalEventBusSkill_4);
record.setThrown(Throwables.getRootCause(e));
this.logger.log(record);
}
}
}
@Override
public boolean hasRegisteredEventListener(Class<?> type) {
return this.eventDispatcher.hasRegisteredEventListener(type);
}
@Override
public <T> int getRegisteredEventListeners(Class<T> type, Collection<? super T> collection) {
return this.eventDispatcher.getRegisteredEventListeners(type, collection);
}
@Override
public void selfEvent(Event event) {
// Ensure that the event source is the agent itself!
event.setSource(getInnerDefaultSpaceAddress());
// If the event must be fired only by the
// agent itself, it is treated in this function.
// Otherwise, it is given to the asynchronous
// listener.
final Class<? extends Event> eventType = event.getClass();
if (Initialize.class.equals(eventType)) {
runInitializationStage(event);
} else if (Destroy.class.equals(eventType)) {
runDestructionStage(event);
} else if (AsynchronousAgentKillingEvent.class.equals(eventType)) {
// Asynchronous kill of the event.
this.agentAsEventListener.killOrMarkAsKilled();
} else if (getOwnerState().isEventHandling()) {
// Asynchronous parallel dispatching of this event
this.agentAsEventListener.receiveEvent(event);
}
//this.logger.debug(Messages.InternalEventBusSkill_0, event);
}
@Override
public final EventListener asEventListener() {
return this.agentAsEventListener;
}
/**
* Definition of the listener on events on the agent's bus.
*
* @author $Author: srodriguez$
* @author $Author: ngaud$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class AgentEventListener implements EventListener {
private Queue<Event> buffer = Queues.newConcurrentLinkedQueue();
private final UUID aid;
private final AtomicBoolean isKilled = new AtomicBoolean(false);
@SuppressWarnings("synthetic-access")
AgentEventListener() {
this.aid = InternalEventBusSkill.this.getOwner().getID();
}
@Override
public UUID getID() {
return this.aid;
}
@SuppressWarnings("synthetic-access")
@Override
public void receiveEvent(Event event) {
final Class<? extends Event> eventType = event.getClass();
assert (!Initialize.class.equals(eventType)) && !Destroy.class.equals(eventType)
&& !AsynchronousAgentKillingEvent.class.equals(eventType) : "Unsupported type of event: " + event; //$NON-NLS-1$
if (AgentSpawned.class.equals(eventType) && ((AgentSpawned) event).agentIdentifiers.contains(this.aid)) {
// This permits to ensure that the killing event
// is correctly treated when fired from the initialization
// handler.
fireEnqueuedEvents(InternalEventBusSkill.this);
if (this.isKilled.get()) {
killOwner(InternalEventBusSkill.this);
return;
}
}
switch (getOwnerState()) {
case UNSTARTED:
case INITIALIZING:
assert getOwnerState().isEventHandling();
this.buffer.add(event);
break;
case ALIVE:
assert getOwnerState().isEventHandling();
fireEnqueuedEvents(InternalEventBusSkill.this);
InternalEventBusSkill.this.eventDispatcher.asyncDispatch(event);
break;
case DYING:
case DEAD:
// Dropping messages since agent is dying
assert !getOwnerState().isEventHandling();
InternalEventBusSkill.this.logger.debug(Messages.InternalEventBusSkill_1, event);
break;
default:
throw new IllegalStateException();
}
}
@SuppressWarnings("synthetic-access")
private void fireEnqueuedEvents(InternalEventBusSkill skill) {
final Queue<Event> queue = this.buffer;
if (queue != null && !queue.isEmpty()) {
this.buffer = null;
for (final Event evt : queue) {
skill.eventDispatcher.asyncDispatch(evt);
}
}
}
@SuppressWarnings("synthetic-access")
private boolean killOwner(InternalEventBusSkill skill) {
return skill.spawnService.killAgent(this.aid);
}
boolean killOrMarkAsKilled() {
this.isKilled.set(true);
final OwnerState state = getOwnerState();
assert state != null;
if (state == OwnerState.ALIVE) {
return killOwner(InternalEventBusSkill.this);
}
return false;
}
}
}