package de.invesdwin.util.bean;
import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Field;
import java.util.Map;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.querydsl.core.annotations.QuerySupertype;
import de.invesdwin.norva.beanpath.annotation.Hidden;
import de.invesdwin.util.lang.Objects;
import de.invesdwin.util.lang.Reflections;
/**
* Standard implementation of {@link PropertyChangeSupported}. This class is thread-safe and uses a lazily initialized
* {@link PropertyChangeSupport} instance to do the heavy lifting. Initialization happens in either of the
* <code>addPropertyChangeListener</code> methods the first time a listener is added.
* <p>
* Implementing classes can safely declare serializability; implementors should be aware that the enclosed
* <code>PropertyChangeSupport</code> is marked <code>transient</code>.
* <p>
* No effort is made to unload the <code>PropertyChangeSupport</code> after the last listener unsubscribes.
*
* Taken from: http://java.sogeti.nl/JavaBlog/2008/03/12/lazily-initialized-propertychangesupport/
*
* @author Barend Garvelink
* @since 1.0
*/
@ThreadSafe
@QuerySupertype
public abstract class APropertyChangeSupported {
/**
* Used to track property change listeners. Lazily initialized. PropertyChangeSupport is itself threadsafe, so we
* only need to synchronize when accessing this field. Once the caller has a local reference it can safely access
* any of its methods.
*/
@GuardedBy("propertyChangeSupportLock")
@Transient
@JsonIgnore
private transient PropertyChangeSupport propertyChangeSupport;
@Transient
@JsonIgnore
private final Object propertyChangeSupportLock = new Object();
static {
Objects.REFLECTION_EXCLUDED_FIELDS.add("propertyChangeSupportLock");
Objects.REFLECTION_EXCLUDED_FIELDS.add("propertyChangeSupport");
//need to remove listeners from innerMergeFrom
Objects.REFLECTION_EXCLUDED_FIELDS.add("propertyChangeListeners");
}
/**
* Returns the <code>propertyChangeSupport</code> for this instance, lazily initialized if necessary.
*
* @param initializeIfNull
* if <code>true</code>, the instance field is initialized if it's <code>null</code>. If fase, the
* <code>null</code> is returned.
* @return the <code>propertyChangeSupport</code> for this instance, which may be <code>null</code> if the
* <code>initializeIfNull</code> parameter is <code>false</code>.
*/
private PropertyChangeSupport lazyGetPropertyChangeSupport(final boolean initializeIfNull) {
synchronized (propertyChangeSupportLock) {
if (initializeIfNull && propertyChangeSupport == null) {
propertyChangeSupport = new PropertyChangeSupport(this);
}
return propertyChangeSupport;
}
}
/**
* @see PropertyChangeSupported#addPropertyChangeListener(java.beans.PropertyChangeListener)
*/
@Hidden(skip = true)
public final void addPropertyChangeListener(final PropertyChangeListener listener) {
if (listener != null) {
lazyGetPropertyChangeSupport(true).addPropertyChangeListener(listener);
}
}
/**
* @see PropertyChangeSupported#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
*/
@Hidden(skip = true)
public final void addPropertyChangeListener(final String property, final PropertyChangeListener listener) {
if (listener != null) {
lazyGetPropertyChangeSupport(true).addPropertyChangeListener(property, listener);
}
}
/**
* @see PropertyChangeSupported#removePropertyChangeListener(java.beans.PropertyChangeListener)
*/
@Hidden(skip = true)
public final void removePropertyChangeListener(final PropertyChangeListener listener) {
final PropertyChangeSupport ref = lazyGetPropertyChangeSupport(false);
if (ref != null) {
ref.removePropertyChangeListener(listener);
}
}
/**
* @see PropertyChangeSupported#removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
*/
@Hidden(skip = true)
public final void removePropertyChangeListener(final String property, final PropertyChangeListener listener) {
final PropertyChangeSupport ref = lazyGetPropertyChangeSupport(false);
if (ref != null) {
ref.removePropertyChangeListener(property, listener);
}
}
/**
* @see PropertyChangeSupported#hasListeners(java.lang.String)
*/
@Hidden(skip = true)
public final boolean hasListeners(final String propertyName) {
final PropertyChangeSupport ref = lazyGetPropertyChangeSupport(false);
return ref != null && ref.hasListeners(propertyName);
}
@Hidden(skip = true)
public boolean hasListeners() {
final PropertyChangeSupport ref = lazyGetPropertyChangeSupport(false);
return ref != null && ref.getPropertyChangeListeners().length > 0;
}
/**
* @see PropertyChangeSupported#getPropertyChangeListeners()
*/
@Transient
@Hidden(skip = true)
@JsonIgnore
public final PropertyChangeListener[] getPropertyChangeListeners() {
final PropertyChangeSupport ref = lazyGetPropertyChangeSupport(true);
return ref.getPropertyChangeListeners();
}
/**
* @see PropertyChangeSupported#getPropertyChangeListeners(java.lang.String)
*/
@Transient
@Hidden(skip = true)
@JsonIgnore
public final PropertyChangeListener[] getPropertyChangeListeners(final String propertyName) {
final PropertyChangeSupport ref = lazyGetPropertyChangeSupport(true);
return ref.getPropertyChangeListeners(propertyName);
}
/**
* This is the same as in the original java beans implementation with the exception that changes from null to null
* are not reported as property change events!
*
* @see java.beans.PropertyChangeSupport#fireIndexedPropertyChange(java.lang.String, int, java.lang.Object,
* java.lang.Object)
*/
public final void fireIndexedPropertyChange(final String propertyName, final int index, final Object oldValue,
final Object newValue) {
final PropertyChangeSupport ref = lazyGetPropertyChangeSupport(false);
if (ref != null && (!Objects.equalsProperty(oldValue, newValue)
|| !equalsPropertyChangeListeners(oldValue, newValue))) {
final IndexedPropertyChangeEvent event = new IndexedPropertyChangeEvent(this, propertyName, oldValue,
newValue, index);
fireEvent(ref, propertyName, event);
}
}
/**
* This is the same as in the original java beans implementation with the exception that changes from null to null
* are not reported as property change events!
*
* @see java.beans.PropertyChangeSupport#firePropertyChange(java.lang.String, java.lang.Object, java.lang.Object)
*/
public final void firePropertyChange(final String propertyName, final Object oldValue, final Object newValue) {
final PropertyChangeSupport ref = lazyGetPropertyChangeSupport(false);
if (ref != null && (!Objects.equalsProperty(oldValue, newValue)
|| !equalsPropertyChangeListeners(oldValue, newValue))) {
final PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
fireEvent(ref, propertyName, event);
}
}
/**
* Performance optimization to just fire the events instead of having PropertyChangeSupport call equals so often.
* Also with this we don't have to rely on equals alone to fire an event.
*/
@SuppressWarnings("unchecked")
private static void fireEvent(final PropertyChangeSupport ref, final String propertyName,
final PropertyChangeEvent event) {
final Field mapField = Reflections.findField(PropertyChangeSupport.class, "map");
Reflections.makeAccessible(mapField);
final Object map = Reflections.getField(mapField, ref);
final Field mapMapField = Reflections.findField(map.getClass(), "map");
Reflections.makeAccessible(mapMapField);
final Map<String, PropertyChangeListener[]> mapMap = (Map<String, PropertyChangeListener[]>) Reflections
.getField(mapMapField, map);
if (mapMap == null) {
return;
}
final PropertyChangeListener[] common = mapMap.get(null);
fireEvent(common, event);
if (propertyName != null) {
final PropertyChangeListener[] named = mapMap.get(propertyName);
fireEvent(named, event);
}
}
private static void fireEvent(final PropertyChangeListener[] listeners, final PropertyChangeEvent event) {
if (listeners != null) {
for (final PropertyChangeListener listener : listeners) {
listener.propertyChange(event);
}
}
}
private static boolean equalsPropertyChangeListeners(final Object oldValue, final Object newValue) {
final APropertyChangeSupported cOldValue;
if (oldValue instanceof APropertyChangeSupported) {
cOldValue = (APropertyChangeSupported) oldValue;
} else {
cOldValue = null;
}
final APropertyChangeSupported cNewValue;
if (newValue instanceof APropertyChangeSupported) {
cNewValue = (APropertyChangeSupported) newValue;
} else {
cNewValue = null;
}
//one of them null
if ((cOldValue == null) != (cNewValue == null)) {
return false;
}
//both null
if (cOldValue == null) {
return true;
}
final PropertyChangeSupport oldValueRef = cOldValue.lazyGetPropertyChangeSupport(false);
final PropertyChangeSupport newValueRef = cNewValue.lazyGetPropertyChangeSupport(false);
//one of them null
if ((oldValueRef == null) != (newValueRef == null)) {
return false;
}
//both null
if (oldValueRef == null) {
return true;
}
return Objects.equals(oldValueRef.getPropertyChangeListeners(), newValueRef.getPropertyChangeListeners());
}
}