com.jgoodies.common.bean.Bean Maven / Gradle / Ivy
Show all versions of jgoodies-common Show documentation
/*
* Copyright (c) 2009-2013 JGoodies Software GmbH. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of JGoodies Software GmbH nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jgoodies.common.bean;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.io.Serializable;
/**
* An abstract superclass that minimizes the effort required to provide
* change support for bound and constrained Bean properties.
* This class follows the conventions and recommendations as described
* in the Java Bean Specification.
*
* This class uses the standard {@link PropertyChangeSupport} to notify
* registered listeners about changes. Subclasses can use different change
* support implementations by overriding {@code createPropertyChangeSupport},
* for example to ensure that notifications are sent in the Event dispatch
* thread, or to compare old and new values with {@code ==} not {@code equals}.
*
* @author Karsten Lentzsch
*
* @see PropertyChangeEvent
* @see PropertyChangeListener
* @see PropertyChangeSupport
* @see VetoableChangeListener
* @see VetoableChangeSupport
*/
public abstract class Bean implements Serializable, ObservableBean2 {
/**
* If any{@code PropertyChangeListeners} have been registered,
* the {@code changeSupport} field describes them.
*
* @see #addPropertyChangeListener(PropertyChangeListener)
* @see #addPropertyChangeListener(String, PropertyChangeListener)
* @see #removePropertyChangeListener(PropertyChangeListener)
* @see #removePropertyChangeListener(String, PropertyChangeListener)
* @see PropertyChangeSupport
*/
protected transient PropertyChangeSupport changeSupport;
/**
* If any {@code VetoableChangeListeners} have been registered,
* the {@code vetoSupport} field describes them.
*
* @see #addVetoableChangeListener(VetoableChangeListener)
* @see #addVetoableChangeListener(String, VetoableChangeListener)
* @see #removeVetoableChangeListener(VetoableChangeListener)
* @see #removeVetoableChangeListener(String, VetoableChangeListener)
* @see #fireVetoableChange(String, Object, Object)
*/
private transient VetoableChangeSupport vetoSupport;
// Managing Property Change Listeners **********************************
/**
* Adds a PropertyChangeListener to the listener list. The listener is
* registered for all bound properties of this class.
*
* If listener is {@code null}, no exception is thrown
* and no action is performed.
*
* @param listener the PropertyChangeListener to be added
*
* @see #removePropertyChangeListener(PropertyChangeListener)
* @see #removePropertyChangeListener(String, PropertyChangeListener)
* @see #addPropertyChangeListener(String, PropertyChangeListener)
* @see #getPropertyChangeListeners()
*/
@Override
public final synchronized void addPropertyChangeListener(
PropertyChangeListener listener) {
if (listener == null) {
return;
}
if (changeSupport == null) {
changeSupport = createPropertyChangeSupport(this);
}
changeSupport.addPropertyChangeListener(listener);
}
/**
* Removes a PropertyChangeListener from the listener list. This method
* should be used to remove PropertyChangeListeners that were registered
* for all bound properties of this class.
*
* If listener is {@code null}, no exception is thrown and no action is performed.
*
* @param listener the PropertyChangeListener to be removed
* @see #addPropertyChangeListener(PropertyChangeListener)
* @see #addPropertyChangeListener(String, PropertyChangeListener)
* @see #removePropertyChangeListener(String, PropertyChangeListener)
* @see #getPropertyChangeListeners()
*/
@Override
public final synchronized void removePropertyChangeListener(
PropertyChangeListener listener) {
if (listener == null || changeSupport == null) {
return;
}
changeSupport.removePropertyChangeListener(listener);
}
/**
* Adds a PropertyChangeListener to the listener list for a specific
* property. The specified property may be user-defined.
*
* Note that if this Model is inheriting a bound property, then no event
* will be fired in response to a change in the inherited property.
*
* If listener is {@code null}, no exception is thrown and no action is performed.
*
* @param propertyName one of the property names listed above
* @param listener the PropertyChangeListener to be added
*
* @see #removePropertyChangeListener(String, PropertyChangeListener)
* @see #addPropertyChangeListener(String, PropertyChangeListener)
* @see #getPropertyChangeListeners(String)
*/
@Override
public final synchronized void addPropertyChangeListener(
String propertyName,
PropertyChangeListener listener) {
if (listener == null) {
return;
}
if (changeSupport == null) {
changeSupport = createPropertyChangeSupport(this);
}
changeSupport.addPropertyChangeListener(propertyName, listener);
}
/**
* Removes a PropertyChangeListener from the listener list for a specific
* property. This method should be used to remove PropertyChangeListeners
* that were registered for a specific bound property.
*
* If listener is {@code null}, no exception is thrown and no action is performed.
*
* @param propertyName a valid property name
* @param listener the PropertyChangeListener to be removed
*
* @see #addPropertyChangeListener(String, PropertyChangeListener)
* @see #removePropertyChangeListener(PropertyChangeListener)
* @see #getPropertyChangeListeners(String)
*/
@Override
public final synchronized void removePropertyChangeListener(
String propertyName,
PropertyChangeListener listener) {
if (listener == null || changeSupport == null) {
return;
}
changeSupport.removePropertyChangeListener(propertyName, listener);
}
// Managing Vetoable Change Listeners ***********************************
/**
* Adds a VetoableChangeListener to the listener list. The listener is
* registered for all bound properties of this class.
*
* If listener is {@code null}, no exception is thrown and no action is
* performed.
*
* @param listener the VetoableChangeListener to be added
*
* @see #removeVetoableChangeListener(String, VetoableChangeListener)
* @see #addVetoableChangeListener(String, VetoableChangeListener)
* @see #getVetoableChangeListeners()
*/
public final synchronized void addVetoableChangeListener(
VetoableChangeListener listener) {
if (listener == null) {
return;
}
if (vetoSupport == null) {
vetoSupport = new VetoableChangeSupport(this);
}
vetoSupport.addVetoableChangeListener(listener);
}
/**
* Removes a VetoableChangeListener from the listener list. This method
* should be used to remove VetoableChangeListeners that were registered
* for all bound properties of this class.
*
* If listener is {@code null}, no exception is thrown and no action is performed.
*
* @param listener the VetoableChangeListener to be removed
*
* @see #addVetoableChangeListener(String, VetoableChangeListener)
* @see #removeVetoableChangeListener(String, VetoableChangeListener)
* @see #getVetoableChangeListeners()
*/
public final synchronized void removeVetoableChangeListener(
VetoableChangeListener listener) {
if (listener == null || vetoSupport == null) {
return;
}
vetoSupport.removeVetoableChangeListener(listener);
}
/**
* Adds a VetoableChangeListener to the listener list for a specific
* property. The specified property may be user-defined.
*
* Note that if this Model is inheriting a bound property, then no event
* will be fired in response to a change in the inherited property.
*
* If listener is {@code null}, no exception is thrown and no action is performed.
*
* @param propertyName one of the property names listed above
* @param listener the VetoableChangeListener to be added
*
* @see #removeVetoableChangeListener(String, VetoableChangeListener)
* @see #addVetoableChangeListener(String, VetoableChangeListener)
* @see #getVetoableChangeListeners(String)
*/
public final synchronized void addVetoableChangeListener(
String propertyName,
VetoableChangeListener listener) {
if (listener == null) {
return;
}
if (vetoSupport == null) {
vetoSupport = new VetoableChangeSupport(this);
}
vetoSupport.addVetoableChangeListener(propertyName, listener);
}
/**
* Removes a VetoableChangeListener from the listener list for a specific
* property. This method should be used to remove VetoableChangeListeners
* that were registered for a specific bound property.
*
* If listener is {@code null}, no exception is thrown and no action is performed.
*
* @param propertyName a valid property name
* @param listener the VetoableChangeListener to be removed
*
* @see #addVetoableChangeListener(String, VetoableChangeListener)
* @see #removeVetoableChangeListener(VetoableChangeListener)
* @see #getVetoableChangeListeners(String)
*/
public final synchronized void removeVetoableChangeListener(
String propertyName,
VetoableChangeListener listener) {
if (listener == null || vetoSupport == null) {
return;
}
vetoSupport.removeVetoableChangeListener(propertyName, listener);
}
// Requesting Listener Sets ***********************************************
/**
* Returns an array of all the property change listeners
* registered on this component.
*
* @return all of this component's {@code PropertyChangeListener}s
* or an empty array if no property change
* listeners are currently registered
*
* @see #addPropertyChangeListener(PropertyChangeListener)
* @see #removePropertyChangeListener(PropertyChangeListener)
* @see #getPropertyChangeListeners(String)
* @see PropertyChangeSupport#getPropertyChangeListeners()
*/
@Override
public final synchronized PropertyChangeListener[] getPropertyChangeListeners() {
if (changeSupport == null) {
return new PropertyChangeListener[0];
}
return changeSupport.getPropertyChangeListeners();
}
/**
* Returns an array of all the listeners which have been associated
* with the named property.
*
* @param propertyName the name of the property to lookup listeners
* @return all of the {@code PropertyChangeListeners} associated with
* the named property or an empty array if no listeners have
* been added
*
* @see #addPropertyChangeListener(String, PropertyChangeListener)
* @see #removePropertyChangeListener(String, PropertyChangeListener)
* @see #getPropertyChangeListeners()
*/
@Override
public final synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
if (changeSupport == null) {
return new PropertyChangeListener[0];
}
return changeSupport.getPropertyChangeListeners(propertyName);
}
/**
* Returns an array of all the property change listeners
* registered on this component.
*
* @return all of this component's {@code VetoableChangeListener}s
* or an empty array if no property change
* listeners are currently registered
*
* @see #addVetoableChangeListener(VetoableChangeListener)
* @see #removeVetoableChangeListener(VetoableChangeListener)
* @see #getVetoableChangeListeners(String)
* @see VetoableChangeSupport#getVetoableChangeListeners()
*/
public final synchronized VetoableChangeListener[] getVetoableChangeListeners() {
if (vetoSupport == null) {
return new VetoableChangeListener[0];
}
return vetoSupport.getVetoableChangeListeners();
}
/**
* Returns an array of all the listeners which have been associated
* with the named property.
*
* @param propertyName the name of the property to lookup listeners
* @return all of the {@code VetoableChangeListeners} associated with
* the named property or an empty array if no listeners have
* been added
*
* @see #addVetoableChangeListener(String, VetoableChangeListener)
* @see #removeVetoableChangeListener(String, VetoableChangeListener)
* @see #getVetoableChangeListeners()
*/
public final synchronized VetoableChangeListener[] getVetoableChangeListeners(String propertyName) {
if (vetoSupport == null) {
return new VetoableChangeListener[0];
}
return vetoSupport.getVetoableChangeListeners(propertyName);
}
/**
* Creates and returns a PropertyChangeSupport for the given bean.
* Invoked by the first call to {@link #addPropertyChangeListener}
* when lazily creating the sole change support instance used throughout
* this bean.
*
* This default implementation creates a {@code PropertyChangeSupport}.
* Subclasses may override to return other change support implementations.
* For example to ensure that listeners are notified in the Event dispatch
* thread (EDT change support).
* The JGoodies Binding uses an extended change support that allows
* to configure whether the old and new value are compared with
* {@code ==} or {@code equals}.
*
* @param bean the bean to create a change support for
* @return the new change support
*/
protected PropertyChangeSupport createPropertyChangeSupport(final Object bean) {
return new PropertyChangeSupport(bean);
}
// Firing Changes for Bound Properties **********************************
/**
* General support for reporting bound property changes. Sends the given
* PropertyChangeEvent to any registered PropertyChangeListener.
*
* Most bean setters will invoke the fireXXX methods that get
* a property name and the old and new value. However some frameworks
* and setters may prefer to use this general method.
* Also, this method allows to fire IndexedPropertyChangeEvents
* that have been introduced in Java 5.
*
* @param event describes the property change
*
* @since 1.3
*/
protected final void firePropertyChange(PropertyChangeEvent event) {
PropertyChangeSupport aChangeSupport = this.changeSupport;
if (aChangeSupport == null) {
return;
}
aChangeSupport.firePropertyChange(event);
}
/**
* Support for reporting bound property changes for Object properties.
* This method can be called when a bound property has changed and it will
* send the appropriate PropertyChangeEvent to any registered
* PropertyChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
*/
protected final void firePropertyChange(String propertyName,
Object oldValue,
Object newValue) {
PropertyChangeSupport aChangeSupport = this.changeSupport;
if (aChangeSupport == null) {
return;
}
aChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
/**
* Support for reporting bound property changes for boolean properties.
* This method can be called when a bound property has changed and it will
* send the appropriate PropertyChangeEvent to any registered
* PropertyChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
*/
protected final void firePropertyChange(String propertyName,
boolean oldValue,
boolean newValue) {
PropertyChangeSupport aChangeSupport = this.changeSupport;
if (aChangeSupport == null) {
return;
}
aChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
/**
* Support for reporting bound property changes for integer properties.
* This method can be called when a bound property has changed and it will
* send the appropriate PropertyChangeEvent to any registered
* PropertyChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
*/
protected final void firePropertyChange(String propertyName,
double oldValue,
double newValue) {
firePropertyChange(propertyName, Double.valueOf(oldValue), Double.valueOf(newValue));
}
/**
* Support for reporting bound property changes for integer properties.
* This method can be called when a bound property has changed and it will
* send the appropriate PropertyChangeEvent to any registered
* PropertyChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
*/
protected final void firePropertyChange(String propertyName,
float oldValue,
float newValue) {
firePropertyChange(propertyName, Float.valueOf(oldValue), Float.valueOf(newValue));
}
/**
* Support for reporting bound property changes for integer properties.
* This method can be called when a bound property has changed and it will
* send the appropriate PropertyChangeEvent to any registered
* PropertyChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
*/
protected final void firePropertyChange(String propertyName,
int oldValue,
int newValue) {
PropertyChangeSupport aChangeSupport = this.changeSupport;
if (aChangeSupport == null) {
return;
}
aChangeSupport.firePropertyChange(propertyName,
Integer.valueOf(oldValue), Integer.valueOf(newValue));
}
/**
* Support for reporting bound property changes for integer properties.
* This method can be called when a bound property has changed and it will
* send the appropriate PropertyChangeEvent to any registered
* PropertyChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
*/
protected final void firePropertyChange(String propertyName,
long oldValue,
long newValue) {
firePropertyChange(propertyName, Long.valueOf(oldValue), Long.valueOf(newValue));
}
/**
* Indicates that an arbitrary set of bound properties have changed.
* Sends a PropertyChangeEvent with property name, old and new value
* set to {@code null} to any registered PropertyChangeListeners.
*
* @see java.beans.PropertyChangeEvent
*
* @since 1.0.3
*/
protected final void fireMultiplePropertiesChanged() {
firePropertyChange(null, null, null);
}
// Firing Indexed Changes *************************************************
/**
* Report a bound indexed property update to any registered listeners.
*
* No event is fired if old and new values are equal and non-null.
*
* @param propertyName The programmatic name of the property that
* was changed.
* @param index index of the property element that was changed.
* @param oldValue The old value of the property.
* @param newValue The new value of the property.
*
* @since 2.0
*/
protected final void fireIndexedPropertyChange(String propertyName, int index,
Object oldValue, Object newValue) {
PropertyChangeSupport aChangeSupport = this.changeSupport;
if (aChangeSupport == null) {
return;
}
aChangeSupport.fireIndexedPropertyChange(propertyName, index,
oldValue, newValue);
}
/**
* Report an {@code int} bound indexed property update
* to any registered listeners.
*
* No event is fired if old and new values are equal and non-null.
*
* This is merely a convenience wrapper around the more general
* fireIndexedPropertyChange method which takes Object values.
*
* @param propertyName The programmatic name of the property that
* was changed.
* @param index index of the property element that was changed.
* @param oldValue The old value of the property.
* @param newValue The new value of the property.
*
* @since 2.0
*/
protected final void fireIndexedPropertyChange(String propertyName, int index,
int oldValue, int newValue) {
if (oldValue == newValue) {
return;
}
fireIndexedPropertyChange(propertyName, index,
Integer.valueOf(oldValue),
Integer.valueOf(newValue));
}
/**
* Report a {@code boolean} bound indexed property update
* to any registered listeners.
*
* No event is fired if old and new values are equal and non-null.
*
* This is merely a convenience wrapper around the more general
* fireIndexedPropertyChange method which takes Object values.
*
* @param propertyName The programmatic name of the property that
* was changed.
* @param index index of the property element that was changed.
* @param oldValue The old value of the property.
* @param newValue The new value of the property.
*
* @since 2.0
*/
protected final void fireIndexedPropertyChange(String propertyName, int index,
boolean oldValue, boolean newValue) {
if (oldValue == newValue) {
return;
}
fireIndexedPropertyChange(propertyName, index,
Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
}
// Firing Changes for Constrained Properties ****************************
/**
* General support for reporting constrained property changes. Sends the
* given PropertyChangeEvent to any registered PropertyChangeListener.
*
* Most bean setters will invoke the fireXXX methods that get
* a property name and the old and new value. However some frameworks
* and setters may prefer to use this general method.
* Also, this method allows to fire IndexedPropertyChangeEvents
* that have been introduced in Java 5.
*
* @param event describes the property change
* @throws PropertyVetoException if a constrained property change is rejected
*
* @since 1.3
*/
protected final void fireVetoableChange(PropertyChangeEvent event)
throws PropertyVetoException {
VetoableChangeSupport aVetoSupport = this.vetoSupport;
if (aVetoSupport == null) {
return;
}
aVetoSupport.fireVetoableChange(event);
}
/**
* Support for reporting changes for constrained Object properties. This
* method can be called before a constrained property will be changed and
* it will send the appropriate PropertyChangeEvent to any registered
* VetoableChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
* @throws PropertyVetoException if a constrained property change is rejected
*/
protected final void fireVetoableChange(String propertyName,
Object oldValue,
Object newValue)
throws PropertyVetoException {
VetoableChangeSupport aVetoSupport = this.vetoSupport;
if (aVetoSupport == null) {
return;
}
aVetoSupport.fireVetoableChange(propertyName, oldValue, newValue);
}
/**
* Support for reporting changes for constrained boolean properties. This
* method can be called before a constrained property will be changed and
* it will send the appropriate PropertyChangeEvent to any registered
* VetoableChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
* @throws PropertyVetoException if a constrained property change is rejected
*/
protected final void fireVetoableChange(String propertyName,
boolean oldValue,
boolean newValue)
throws PropertyVetoException {
VetoableChangeSupport aVetoSupport = this.vetoSupport;
if (aVetoSupport == null) {
return;
}
aVetoSupport.fireVetoableChange(propertyName, oldValue, newValue);
}
/**
* Support for reporting changes for constrained integer properties. This
* method can be called before a constrained property will be changed and
* it will send the appropriate PropertyChangeEvent to any registered
* VetoableChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
* @throws PropertyVetoException if a constrained property change is rejected
*/
protected final void fireVetoableChange(String propertyName,
double oldValue,
double newValue)
throws PropertyVetoException {
fireVetoableChange(propertyName, Double.valueOf(oldValue), Double.valueOf(newValue));
}
/**
* Support for reporting changes for constrained integer properties. This
* method can be called before a constrained property will be changed and
* it will send the appropriate PropertyChangeEvent to any registered
* VetoableChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
* @throws PropertyVetoException if a constrained property change is rejected
*/
protected final void fireVetoableChange(String propertyName,
int oldValue,
int newValue)
throws PropertyVetoException {
VetoableChangeSupport aVetoSupport = this.vetoSupport;
if (aVetoSupport == null) {
return;
}
aVetoSupport.fireVetoableChange(propertyName,
Integer.valueOf(oldValue), Integer.valueOf(newValue));
}
/**
* Support for reporting changes for constrained integer properties. This
* method can be called before a constrained property will be changed and
* it will send the appropriate PropertyChangeEvent to any registered
* VetoableChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
* @throws PropertyVetoException if a constrained property change is rejected
*/
protected final void fireVetoableChange(String propertyName,
float oldValue,
float newValue)
throws PropertyVetoException {
fireVetoableChange(propertyName, Float.valueOf(oldValue), Float.valueOf(newValue));
}
/**
* Support for reporting changes for constrained integer properties. This
* method can be called before a constrained property will be changed and
* it will send the appropriate PropertyChangeEvent to any registered
* VetoableChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
* @throws PropertyVetoException if a constrained property change is rejected
*/
protected final void fireVetoableChange(String propertyName,
long oldValue,
long newValue)
throws PropertyVetoException {
fireVetoableChange(propertyName, Long.valueOf(oldValue), Long.valueOf(newValue));
}
}