PropertyChangeListeners.java
package sprouts.impl;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import sprouts.Observer;
import sprouts.*;
import java.util.*;
/**
* This class is technically an internal class and should not be used directly.
* If you use this class directly, most likely, you are at risk of your code breaking
* in future releases of Sprouts.
* @param <T> The type of the property that this listener listens to.
*/
public final class PropertyChangeListeners<T>
{
private static final Logger log = org.slf4j.LoggerFactory.getLogger(PropertyChangeListeners.class);
private final Map<Channel, ChangeListeners<ValDelegate<T>>> _actions = new LinkedHashMap<>();
public PropertyChangeListeners() {}
public PropertyChangeListeners( PropertyChangeListeners<T> other ) {
_copyFrom(other);
}
public void onChange( Channel channel, Action<ValDelegate<T>> action ) {
Objects.requireNonNull(channel);
Objects.requireNonNull(action);
_getActionsFor(channel).add(action);
}
public void onChange( Observer observer ) {
if ( observer instanceof WeakObserverImpl)
this.onChange(Sprouts.factory().defaultObservableChannel(), new WeakObserverAsActionImpl<>( (WeakObserverImpl<?>) observer ) );
else
this.onChange(Sprouts.factory().defaultObservableChannel(), new ObserverAsActionImpl<>(observer) );
}
public void unsubscribe( Subscriber subscriber ) {
for ( ChangeListeners<ValDelegate<T>> actions : _actions.values() )
actions.unsubscribe(subscriber);
}
public void unsubscribeAll() {
for ( ChangeListeners<ValDelegate<T>> actions : _actions.values() )
actions.unsubscribeAll();
}
public void fireChange( Val<T> owner, Channel channel, @Nullable T newValue, @Nullable T oldValue ) {
fireChange(owner, channel, new ItemPair<>(owner.type(), newValue, oldValue));
}
void fireChange(
Val<T> owner,
Channel channel,
ItemPair<T> pair
) {
ValDelegate<T> delegate = Sprouts.factory().delegateOf(owner, channel, pair.change(), pair.newValue(), pair.oldValue());
// We clone this property to avoid concurrent modification
if ( channel == From.ALL)
for ( Channel key : _actions.keySet() )
_getActionsFor(key).fireChange( delegate );
else {
_getActionsFor(channel).fireChange( delegate );
_getActionsFor(From.ALL).fireChange( delegate );
}
}
/**
* Returns the number of change listeners that are currently registered.
* This is useful for debugging purposes.
*
* @return The number of change listeners that are currently registered.
*/
public long numberOfChangeListeners() {
return _actions.values()
.stream()
.mapToLong(ChangeListeners::numberOfChangeListeners)
.sum();
}
private void _copyFrom(PropertyChangeListeners<T> other) {
other._actions.forEach( (k, v) -> _actions.put(k, new ChangeListeners<>(v)) );
}
private ChangeListeners<ValDelegate<T>> _getActionsFor( Channel channel ) {
return _actions.computeIfAbsent(channel, k->new ChangeListeners<>());
}
@Override
public final String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName()).append("[");
for ( Channel key : _actions.keySet() ) {
try {
sb.append(key).append("->").append(_actions.get(key)).append(", ");
} catch ( Exception e ) {
log.error("An error occurred while trying to get the number of change listeners for channel '{}'", key, e);
}
}
sb.append("]");
return sb.toString();
}
}