/*
* $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.sarl.lang.core;
import java.security.InvalidParameterException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject;
import com.google.common.reflect.TypeToken;
import org.eclipse.xtext.xbase.lib.Inline;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
import org.eclipse.xtext.xbase.lib.Pure;
import io.sarl.lang.SARLVersion;
import io.sarl.lang.annotation.SarlSpecification;
import io.sarl.lang.util.ClearableReference;
/**
* The definition of the notion of Agent in SARL.
* An agent is an autonomous entity having some intrinsic skills to realize
* the capacities it exhibits. An agent defines a context.
*
* @author $Author: srodriguez$
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
@SarlSpecification(SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING)
public class Agent extends AgentProtectedAPIObject implements Identifiable {
private final UUID id;
private final Map<Class<? extends Capacity>, ClearableReference<Skill>> skills = new ConcurrentHashMap<>();
private final UUID parentID;
/**
* Creates a new agent with a parent <code>parentID</code> and initialize the built-in capacity
* with the given provider.
*
* @param provider - the provider of built-in capacities for this agent. If <code>null</code>, the builtin
* capacities will not be initialized.
* @param parentID - the agent's parent.
* @param agentID - the identifier of the agent, or
* <code>null</code> for computing it randomly.
*/
@Inject
public Agent(
BuiltinCapacitiesProvider provider,
UUID parentID,
UUID agentID) {
this.parentID = parentID;
this.id = (agentID == null) ? UUID.randomUUID() : agentID;
if (provider != null) {
final Map<Class<? extends Capacity>, Skill> builtinCapacities = provider.getBuiltinCapacities(this);
if (builtinCapacities != null && !builtinCapacities.isEmpty()) {
for (final Entry<Class<? extends Capacity>, Skill> bic : builtinCapacities.entrySet()) {
mapCapacity(bic.getKey(), bic.getValue());
}
}
}
}
/**
* Creates a new agent with a parent <code>parentID</code> without initializing the built-in capacities.
*
* @param parentID - the agent's spawner.
* @param agentID - the identifier of the agent, or
* <code>null</code> for computing it randomly.
* @since 0.5
*/
public Agent(
UUID parentID,
UUID agentID) {
this(null, parentID, agentID);
}
private ClearableReference<Skill> mapCapacity(Class<? extends Capacity> capacity, Skill skill) {
return this.skills.put(capacity, new ClearableReference<>(skill));
}
@Override
protected String attributesToString() {
final StringBuilder builder = new StringBuilder();
builder.append("id = "); //$NON-NLS-1$
builder.append(this.id);
builder.append(", parentID="); //$NON-NLS-1$
builder.append(this.parentID);
return builder.toString();
}
@Override
@Pure
public String toString() {
return getClass().getSimpleName()
+ " [" + attributesToString() //$NON-NLS-1$
+ "]"; //$NON-NLS-1$
}
/**
* Replies the agent's parent's ID.
*
* @return the identifier of the agent's parent.
*/
@Pure
public UUID getParentID() {
return this.parentID;
}
@Override
@Pure
public UUID getID() {
return this.id;
}
/**
* Set the skill for the {@link Capacity} <code>capacity</code>.
*
* @param <S> - type of the skill.
* @param capacity capacity to set.
* @param skill implementaion of <code>capacity</code>.
* @return the skill that was set.
* @deprecated since 0.4, see {@link #setSkill(Skill, Class...)}
*/
@Inline("setSkill($2, $1)")
@Deprecated
protected <S extends Skill> S setSkill(Class<? extends Capacity> capacity, S skill) {
return setSkill(skill, capacity);
}
@Override
@SafeVarargs
protected final <S extends Skill> S setSkill(S skill, Class<? extends Capacity>... capacities) {
assert skill != null : "the skill parameter must not be null"; //$NON-NLS-1$
skill.setOwner(this);
if (capacities == null || capacities.length == 0) {
runOnImplementedCapacities(skill, (capacity) -> {
final ClearableReference<Skill> oldS = mapCapacity(capacity, skill);
skill.registerUse();
if (oldS != null) {
final Skill oldSkill = oldS.clear();
if (oldSkill != null && oldSkill != skill) {
oldSkill.unregisterUse();
}
}
});
} else {
for (final Class<? extends Capacity> capacity : capacities) {
assert capacity != null : "the capacity parameter must not be null"; //$NON-NLS-1$
assert capacity.isInterface() : "the capacity parameter must be an interface"; //$NON-NLS-1$
if (!capacity.isInstance(skill)) {
throw new InvalidParameterException(
"the skill must implement the given capacity " //$NON-NLS-1$
+ capacity.getName());
}
final ClearableReference<Skill> oldS = mapCapacity(capacity, skill);
skill.registerUse();
if (oldS != null) {
final Skill oldSkill = oldS.clear();
if (oldSkill != null && oldSkill != skill) {
oldSkill.unregisterUse();
}
}
}
}
return skill;
}
private static void runOnImplementedCapacities(Skill skill, Procedure1<Class<? extends Capacity>> callback) {
TypeToken.of(skill.getClass()).getTypes().interfaces().stream().parallel().forEach((it) -> {
final Class<?> type = it.getRawType();
if (Capacity.class.isAssignableFrom(type) && !Capacity.class.equals(type)) {
callback.apply(type.asSubclass(Capacity.class));
}
});
}
@Override
@Inline("setSkill($2, $1)")
protected <S extends Skill> void operator_mappedTo(Class<? extends Capacity> capacity, S skill) {
setSkill(skill, capacity);
}
@Override
protected <S extends Capacity> S clearSkill(Class<S> capacity) {
assert capacity != null;
final ClearableReference<Skill> reference = this.skills.remove(capacity);
if (reference != null) {
final Skill skill = reference.clear();
if (skill != null) {
skill.unregisterUse();
return capacity.cast(skill);
}
}
return null;
}
@Override
@Pure
protected final <S extends Capacity> S getSkill(Class<S> capacity) {
assert capacity != null;
return $castSkill(capacity, $getSkill(capacity));
}
/** Cast the skill reference to the given capacity type.
*
* @param <S> the expected capacity type.
* @param capacity the expected capacity type.
* @param skillReference the skill reference.
* @return the skill casted to the given capacity.
*/
@Pure
protected <S extends Capacity> S $castSkill(Class<S> capacity, ClearableReference<Skill> skillReference) {
final S skill = capacity.cast(skillReference.get());
if (skill == null) {
throw new UnimplementedCapacityException(capacity, getID());
}
return skill;
}
@Override
@Pure
protected ClearableReference<Skill> $getSkill(Class<? extends Capacity> capacity) {
final ClearableReference<Skill> skill = this.skills.get(capacity);
if (skill == null) {
throw new UnimplementedCapacityException(capacity, getID());
}
return skill;
}
@Override
@Pure
protected boolean hasSkill(Class<? extends Capacity> capacity) {
assert capacity != null;
return this.skills.containsKey(capacity);
}
@Override
@Pure
protected boolean isMe(Address address) {
return (address != null) && (this.id.equals(address.getUUID()));
}
@Override
@Pure
protected boolean isMe(UUID uID) {
return (uID != null) && (this.id.equals(uID));
}
@Override
@Pure
protected boolean isFromMe(Event event) {
return (event != null) && isMe(event.getSource());
}
}