package jalse.entities;
import static jalse.attributes.Attributes.EMPTY_ATTRIBUTECONTAINER;
import static jalse.entities.Entities.asType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Stream;
import jalse.attributes.AttributeContainer;
import jalse.misc.ListenerSet;
/**
* An DefaultEntityContainer is a thread-safe implementation of {@link EntityContainer}. <br>
* <br>
*
* DefaultEntityContainer can take a delegate container to supply to {@link EntityEvent}. Entity
* updates will trigger these events using {@link EntityListener}.<br>
* <br>
*
* By default DefaultEntityContainer will use {@link DefaultEntityFactory} with no delegate
* container.
*
* @author Elliot Ford
*
*/
public class DefaultEntityContainer implements EntityContainer {
/**
* A {@link DefaultEntityContainer} instance builder that uses the provided
* {@link EntityFactory} and delegate {@link EntityContainer}.<br>
*
* @author Dennis Ting
*
*/
public static final class Builder {
private class EntityStub {
private final UUID id;
private final Class<? extends Entity> type;
private final AttributeContainer sourceContainer;
private EntityStub(final UUID id, final Class<? extends Entity> type,
final AttributeContainer sourceContainer) {
this.id = Objects.requireNonNull(id);
this.type = type;
this.sourceContainer = sourceContainer != null ? sourceContainer : EMPTY_ATTRIBUTECONTAINER;
}
}
private final Set<EntityListener> builderListeners;
private final List<EntityStub> builderEntities;
private EntityFactory builderFactory;
private EntityContainer builderDelegateContainer;
/**
* Creates a new Builder instance.
*/
public Builder() {
builderListeners = new HashSet<>();
builderEntities = new ArrayList<>();
builderFactory = null;
builderDelegateContainer = null;
}
/**
* Adds new EntityListener.
*
* @param listener
* EntityListener.
* @return This builder.
*/
public Builder addListener(final EntityListener listener) {
builderListeners.add(Objects.requireNonNull(listener));
return this;
}
/**
* Builds an instance of DefaultEntityContainer with the supplied parameters.
*
* @return Newly created DefaultEntityContainer instance.
*/
public DefaultEntityContainer build() {
final EntityFactory factory = builderFactory != null ? builderFactory : new DefaultEntityFactory();
final DefaultEntityContainer container = new DefaultEntityContainer(factory, builderDelegateContainer,
builderListeners);
builderEntities.forEach(e -> container.newEntity0(e.id, e.type, e.sourceContainer));
return container;
}
/**
* Adds an Entity to be created when building.
*
* @param id
* ID for Entity.
* @return This builder.
*/
public Builder newEntity(final UUID id) {
builderEntities.add(new EntityStub(id, null, null));
return this;
}
/**
* Adds an Entity to be created when building.
*
* @param id
* ID for Entity.
* @param sourceContainer
* AttributeContainer for entity.
* @return This builder.
*/
public Builder newEntity(final UUID id, final AttributeContainer sourceContainer) {
builderEntities.add(new EntityStub(id, null, Objects.requireNonNull(sourceContainer)));
return this;
}
/**
* Adds an Entity to be created when building.
*
* @param id
* ID for Entity.
* @param type
* Class for Entity.
* @return This builder.
*/
public <T extends Entity> Builder newEntity(final UUID id, final Class<T> type) {
builderEntities.add(new EntityStub(id, Objects.requireNonNull(type), null));
return this;
}
/**
* Adds an Entity to be created when building.
*
* @param id
* ID for Entity.
* @param type
* Class for Entity.
* @param sourceContainer
* AttributeContainer for entity.
* @return This builder.
*/
public <T extends Entity> Builder newEntity(final UUID id, final Class<T> type,
final AttributeContainer sourceContainer) {
builderEntities
.add(new EntityStub(id, Objects.requireNonNull(type), Objects.requireNonNull(sourceContainer)));
return this;
}
/**
* Sets DelegateContainer.
*
* @param delegateContainer
* Delegate container for events and entity creation.
* @return This builder.
*/
public Builder setDelegateContainer(final EntityContainer delegateContainer) {
builderDelegateContainer = Objects.requireNonNull(delegateContainer);
return this;
}
/**
* Sets EntityFactory.
*
* @param factory
* Entity creation/death factory.
* @return This builder.
*/
public Builder setFactory(final EntityFactory factory) {
builderFactory = Objects.requireNonNull(factory);
return this;
}
}
private final Map<UUID, Entity> entities;
private final ListenerSet<EntityListener> listeners;
private final EntityFactory factory;
private final EntityContainer delegateContainer;
private final Lock read;
private final Lock write;
/**
* Creates an entity container with the default entity factory and no delegate container.
*
*/
public DefaultEntityContainer() {
this(new DefaultEntityFactory());
}
/**
* Creates an entity container with the supplied factory and no delegate container.
*
* @param factory
* Entity creation/death factory.
*/
public DefaultEntityContainer(final EntityFactory factory) {
this(factory, null, null);
}
/**
* Creates an entity container with the supplied factory and delegate container.
*
* @param factory
* Entity creation/death factory.
* @param delegateContainer
* Delegate container for events and entity creation.
*/
public DefaultEntityContainer(final EntityFactory factory, final EntityContainer delegateContainer) {
this(factory, Objects.requireNonNull(delegateContainer), null);
}
private DefaultEntityContainer(final EntityFactory factory, final EntityContainer delegateContainer,
final Set<EntityListener> listeners) {
this.factory = Objects.requireNonNull(factory);
this.delegateContainer = delegateContainer != null ? delegateContainer : this;
entities = new HashMap<>();
this.listeners = new ListenerSet<>(EntityListener.class);
if (listeners != null) {
this.listeners.addAll(listeners);
}
final ReadWriteLock rwLock = new ReentrantReadWriteLock();
read = rwLock.readLock();
write = rwLock.writeLock();
}
@Override
public boolean addEntityListener(final EntityListener listener) {
Objects.requireNonNull(listener);
write.lock();
try {
return listeners.add(listener);
} finally {
write.unlock();
}
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof DefaultEntityContainer)) {
return false;
}
final DefaultEntityContainer other = (DefaultEntityContainer) obj;
return entities.equals(other.entities) && listeners.equals(other.listeners);
}
/**
* Gets the delegate container for events and entity creation.
*
* @return Delegate container.
*/
public EntityContainer getDelegateContainer() {
return delegateContainer;
}
@Override
public Entity getEntity(final UUID id) {
Objects.requireNonNull(id);
read.lock();
try {
return entities.get(id);
} finally {
read.unlock();
}
}
@Override
public int getEntityCount() {
read.lock();
try {
return entities.size();
} finally {
read.unlock();
}
}
@Override
public Set<UUID> getEntityIDs() {
read.lock();
try {
return new HashSet<>(entities.keySet());
} finally {
read.unlock();
}
}
@Override
public Set<? extends EntityListener> getEntityListeners() {
read.lock();
try {
return new HashSet<>(listeners);
} finally {
read.unlock();
}
}
/**
* Gets entity factory for this set.
*
* @return Entity creation / death factory.
*/
public EntityFactory getFactory() {
return factory;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + entities.hashCode();
result = prime * result + listeners.hashCode();
return result;
}
@Override
public void killEntities() {
write.lock();
try {
new ArrayList<>(entities.keySet()).forEach(this::killEntity);
} finally {
write.unlock();
}
}
@Override
public boolean killEntity(final UUID id) {
Objects.requireNonNull(id);
write.lock();
try {
final Entity e = entities.get(id);
if (e == null || !factory.tryKillEntity(e)) {
return false;
}
entities.remove(id);
listeners.getProxy().entityKilled(new EntityEvent(delegateContainer, e));
return true;
} finally {
write.unlock();
}
}
@Override
public Entity newEntity(final UUID id, final AttributeContainer sourceContainer) {
return newEntity0(id, null, sourceContainer);
}
@Override
public <T extends Entity> T newEntity(final UUID id, final Class<T> type,
final AttributeContainer sourceContainer) {
Objects.requireNonNull(type);
return asType(newEntity0(id, type, sourceContainer), type);
}
private Entity newEntity0(final UUID id, final Class<? extends Entity> type,
final AttributeContainer sourceContainer) {
Objects.requireNonNull(id);
Objects.requireNonNull(sourceContainer);
write.lock();
try {
Entity e = entities.get(id);
if (e != null) {
throw new IllegalArgumentException(String.format("Entity %s is already associated", id));
}
e = factory.newEntity(id, delegateContainer);
entities.put(id, e);
if (type != null) {
e.markAsType(type);
}
e.addAll(sourceContainer);
listeners.getProxy().entityCreated(new EntityEvent(delegateContainer, e));
return e;
} finally {
write.unlock();
}
}
@Override
public boolean receiveEntity(final Entity e) {
if (Objects.equals(delegateContainer, Objects.requireNonNull(e))) {
throw new IllegalArgumentException(String.format("Cannot transfer %s to itself", e.getID()));
}
write.lock();
try {
final UUID id = e.getID();
if (entities.containsKey(id)) {
return false;
}
boolean imported = false;
if (!factory.tryTakeFromTree(e, delegateContainer)) {
if (!factory.tryImportEntity(e, delegateContainer)) {
return false;
}
imported = true;
}
entities.put(id, e);
if (imported) { // Otherwise transfer is triggered.
listeners.getProxy().entityReceived(new EntityEvent(delegateContainer, e));
}
return true;
} finally {
write.unlock();
}
}
@Override
public boolean removeEntityListener(final EntityListener listener) {
write.lock();
try {
return listeners.remove(listener);
} finally {
write.unlock();
}
}
@Override
public void removeEntityListeners() {
write.lock();
try {
listeners.clear();
} finally {
write.unlock();
}
}
@Override
public Stream<Entity> streamEntities() {
read.lock();
try {
return new ArrayList<>(entities.values()).stream();
} finally {
read.unlock();
}
}
@Override
public String toString() {
return "DefaultEntityContainer [" + getEntityIDs() + "]";
}
@Override
public boolean transferEntity(final UUID id, final EntityContainer destination) {
Objects.requireNonNull(id);
if (Objects.equals(delegateContainer, Objects.requireNonNull(destination))) {
throw new IllegalArgumentException(String.format("Cannot transfer %s to the same container", id));
}
write.lock();
try {
final Entity e = entities.get(id);
if (e == null) {
return false;
}
if (Objects.equals(e, destination)) {
throw new IllegalArgumentException(String.format("Cannot transfer %s to itself", id));
}
boolean exported = false;
if (!factory.withinSameTree(delegateContainer, destination)) {
factory.exportEntity(e);
exported = true;
}
if (!destination.receiveEntity(e)) {
if (exported) {
throw new IllegalStateException(String.format("Entity %s exported but not transferred", id));
}
return false;
}
entities.remove(id);
listeners.getProxy().entityTransferred(new EntityEvent(delegateContainer, e, destination));
return true;
} finally {
write.unlock();
}
}
}