package org.netbeans.gradle.project.properties; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListenerProxy; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.jtrim.cancel.Cancellation; import org.jtrim.cancel.CancellationToken; import org.jtrim.collections.CollectionsEx; import org.jtrim.concurrent.CancelableTask; import org.jtrim.concurrent.GenericUpdateTaskExecutor; import org.jtrim.concurrent.TaskExecutor; import org.jtrim.concurrent.UpdateTaskExecutor; import org.jtrim.event.InitLaterListenerRef; import org.jtrim.event.ListenerRef; import org.jtrim.event.ListenerRegistries; import org.jtrim.property.PropertySource; import org.jtrim.utils.ExceptionHelper; public final class SwingPropertyChangeForwarder { public static final class Builder { private final TaskExecutor eventExecutorSrc; private final UpdateTaskExecutor eventExecutor; private final List<NamedProperty> properties; public Builder() { this(null, null); } public Builder(TaskExecutor eventExecutor) { this(eventExecutor, new GenericUpdateTaskExecutor(eventExecutor)); } private Builder(TaskExecutor eventExecutorSrc, UpdateTaskExecutor eventExecutor) { this.eventExecutorSrc = eventExecutorSrc; this.eventExecutor = eventExecutor; this.properties = new ArrayList<>(); } public void addPropertyNoValue(String name, PropertySource<?> property, Object source) { properties.add(new NamedProperty(name, property, source, false)); } public void addPropertyNoValue(String name, PropertySource<?> property) { addPropertyNoValue(name, property, property); } public void addProperty(String name, PropertySource<?> property, Object source) { properties.add(new NamedProperty(name, property, source, true)); } public void addProperty(String name, PropertySource<?> property) { addProperty(name, property, property); } public SwingPropertyChangeForwarder create() { return new SwingPropertyChangeForwarder(this); } } private final Lock mainLock; private final Map<PropertyChangeListener, RegistrationRef> listeners; private final List<NamedProperty> properties; private final TaskExecutor eventExecutorSrc; private final UpdateTaskExecutor eventExecutor; private SwingPropertyChangeForwarder(Builder builder) { this.mainLock = new ReentrantLock(); this.listeners = new HashMap<>(); this.properties = CollectionsEx.readOnlyCopy(builder.properties); this.eventExecutorSrc = builder.eventExecutorSrc; this.eventExecutor = builder.eventExecutor; } private void fireListeners(PropertyChangeEvent changeEvent, List<PropertyChangeListener> toCall) { for (PropertyChangeListener listener: toCall) { listener.propertyChange(changeEvent); } } public void firePropertyChange(final PropertyChangeEvent changeEvent) { ExceptionHelper.checkNotNullArgument(changeEvent, "changeEvent"); String name = changeEvent.getPropertyName(); boolean addedAll = true; final List<PropertyChangeListener> toCall; mainLock.lock(); try { toCall = new ArrayList<>(listeners.size()); for (RegistrationRef ref: listeners.values()) { boolean currentAddedAll = ref.addListeners(name, toCall); addedAll = addedAll && currentAddedAll; } } finally { mainLock.unlock(); } if (eventExecutor == null) { fireListeners(changeEvent, toCall); } else { if (addedAll) { eventExecutor.execute(new Runnable() { @Override public void run() { fireListeners(changeEvent, toCall); } }); } else { eventExecutorSrc.execute(Cancellation.UNCANCELABLE_TOKEN, new CancelableTask() { @Override public void execute(CancellationToken cancelToken) { fireListeners(changeEvent, toCall); } }, null); } } } public void addPropertyChangeListener(String name, PropertyChangeListener listener) { if (listener == null) { return; } InitLaterListenerRef combinedRef = new InitLaterListenerRef(); RegisteredListener registeredListener = new RegisteredListener(name, listener, combinedRef); mainLock.lock(); try { RegistrationRef currentRef = listeners.get(listener); if (currentRef != null) { currentRef.incRegCount(registeredListener); } else { listeners.put(listener, new RegistrationRef(registeredListener)); } } finally { mainLock.unlock(); } List<ListenerRef> refs = new ArrayList<>(properties.size()); for (NamedProperty namedProperty: properties) { if (name == null || Objects.equals(name, namedProperty.name)) { refs.add(namedProperty.property.addChangeListener(namedProperty.forwarderTask(eventExecutor, listener))); } } combinedRef.init(ListenerRegistries.combineListenerRefs(refs)); } public void addPropertyChangeListener(PropertyChangeListener listener) { if (listener instanceof PropertyChangeListenerProxy) { PropertyChangeListenerProxy listenerProxy = (PropertyChangeListenerProxy)listener; addPropertyChangeListener(listenerProxy.getPropertyName(), listenerProxy.getListener()); } else { addPropertyChangeListener(null, listener); } } public void removePropertyChangeListener(PropertyChangeListener listener) { if (listener == null) { return; } ListenerRef toUnregister = null; mainLock.lock(); try { RegistrationRef regRef = listeners.get(listener); if (regRef != null) { toUnregister = regRef.decRegCount(listener); if (regRef.isCompletelyUnregistered()) { listeners.remove(listener); } } } finally { mainLock.unlock(); } if (toUnregister != null) { toUnregister.unregister(); } } // For testing purposes void checkListenerConsistency() { mainLock.lock(); try { for (RegistrationRef ref: listeners.values()) { if (ref.listeners.isEmpty()) { throw new AssertionError("There are no listeners and the listener map still contains the reference."); } if (ref.listeners.size() != ref.regCount) { throw new AssertionError("Number of listeners is not equal to registration count."); } } } finally { mainLock.unlock(); } } private static final class RegistrationRef { private final List<RegisteredListener> listeners; private int regCount; public RegistrationRef(RegisteredListener listener) { this.regCount = 1; this.listeners = new LinkedList<>(); this.listeners.add(listener); } public void incRegCount(RegisteredListener listener) { regCount++; listeners.add(listener); } public ListenerRef decRegCount(PropertyChangeListener listener) { regCount--; Iterator<RegisteredListener> listenersItr = listeners.iterator(); while (listenersItr.hasNext()) { RegisteredListener currentListener = listenersItr.next(); if (Objects.equals(currentListener.listener, listener)) { listenersItr.remove(); return currentListener.listenerRef; } } return null; } public boolean isCompletelyUnregistered() { return regCount <= 0; } private boolean addListeners(String name, Collection<? super PropertyChangeListener> result) { boolean addedAll = true; for (RegisteredListener listener: listeners) { if (name == null || listener.name == null || Objects.equals(name, listener.name)) { result.add(listener.listener); } else { addedAll = false; } } return addedAll; } } private static final class RegisteredListener { public final String name; public final PropertyChangeListener listener; public final ListenerRef listenerRef; public RegisteredListener(String name, PropertyChangeListener listener, ListenerRef listenerRef) { this.name = name; this.listener = listener; this.listenerRef = listenerRef; } } private static final class NamedProperty { public final String name; public final PropertySource<?> property; public final Object source; private final boolean forwardValue; public NamedProperty(String name, PropertySource<?> property, Object source, boolean forwardValue) { ExceptionHelper.checkNotNullArgument(name, "name"); ExceptionHelper.checkNotNullArgument(property, "property"); ExceptionHelper.checkNotNullArgument(source, "source"); this.name = name; this.property = property; this.source = source; this.forwardValue = forwardValue; } private PropertyChangeEvent getChangeEventWithValue() { return new PropertyChangeEvent(source, name, null, property.getValue()); } public Runnable forwarderTask(final UpdateTaskExecutor eventExecutor, PropertyChangeListener listener) { final Runnable forwardNowTask = directForwarderTask(listener); if (eventExecutor == null) { return forwardNowTask; } return new Runnable() { @Override public void run() { eventExecutor.execute(forwardNowTask); } }; } public Runnable directForwarderTask(final PropertyChangeListener listener) { if (forwardValue) { return new Runnable() { @Override public void run() { listener.propertyChange(getChangeEventWithValue()); } }; } else { final PropertyChangeEvent event = new PropertyChangeEvent(source, name, null, null); return new Runnable() { @Override public void run() { listener.propertyChange(event); } }; } } } }