package co.codewizards.cloudstore.ls.core.invoke.refjanitor;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import static co.codewizards.cloudstore.core.util.ReflectionUtil.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import co.codewizards.cloudstore.core.bean.PropertyBase;
import co.codewizards.cloudstore.core.collection.WeakIdentityHashMap;
import co.codewizards.cloudstore.core.ref.IdentityWeakReference;
import co.codewizards.cloudstore.ls.core.invoke.MethodInvocationRequest;
import co.codewizards.cloudstore.ls.core.invoke.filter.ExtMethodInvocationRequest;
public class PropertyChangeListenerJanitor extends AbstractReferenceJanitor {
private static final Logger logger = LoggerFactory.getLogger(PropertyChangeListenerJanitor.class);
private final WeakIdentityHashMap<Object, Map<Object, List<IdentityWeakReference<PropertyChangeListener>>>> bean2Property2ListenerRefs = new WeakIdentityHashMap<>();
private final WeakIdentityHashMap<PropertyChangeListener, WeakReference<FaultTolerantPropertyChangeListener>> originalListener2FaultTolerantPropertyChangeListenerRef =
new WeakIdentityHashMap<>();
// // needed to pin the original listener into memory as long as the FaultTolerantPropertyChangeListener is still needed.
// private final WeakIdentityHashMap<FaultTolerantPropertyChangeListener, PropertyChangeListener> faultTolerantPropertyChangeListener2OriginalListener =
// new WeakIdentityHashMap<>();
@Override
public void preInvoke(final ExtMethodInvocationRequest extMethodInvocationRequest) {
final MethodInvocationRequest methodInvocationRequest = extMethodInvocationRequest.getMethodInvocationRequest();
final Object bean = methodInvocationRequest.getObject(); // we don't support registering a PropertyChangeListener statically - or should we?!
if (bean == null)
return;
final String methodName = methodInvocationRequest.getMethodName();
final Object[] arguments = methodInvocationRequest.getArguments();
Object property = null;
PropertyChangeListener listener = null;
if (arguments.length == 1 && arguments[0] instanceof PropertyChangeListener) {
listener = (PropertyChangeListener) arguments[0];
arguments[0] = getFaultTolerantPropertyChangeListenerOrCreate(listener);
}
else if (arguments.length == 2 && arguments[1] instanceof PropertyChangeListener) {
listener = (PropertyChangeListener) arguments[1];
if (arguments[0] instanceof PropertyBase)
property = arguments[0];
else if (arguments[0] instanceof String)
property = arguments[0];
else
return;
arguments[1] = getFaultTolerantPropertyChangeListenerOrCreate(listener);
}
else
return;
assertNotNull(listener, "listener");
if ("addPropertyChangeListener".equals(methodName))
trackAddPropertyChangeListener(bean, property, listener);
else if ("removePropertyChangeListener".equals(methodName))
trackRemovePropertyChangeListener(bean, property, listener);
}
private synchronized FaultTolerantPropertyChangeListener getFaultTolerantPropertyChangeListenerOrCreate(final PropertyChangeListener listener) {
assertNotNull(listener, "listener");
final WeakReference<FaultTolerantPropertyChangeListener> ref = originalListener2FaultTolerantPropertyChangeListenerRef.get(listener);
FaultTolerantPropertyChangeListener faultTolerantListener = ref == null ? null : ref.get();
if (faultTolerantListener == null) {
faultTolerantListener = new FaultTolerantPropertyChangeListener(listener);
originalListener2FaultTolerantPropertyChangeListenerRef.put(listener, new WeakReference<>(faultTolerantListener));
}
return faultTolerantListener;
}
private synchronized FaultTolerantPropertyChangeListener getFaultTolerantPropertyChangeListener(final PropertyChangeListener listener) {
assertNotNull(listener, "listener");
final WeakReference<FaultTolerantPropertyChangeListener> ref = originalListener2FaultTolerantPropertyChangeListenerRef.get(listener);
final FaultTolerantPropertyChangeListener faultTolerantListener = ref == null ? null : ref.get();
return faultTolerantListener;
}
@Override
public void cleanUp() {
final Map<Object, Map<Object, List<IdentityWeakReference<PropertyChangeListener>>>> bean2Property2ListenerRefs;
synchronized (this) {
bean2Property2ListenerRefs = new HashMap<>(this.bean2Property2ListenerRefs);
this.bean2Property2ListenerRefs.clear();
}
for (final Map.Entry<Object, Map<Object, List<IdentityWeakReference<PropertyChangeListener>>>> me1 : bean2Property2ListenerRefs.entrySet()) {
final Object bean = me1.getKey();
if (bean == null)
throw new IllegalStateException("bean2Property2ListenerRefs.entrySet() contained null-key!");
for (final Map.Entry<Object, List<IdentityWeakReference<PropertyChangeListener>>> me2 : me1.getValue().entrySet()) {
final Object property = me2.getKey();
for (final IdentityWeakReference<PropertyChangeListener> ref : me2.getValue()) {
final PropertyChangeListener listener = ref.get();
if (listener != null)
tryRemovePropertyChangeListener(bean, property, listener);
}
}
}
}
private void tryRemovePropertyChangeListener(final Object bean, final Object property, final PropertyChangeListener listener) {
assertNotNull(bean, "bean");
assertNotNull(listener, "listener");
final FaultTolerantPropertyChangeListener faultTolerantPropertyChangeListener = getFaultTolerantPropertyChangeListener(listener);
if (faultTolerantPropertyChangeListener == null)
return;
try {
if (property != null)
invoke(bean, "removePropertyChangeListener", property, faultTolerantPropertyChangeListener);
else
invoke(bean, "removePropertyChangeListener", faultTolerantPropertyChangeListener);
} catch (final Exception x) {
logger.error("tryRemovePropertyChangeListener: " + x, x);
}
}
private synchronized void trackAddPropertyChangeListener(final Object bean, final Object property, final PropertyChangeListener listener) {
assertNotNull(bean, "bean");
assertNotNull(listener, "listener");
Map<Object, List<IdentityWeakReference<PropertyChangeListener>>> property2ListenerRefs = bean2Property2ListenerRefs.get(bean);
if (property2ListenerRefs == null) {
property2ListenerRefs = new HashMap<>();
bean2Property2ListenerRefs.put(bean, property2ListenerRefs);
}
List<IdentityWeakReference<PropertyChangeListener>> listenerRefs = property2ListenerRefs.get(property);
if (listenerRefs == null) {
listenerRefs = new LinkedList<>();
property2ListenerRefs.put(property, listenerRefs);
}
else
expunge(listenerRefs);
// PropertyChangeSupport.addPropertyChangeListener(...) causes the same listener to be added multiple times.
// Hence, we do the same here: Add it once for each invocation.
final IdentityWeakReference<PropertyChangeListener> listenerRef = new IdentityWeakReference<PropertyChangeListener>(listener);
listenerRefs.add(listenerRef);
}
private synchronized void trackRemovePropertyChangeListener(final Object bean, final Object property, final PropertyChangeListener listener) {
assertNotNull(bean, "bean");
assertNotNull(listener, "listener");
final Map<Object, List<IdentityWeakReference<PropertyChangeListener>>> property2ListenerRefs = bean2Property2ListenerRefs.get(bean);
if (property2ListenerRefs == null)
return;
final List<IdentityWeakReference<PropertyChangeListener>> listenerRefs = property2ListenerRefs.get(property);
if (listenerRefs == null)
return;
final IdentityWeakReference<PropertyChangeListener> listenerRef = new IdentityWeakReference<PropertyChangeListener>(listener);
listenerRefs.remove(listenerRef);
expunge(listenerRefs);
if (listenerRefs.isEmpty())
property2ListenerRefs.remove(property);
if (property2ListenerRefs.isEmpty())
bean2Property2ListenerRefs.remove(bean);
}
private void expunge(final List<IdentityWeakReference<PropertyChangeListener>> listenerRefs) {
assertNotNull(listenerRefs, "listenerRefs");
for (final Iterator<IdentityWeakReference<PropertyChangeListener>> it = listenerRefs.iterator(); it.hasNext();) {
final IdentityWeakReference<PropertyChangeListener> ref = it.next();
if (ref.get() == null)
it.remove();
}
}
private static class FaultTolerantPropertyChangeListener implements PropertyChangeListener {
private static final Logger logger = LoggerFactory.getLogger(PropertyChangeListenerJanitor.FaultTolerantPropertyChangeListener.class);
private final PropertyChangeListener delegate;
public FaultTolerantPropertyChangeListener(final PropertyChangeListener delegate) {
this.delegate = assertNotNull(delegate, "delegate");
}
@Override
public void propertyChange(final PropertyChangeEvent event) {
try {
delegate.propertyChange(event);
} catch (final Exception x) {
logger.error("propertyChange: " + x, x);
}
}
@Override
protected void finalize() throws Throwable {
logger.debug("finalize: entered.");
super.finalize();
}
}
}