/*
* $Id$
*
* Copyright (c) 2009-2010 by Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.property;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A thread-safe implementation of {@link PropertySupport}.
*
* @since 3.2.0
* @author Joel Uckelman
*/
public class ConcurrentPropertySupport implements PropertySupport {
// FIXME: consider using WeakReferences for listeners to prevent memory leaks
protected final CopyOnWriteArrayList<PropertyListener<Object>> listeners =
new CopyOnWriteArrayList<PropertyListener<Object>>();
/**
* {@inheritDoc}
*
* @throws IllegalArgumentException if {@code l} is {@code null}
*/
public void addPropertyListener(PropertyListener<Object> l) {
if (l == null) throw new IllegalArgumentException();
listeners.add(l);
}
/**
* {@inheritDoc}
*
* @throws IllegalArgumentException if {@code l} is {@code null}
*/
public void removePropertyListener(PropertyListener<Object> l) {
if (l == null) throw new IllegalArgumentException();
listeners.remove(l);
}
/** {@inheritDoc} */
public boolean hasListeners() {
return !listeners.isEmpty();
}
/** {@inheritDoc} */
public List<PropertyListener<Object>> getPropertyListeners() {
return listeners.isEmpty() ?
Collections.<PropertyListener<Object>>emptyList() :
new ArrayList<PropertyListener<Object>>(listeners);
}
protected final ConcurrentMap<Property<?>,List<PropertyListener<?>>>
plisteners = new ConcurrentHashMap<Property<?>,List<PropertyListener<?>>>();
/** {@inheritDoc} */
public <T> void addPropertyListener(Property<T> prop,
PropertyListener<? super T> l) {
final List<PropertyListener<?>> empty =
new CopyOnWriteArrayList<PropertyListener<?>>();
List<PropertyListener<?>> list = plisteners.putIfAbsent(prop, empty);
if (list == null) list = empty;
list.add(l);
}
/** {@inheritDoc} */
public <T> void removePropertyListener(Property<T> prop,
PropertyListener<? super T> l) {
final List<PropertyListener<?>> list = plisteners.get(prop);
if (list != null) list.remove(l);
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public <T> List<PropertyListener<? super T>>
getPropertyListeners(Property<T> prop) {
final List<PropertyListener<?>> list = plisteners.get(prop);
return list == null || list.isEmpty() ?
Collections.<PropertyListener<? super T>>emptyList() :
new ArrayList<PropertyListener<? super T>>((List) list);
}
/** {@inheritDoc} */
public <T> boolean hasListeners(Property<T> prop) {
final List<PropertyListener<?>> list = plisteners.get(prop);
return list != null && !list.isEmpty();
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public <T> void fireChanged(Object src, Property<T> prop,
T oldVal, T newVal) {
// do nothing if oldVal and newVal are the same
if (oldVal == newVal || (oldVal != null && oldVal.equals(newVal))) return;
// notify all general listeners
for (PropertyListener<Object> l : listeners) {
l.propertyChanged(src, prop, oldVal, newVal);
}
// notify all listeners on this property
final List<PropertyListener<?>> list = plisteners.get(prop);
if (list != null) {
for (PropertyListener<?> l : list) {
((PropertyListener<? super T>) l).propertyChanged(
src, prop, oldVal, newVal
);
}
}
}
}