package jalse.attributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
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.misc.ListenerSet;
/**
* An DefaultAttributeContainer is a thread-safe implementation of {@link AttributeContainer}.<br>
* <br>
*
* DefaultAttributeContainer can take a delegate AttributeContainer to supply to
* {@link AttributeEvent}. Attribute updates will trigger these events using
* {@link AttributeListener}.
*
* @author Elliot Ford
*
*/
public class DefaultAttributeContainer implements AttributeContainer {
/**
* Chaining builder for DefaultAttributeContainer.
*
* @author Elliot Ford
*
*/
public static final class Builder {
private final Map<NamedAttributeType<?>, Object> builderAttributes;
private final Map<NamedAttributeType<?>, Set<AttributeListener<?>>> builderListeners;
private AttributeContainer builderDelegateContainer;
/**
* Creates a new builder.
*/
public Builder() {
builderAttributes = new HashMap<>();
builderListeners = new HashMap<>();
builderDelegateContainer = null;
}
/**
* Adds an attribute listener.
*
* @param namedType
* Named attribute type.
* @param listener
* Listener to add.
* @return This builder.
*/
public <T> Builder addListener(final NamedAttributeType<T> namedType, final AttributeListener<T> listener) {
Objects.requireNonNull(namedType);
Objects.requireNonNull(listener);
Set<AttributeListener<?>> lst = builderListeners.get(namedType);
if (lst == null) {
lst = new HashSet<>();
builderAttributes.put(namedType, lst);
}
lst.add(listener);
return this;
}
/**
* Adds an attribute listener.
*
* @param name
* Attribute name.
* @param type
* Attribute type.
* @param listener
* Listener to add.
* @return This builder.
*/
public <T> Builder addListener(final String name, final AttributeType<T> type,
final AttributeListener<T> listener) {
return addListener(new NamedAttributeType<>(name, type), listener);
}
/**
* Builds the container.
*
* @return The new container.
*/
public DefaultAttributeContainer build() {
final DefaultAttributeContainer container = new DefaultAttributeContainer(builderAttributes,
builderListeners);
if (builderDelegateContainer != null) {
container.setDelegateContainer(builderDelegateContainer);
}
return container;
}
/**
* Sets an attribute value.
*
* @param namedType
* Named attribute type.
* @param value
* Value to set.
* @return This builder.
*/
public <T> Builder setAttribute(final NamedAttributeType<T> namedType, final T value) {
Objects.requireNonNull(namedType);
Objects.requireNonNull(value);
builderAttributes.put(namedType, value);
return this;
}
/**
* Sets an attribute value.
*
* @param name
* Attribute name.
* @param type
* Attribute type.
* @param value
* Value to set.
* @return This builder.
*/
public <T> Builder setAttribute(final String name, final AttributeType<T> type, final T value) {
return setAttribute(new NamedAttributeType<>(name, type), value);
}
/**
* Sets the delegate attribute container.
*
* @param builderDelegateContainer
* Delegate container to set.
* @return This builder.
*/
public Builder setDelegateContainer(final AttributeContainer builderDelegateContainer) {
this.builderDelegateContainer = Objects.requireNonNull(builderDelegateContainer);
return this;
}
}
private final Map<NamedAttributeType<?>, ListenerSet<?>> listeners;
private final Map<NamedAttributeType<?>, Object> attributes;
private AttributeContainer delegateContainer;
private final Lock read;
private final Lock write;
/**
* Creates a new instance of DefaultAttributeContainer with no delegate container (self).
*/
public DefaultAttributeContainer() {
this(null, null);
}
/**
* Creates a new instance of DefaultAttributeContainer with a delegate container.
*
* @param delegateContainer
* Delegate AttributeContainer for events.
*/
public DefaultAttributeContainer(final AttributeContainer delegateContainer) {
this(null, null);
setDelegateContainer(delegateContainer);
}
private DefaultAttributeContainer(final Map<NamedAttributeType<?>, Object> attributes,
final Map<NamedAttributeType<?>, Set<AttributeListener<?>>> listeners) {
delegateContainer = this;
this.attributes = new HashMap<>();
this.listeners = new HashMap<>();
final ReadWriteLock rwLock = new ReentrantReadWriteLock();
read = rwLock.readLock();
write = rwLock.writeLock();
// Add starting attributes
if (attributes != null) {
this.attributes.putAll(attributes);
}
// Add starting listeners
if (listeners != null) {
for (final Entry<NamedAttributeType<?>, Set<AttributeListener<?>>> entry : listeners.entrySet()) {
this.listeners.put(entry.getKey(), new ListenerSet<>(AttributeListener.class, entry.getValue()));
}
}
}
@Override
public <T> boolean addAttributeListener(final NamedAttributeType<T> namedType,
final AttributeListener<T> listener) {
Objects.requireNonNull(namedType);
Objects.requireNonNull(listener);
write.lock();
try {
@SuppressWarnings({ "unchecked" })
ListenerSet<AttributeListener<T>> lst = (ListenerSet<AttributeListener<T>>) listeners.get(namedType);
if (lst == null) {
// No existing listeners
lst = new ListenerSet<>(AttributeListener.class);
listeners.put(namedType, lst);
}
return lst.add(listener);
} finally {
write.unlock();
}
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof DefaultAttributeContainer)) {
return false;
}
final DefaultAttributeContainer other = (DefaultAttributeContainer) obj;
return attributes.equals(other.attributes) && listeners.equals(other.listeners);
}
@Override
public <T> void fireAttributeChanged(final NamedAttributeType<T> namedType) {
Objects.requireNonNull(namedType);
read.lock();
try {
@SuppressWarnings("unchecked")
final T current = (T) attributes.get(namedType);
if (current == null) {
return;
}
@SuppressWarnings("unchecked")
final ListenerSet<AttributeListener<T>> ls = (ListenerSet<AttributeListener<T>>) listeners.get(namedType);
if (ls != null) {
ls.getProxy().attributeChanged(new AttributeEvent<>(delegateContainer, namedType, current));
}
} finally {
read.unlock();
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAttribute(final NamedAttributeType<T> namedType) {
Objects.requireNonNull(namedType);
read.lock();
try {
return (T) attributes.get(namedType);
} finally {
read.unlock();
}
}
@Override
public int getAttributeCount() {
read.lock();
try {
return attributes.size();
} finally {
read.unlock();
}
}
@Override
public <T> Set<? extends AttributeListener<T>> getAttributeListeners(final NamedAttributeType<T> namedType) {
Objects.requireNonNull(namedType);
read.lock();
try {
@SuppressWarnings("unchecked")
final Set<? extends AttributeListener<T>> ls = (Set<? extends AttributeListener<T>>) listeners
.get(namedType);
return ls != null ? new HashSet<>(ls) : Collections.emptySet();
} finally {
read.unlock();
}
}
@Override
public Set<NamedAttributeType<?>> getAttributeListenerTypes() {
read.lock();
try {
return new HashSet<>(listeners.keySet());
} finally {
read.unlock();
}
}
@Override
public Set<NamedAttributeType<?>> getAttributeTypes() {
read.lock();
try {
return new HashSet<>(attributes.keySet());
} finally {
read.unlock();
}
}
/**
* Gets the delegate container.
*
* @return Delegate event container.
*/
public AttributeContainer getDelegateContainer() {
return delegateContainer;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + attributes.hashCode();
result = prime * result + listeners.hashCode();
return result;
}
@Override
public <T> T removeAttribute(final NamedAttributeType<T> namedType) {
Objects.requireNonNull(namedType);
write.lock();
try {
@SuppressWarnings("unchecked")
final T prev = (T) attributes.remove(namedType);
if (prev != null) {
@SuppressWarnings("unchecked")
final ListenerSet<AttributeListener<T>> ls = (ListenerSet<AttributeListener<T>>) listeners
.get(namedType);
if (ls != null) {
ls.getProxy().attributeRemoved(new AttributeEvent<>(delegateContainer, namedType, prev));
}
}
return prev;
} finally {
write.unlock();
}
}
@Override
public <T> boolean removeAttributeListener(final NamedAttributeType<T> namedType,
final AttributeListener<T> listener) {
Objects.requireNonNull(namedType);
Objects.requireNonNull(listener);
write.lock();
try {
@SuppressWarnings("unchecked")
final ListenerSet<AttributeListener<T>> lst = (ListenerSet<AttributeListener<T>>) listeners.get(namedType);
// Try and remove
if (lst == null || !lst.remove(listener)) {
return false;
}
if (lst.isEmpty()) {
// No more listeners
listeners.remove(namedType);
}
return true;
} finally {
write.unlock();
}
}
@Override
public void removeAttributeListeners() {
write.lock();
try {
listeners.clear();
} finally {
write.unlock();
}
}
@Override
public <T> void removeAttributeListeners(final NamedAttributeType<T> namedType) {
Objects.requireNonNull(namedType);
write.lock();
try {
listeners.remove(namedType);
} finally {
write.unlock();
}
}
@Override
public void removeAttributes() {
write.lock();
try {
new ArrayList<>(attributes.keySet()).forEach(this::removeAttribute);
} finally {
write.unlock();
}
}
@Override
public <T> T setAttribute(final NamedAttributeType<T> namedType, final T attr) {
Objects.requireNonNull(namedType);
Objects.requireNonNull(attr);
write.lock();
try {
@SuppressWarnings("unchecked")
final T prev = (T) attributes.put(namedType, attr);
@SuppressWarnings("unchecked")
final ListenerSet<AttributeListener<T>> ls = (ListenerSet<AttributeListener<T>>) listeners.get(namedType);
if (ls != null) {
ls.getProxy().attributeAdded(new AttributeEvent<>(delegateContainer, namedType, attr, prev));
}
return prev;
} finally {
write.unlock();
}
}
private void setDelegateContainer(final AttributeContainer delegateContainer) {
this.delegateContainer = Objects.requireNonNull(delegateContainer);
}
@Override
public Stream<?> streamAttributes() {
read.lock();
try {
return new ArrayList<>(attributes.values()).stream();
} finally {
read.unlock();
}
}
@Override
public String toString() {
return "DefaultAttributeContainer [" + getAttributeTypes() + "]";
}
}