001package co.codewizards.cloudstore.ls.core.invoke.refjanitor; 002 003import static co.codewizards.cloudstore.core.util.AssertUtil.*; 004import static co.codewizards.cloudstore.core.util.ReflectionUtil.*; 005 006import java.beans.PropertyChangeEvent; 007import java.beans.PropertyChangeListener; 008import java.lang.ref.WeakReference; 009import java.util.HashMap; 010import java.util.Iterator; 011import java.util.LinkedList; 012import java.util.List; 013import java.util.Map; 014 015import org.slf4j.Logger; 016import org.slf4j.LoggerFactory; 017 018import co.codewizards.cloudstore.core.bean.PropertyBase; 019import co.codewizards.cloudstore.core.collection.WeakIdentityHashMap; 020import co.codewizards.cloudstore.core.ref.IdentityWeakReference; 021import co.codewizards.cloudstore.ls.core.invoke.MethodInvocationRequest; 022import co.codewizards.cloudstore.ls.core.invoke.filter.ExtMethodInvocationRequest; 023 024public class PropertyChangeListenerJanitor extends AbstractReferenceJanitor { 025 026 private static final Logger logger = LoggerFactory.getLogger(PropertyChangeListenerJanitor.class); 027 028 private final WeakIdentityHashMap<Object, Map<Object, List<IdentityWeakReference<PropertyChangeListener>>>> bean2Property2ListenerRefs = new WeakIdentityHashMap<>(); 029 private final WeakIdentityHashMap<PropertyChangeListener, WeakReference<FaultTolerantPropertyChangeListener>> originalListener2FaultTolerantPropertyChangeListenerRef = 030 new WeakIdentityHashMap<>(); 031 032// // needed to pin the original listener into memory as long as the FaultTolerantPropertyChangeListener is still needed. 033// private final WeakIdentityHashMap<FaultTolerantPropertyChangeListener, PropertyChangeListener> faultTolerantPropertyChangeListener2OriginalListener = 034// new WeakIdentityHashMap<>(); 035 036 @Override 037 public void preInvoke(final ExtMethodInvocationRequest extMethodInvocationRequest) { 038 final MethodInvocationRequest methodInvocationRequest = extMethodInvocationRequest.getMethodInvocationRequest(); 039 040 final Object bean = methodInvocationRequest.getObject(); // we don't support registering a PropertyChangeListener statically - or should we?! 041 if (bean == null) 042 return; 043 044 final String methodName = methodInvocationRequest.getMethodName(); 045 final Object[] arguments = methodInvocationRequest.getArguments(); 046 047 Object property = null; 048 PropertyChangeListener listener = null; 049 if (arguments.length == 1 && arguments[0] instanceof PropertyChangeListener) { 050 listener = (PropertyChangeListener) arguments[0]; 051 arguments[0] = getFaultTolerantPropertyChangeListenerOrCreate(listener); 052 } 053 else if (arguments.length == 2 && arguments[1] instanceof PropertyChangeListener) { 054 listener = (PropertyChangeListener) arguments[1]; 055 if (arguments[0] instanceof PropertyBase) 056 property = arguments[0]; 057 else if (arguments[0] instanceof String) 058 property = arguments[0]; 059 else 060 return; 061 062 arguments[1] = getFaultTolerantPropertyChangeListenerOrCreate(listener); 063 } 064 else 065 return; 066 067 assertNotNull(listener, "listener"); 068 069 if ("addPropertyChangeListener".equals(methodName)) 070 trackAddPropertyChangeListener(bean, property, listener); 071 else if ("removePropertyChangeListener".equals(methodName)) 072 trackRemovePropertyChangeListener(bean, property, listener); 073 } 074 075 private synchronized FaultTolerantPropertyChangeListener getFaultTolerantPropertyChangeListenerOrCreate(final PropertyChangeListener listener) { 076 assertNotNull(listener, "listener"); 077 078 final WeakReference<FaultTolerantPropertyChangeListener> ref = originalListener2FaultTolerantPropertyChangeListenerRef.get(listener); 079 FaultTolerantPropertyChangeListener faultTolerantListener = ref == null ? null : ref.get(); 080 if (faultTolerantListener == null) { 081 faultTolerantListener = new FaultTolerantPropertyChangeListener(listener); 082 originalListener2FaultTolerantPropertyChangeListenerRef.put(listener, new WeakReference<>(faultTolerantListener)); 083 } 084 return faultTolerantListener; 085 } 086 087 private synchronized FaultTolerantPropertyChangeListener getFaultTolerantPropertyChangeListener(final PropertyChangeListener listener) { 088 assertNotNull(listener, "listener"); 089 090 final WeakReference<FaultTolerantPropertyChangeListener> ref = originalListener2FaultTolerantPropertyChangeListenerRef.get(listener); 091 final FaultTolerantPropertyChangeListener faultTolerantListener = ref == null ? null : ref.get(); 092 return faultTolerantListener; 093 } 094 095 @Override 096 public void cleanUp() { 097 final Map<Object, Map<Object, List<IdentityWeakReference<PropertyChangeListener>>>> bean2Property2ListenerRefs; 098 synchronized (this) { 099 bean2Property2ListenerRefs = new HashMap<>(this.bean2Property2ListenerRefs); 100 this.bean2Property2ListenerRefs.clear(); 101 } 102 103 for (final Map.Entry<Object, Map<Object, List<IdentityWeakReference<PropertyChangeListener>>>> me1 : bean2Property2ListenerRefs.entrySet()) { 104 final Object bean = me1.getKey(); 105 if (bean == null) 106 throw new IllegalStateException("bean2Property2ListenerRefs.entrySet() contained null-key!"); 107 108 for (final Map.Entry<Object, List<IdentityWeakReference<PropertyChangeListener>>> me2 : me1.getValue().entrySet()) { 109 final Object property = me2.getKey(); 110 111 for (final IdentityWeakReference<PropertyChangeListener> ref : me2.getValue()) { 112 final PropertyChangeListener listener = ref.get(); 113 if (listener != null) 114 tryRemovePropertyChangeListener(bean, property, listener); 115 } 116 } 117 } 118 } 119 120 private void tryRemovePropertyChangeListener(final Object bean, final Object property, final PropertyChangeListener listener) { 121 assertNotNull(bean, "bean"); 122 assertNotNull(listener, "listener"); 123 124 final FaultTolerantPropertyChangeListener faultTolerantPropertyChangeListener = getFaultTolerantPropertyChangeListener(listener); 125 if (faultTolerantPropertyChangeListener == null) 126 return; 127 128 try { 129 if (property != null) 130 invoke(bean, "removePropertyChangeListener", property, faultTolerantPropertyChangeListener); 131 else 132 invoke(bean, "removePropertyChangeListener", faultTolerantPropertyChangeListener); 133 } catch (final Exception x) { 134 logger.error("tryRemovePropertyChangeListener: " + x, x); 135 } 136 } 137 138 private synchronized void trackAddPropertyChangeListener(final Object bean, final Object property, final PropertyChangeListener listener) { 139 assertNotNull(bean, "bean"); 140 assertNotNull(listener, "listener"); 141 142 Map<Object, List<IdentityWeakReference<PropertyChangeListener>>> property2ListenerRefs = bean2Property2ListenerRefs.get(bean); 143 if (property2ListenerRefs == null) { 144 property2ListenerRefs = new HashMap<>(); 145 bean2Property2ListenerRefs.put(bean, property2ListenerRefs); 146 } 147 148 List<IdentityWeakReference<PropertyChangeListener>> listenerRefs = property2ListenerRefs.get(property); 149 if (listenerRefs == null) { 150 listenerRefs = new LinkedList<>(); 151 property2ListenerRefs.put(property, listenerRefs); 152 } 153 else 154 expunge(listenerRefs); 155 156 // PropertyChangeSupport.addPropertyChangeListener(...) causes the same listener to be added multiple times. 157 // Hence, we do the same here: Add it once for each invocation. 158 final IdentityWeakReference<PropertyChangeListener> listenerRef = new IdentityWeakReference<PropertyChangeListener>(listener); 159 listenerRefs.add(listenerRef); 160 } 161 162 private synchronized void trackRemovePropertyChangeListener(final Object bean, final Object property, final PropertyChangeListener listener) { 163 assertNotNull(bean, "bean"); 164 assertNotNull(listener, "listener"); 165 166 final Map<Object, List<IdentityWeakReference<PropertyChangeListener>>> property2ListenerRefs = bean2Property2ListenerRefs.get(bean); 167 if (property2ListenerRefs == null) 168 return; 169 170 final List<IdentityWeakReference<PropertyChangeListener>> listenerRefs = property2ListenerRefs.get(property); 171 if (listenerRefs == null) 172 return; 173 174 final IdentityWeakReference<PropertyChangeListener> listenerRef = new IdentityWeakReference<PropertyChangeListener>(listener); 175 listenerRefs.remove(listenerRef); 176 177 expunge(listenerRefs); 178 179 if (listenerRefs.isEmpty()) 180 property2ListenerRefs.remove(property); 181 182 if (property2ListenerRefs.isEmpty()) 183 bean2Property2ListenerRefs.remove(bean); 184 } 185 186 private void expunge(final List<IdentityWeakReference<PropertyChangeListener>> listenerRefs) { 187 assertNotNull(listenerRefs, "listenerRefs"); 188 for (final Iterator<IdentityWeakReference<PropertyChangeListener>> it = listenerRefs.iterator(); it.hasNext();) { 189 final IdentityWeakReference<PropertyChangeListener> ref = it.next(); 190 if (ref.get() == null) 191 it.remove(); 192 } 193 } 194 195 private static class FaultTolerantPropertyChangeListener implements PropertyChangeListener { 196 private static final Logger logger = LoggerFactory.getLogger(PropertyChangeListenerJanitor.FaultTolerantPropertyChangeListener.class); 197 198 private final PropertyChangeListener delegate; 199 200 public FaultTolerantPropertyChangeListener(final PropertyChangeListener delegate) { 201 this.delegate = assertNotNull(delegate, "delegate"); 202 } 203 204 @Override 205 public void propertyChange(final PropertyChangeEvent event) { 206 try { 207 delegate.propertyChange(event); 208 } catch (final Exception x) { 209 logger.error("propertyChange: " + x, x); 210 } 211 } 212 213 @Override 214 protected void finalize() throws Throwable { 215 logger.debug("finalize: entered."); 216 super.finalize(); 217 } 218 } 219}