Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/beans/PropertyChangeSupport.java
38829 views
/*1* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package java.beans;2526import java.io.Serializable;27import java.io.ObjectStreamField;28import java.io.ObjectOutputStream;29import java.io.ObjectInputStream;30import java.io.IOException;31import java.util.Hashtable;32import java.util.Map.Entry;3334/**35* This is a utility class that can be used by beans that support bound36* properties. It manages a list of listeners and dispatches37* {@link PropertyChangeEvent}s to them. You can use an instance of this class38* as a member field of your bean and delegate these types of work to it.39* The {@link PropertyChangeListener} can be registered for all properties40* or for a property specified by name.41* <p>42* Here is an example of {@code PropertyChangeSupport} usage that follows43* the rules and recommendations laid out in the JavaBeans™ specification:44* <pre>45* public class MyBean {46* private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);47*48* public void addPropertyChangeListener(PropertyChangeListener listener) {49* this.pcs.addPropertyChangeListener(listener);50* }51*52* public void removePropertyChangeListener(PropertyChangeListener listener) {53* this.pcs.removePropertyChangeListener(listener);54* }55*56* private String value;57*58* public String getValue() {59* return this.value;60* }61*62* public void setValue(String newValue) {63* String oldValue = this.value;64* this.value = newValue;65* this.pcs.firePropertyChange("value", oldValue, newValue);66* }67*68* [...]69* }70* </pre>71* <p>72* A {@code PropertyChangeSupport} instance is thread-safe.73* <p>74* This class is serializable. When it is serialized it will save75* (and restore) any listeners that are themselves serializable. Any76* non-serializable listeners will be skipped during serialization.77*78* @see VetoableChangeSupport79*/80public class PropertyChangeSupport implements Serializable {81private PropertyChangeListenerMap map = new PropertyChangeListenerMap();8283/**84* Constructs a <code>PropertyChangeSupport</code> object.85*86* @param sourceBean The bean to be given as the source for any events.87*/88public PropertyChangeSupport(Object sourceBean) {89if (sourceBean == null) {90throw new NullPointerException();91}92source = sourceBean;93}9495/**96* Add a PropertyChangeListener to the listener list.97* The listener is registered for all properties.98* The same listener object may be added more than once, and will be called99* as many times as it is added.100* If <code>listener</code> is null, no exception is thrown and no action101* is taken.102*103* @param listener The PropertyChangeListener to be added104*/105public void addPropertyChangeListener(PropertyChangeListener listener) {106if (listener == null) {107return;108}109if (listener instanceof PropertyChangeListenerProxy) {110PropertyChangeListenerProxy proxy =111(PropertyChangeListenerProxy)listener;112// Call two argument add method.113addPropertyChangeListener(proxy.getPropertyName(),114proxy.getListener());115} else {116this.map.add(null, listener);117}118}119120/**121* Remove a PropertyChangeListener from the listener list.122* This removes a PropertyChangeListener that was registered123* for all properties.124* If <code>listener</code> was added more than once to the same event125* source, it will be notified one less time after being removed.126* If <code>listener</code> is null, or was never added, no exception is127* thrown and no action is taken.128*129* @param listener The PropertyChangeListener to be removed130*/131public void removePropertyChangeListener(PropertyChangeListener listener) {132if (listener == null) {133return;134}135if (listener instanceof PropertyChangeListenerProxy) {136PropertyChangeListenerProxy proxy =137(PropertyChangeListenerProxy)listener;138// Call two argument remove method.139removePropertyChangeListener(proxy.getPropertyName(),140proxy.getListener());141} else {142this.map.remove(null, listener);143}144}145146/**147* Returns an array of all the listeners that were added to the148* PropertyChangeSupport object with addPropertyChangeListener().149* <p>150* If some listeners have been added with a named property, then151* the returned array will be a mixture of PropertyChangeListeners152* and <code>PropertyChangeListenerProxy</code>s. If the calling153* method is interested in distinguishing the listeners then it must154* test each element to see if it's a155* <code>PropertyChangeListenerProxy</code>, perform the cast, and examine156* the parameter.157*158* <pre>{@code159* PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();160* for (int i = 0; i < listeners.length; i++) {161* if (listeners[i] instanceof PropertyChangeListenerProxy) {162* PropertyChangeListenerProxy proxy =163* (PropertyChangeListenerProxy)listeners[i];164* if (proxy.getPropertyName().equals("foo")) {165* // proxy is a PropertyChangeListener which was associated166* // with the property named "foo"167* }168* }169* }170* }</pre>171*172* @see PropertyChangeListenerProxy173* @return all of the <code>PropertyChangeListeners</code> added or an174* empty array if no listeners have been added175* @since 1.4176*/177public PropertyChangeListener[] getPropertyChangeListeners() {178return this.map.getListeners();179}180181/**182* Add a PropertyChangeListener for a specific property. The listener183* will be invoked only when a call on firePropertyChange names that184* specific property.185* The same listener object may be added more than once. For each186* property, the listener will be invoked the number of times it was added187* for that property.188* If <code>propertyName</code> or <code>listener</code> is null, no189* exception is thrown and no action is taken.190*191* @param propertyName The name of the property to listen on.192* @param listener The PropertyChangeListener to be added193*/194public void addPropertyChangeListener(195String propertyName,196PropertyChangeListener listener) {197if (listener == null || propertyName == null) {198return;199}200listener = this.map.extract(listener);201if (listener != null) {202this.map.add(propertyName, listener);203}204}205206/**207* Remove a PropertyChangeListener for a specific property.208* If <code>listener</code> was added more than once to the same event209* source for the specified property, it will be notified one less time210* after being removed.211* If <code>propertyName</code> is null, no exception is thrown and no212* action is taken.213* If <code>listener</code> is null, or was never added for the specified214* property, no exception is thrown and no action is taken.215*216* @param propertyName The name of the property that was listened on.217* @param listener The PropertyChangeListener to be removed218*/219public void removePropertyChangeListener(220String propertyName,221PropertyChangeListener listener) {222if (listener == null || propertyName == null) {223return;224}225listener = this.map.extract(listener);226if (listener != null) {227this.map.remove(propertyName, listener);228}229}230231/**232* Returns an array of all the listeners which have been associated233* with the named property.234*235* @param propertyName The name of the property being listened to236* @return all of the <code>PropertyChangeListeners</code> associated with237* the named property. If no such listeners have been added,238* or if <code>propertyName</code> is null, an empty array is239* returned.240* @since 1.4241*/242public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {243return this.map.getListeners(propertyName);244}245246/**247* Reports a bound property update to listeners248* that have been registered to track updates of249* all properties or a property with the specified name.250* <p>251* No event is fired if old and new values are equal and non-null.252* <p>253* This is merely a convenience wrapper around the more general254* {@link #firePropertyChange(PropertyChangeEvent)} method.255*256* @param propertyName the programmatic name of the property that was changed257* @param oldValue the old value of the property258* @param newValue the new value of the property259*/260public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {261if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {262firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));263}264}265266/**267* Reports an integer bound property update to listeners268* that have been registered to track updates of269* all properties or a property with the specified name.270* <p>271* No event is fired if old and new values are equal.272* <p>273* This is merely a convenience wrapper around the more general274* {@link #firePropertyChange(String, Object, Object)} method.275*276* @param propertyName the programmatic name of the property that was changed277* @param oldValue the old value of the property278* @param newValue the new value of the property279*/280public void firePropertyChange(String propertyName, int oldValue, int newValue) {281if (oldValue != newValue) {282firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));283}284}285286/**287* Reports a boolean bound property update to listeners288* that have been registered to track updates of289* all properties or a property with the specified name.290* <p>291* No event is fired if old and new values are equal.292* <p>293* This is merely a convenience wrapper around the more general294* {@link #firePropertyChange(String, Object, Object)} method.295*296* @param propertyName the programmatic name of the property that was changed297* @param oldValue the old value of the property298* @param newValue the new value of the property299*/300public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {301if (oldValue != newValue) {302firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));303}304}305306/**307* Fires a property change event to listeners308* that have been registered to track updates of309* all properties or a property with the specified name.310* <p>311* No event is fired if the given event's old and new values are equal and non-null.312*313* @param event the {@code PropertyChangeEvent} to be fired314*/315public void firePropertyChange(PropertyChangeEvent event) {316Object oldValue = event.getOldValue();317Object newValue = event.getNewValue();318if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {319String name = event.getPropertyName();320321PropertyChangeListener[] common = this.map.get(null);322PropertyChangeListener[] named = (name != null)323? this.map.get(name)324: null;325326fire(common, event);327fire(named, event);328}329}330331private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {332if (listeners != null) {333for (PropertyChangeListener listener : listeners) {334listener.propertyChange(event);335}336}337}338339/**340* Reports a bound indexed property update to listeners341* that have been registered to track updates of342* all properties or a property with the specified name.343* <p>344* No event is fired if old and new values are equal and non-null.345* <p>346* This is merely a convenience wrapper around the more general347* {@link #firePropertyChange(PropertyChangeEvent)} method.348*349* @param propertyName the programmatic name of the property that was changed350* @param index the index of the property element that was changed351* @param oldValue the old value of the property352* @param newValue the new value of the property353* @since 1.5354*/355public void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue) {356if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {357firePropertyChange(new IndexedPropertyChangeEvent(source, propertyName, oldValue, newValue, index));358}359}360361/**362* Reports an integer bound indexed property update to listeners363* that have been registered to track updates of364* all properties or a property with the specified name.365* <p>366* No event is fired if old and new values are equal.367* <p>368* This is merely a convenience wrapper around the more general369* {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.370*371* @param propertyName the programmatic name of the property that was changed372* @param index the index of the property element that was changed373* @param oldValue the old value of the property374* @param newValue the new value of the property375* @since 1.5376*/377public void fireIndexedPropertyChange(String propertyName, int index, int oldValue, int newValue) {378if (oldValue != newValue) {379fireIndexedPropertyChange(propertyName, index, Integer.valueOf(oldValue), Integer.valueOf(newValue));380}381}382383/**384* Reports a boolean bound indexed property update to listeners385* that have been registered to track updates of386* all properties or a property with the specified name.387* <p>388* No event is fired if old and new values are equal.389* <p>390* This is merely a convenience wrapper around the more general391* {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.392*393* @param propertyName the programmatic name of the property that was changed394* @param index the index of the property element that was changed395* @param oldValue the old value of the property396* @param newValue the new value of the property397* @since 1.5398*/399public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue) {400if (oldValue != newValue) {401fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));402}403}404405/**406* Check if there are any listeners for a specific property, including407* those registered on all properties. If <code>propertyName</code>408* is null, only check for listeners registered on all properties.409*410* @param propertyName the property name.411* @return true if there are one or more listeners for the given property412*/413public boolean hasListeners(String propertyName) {414return this.map.hasListeners(propertyName);415}416417/**418* @serialData Null terminated list of <code>PropertyChangeListeners</code>.419* <p>420* At serialization time we skip non-serializable listeners and421* only serialize the serializable listeners.422*/423private void writeObject(ObjectOutputStream s) throws IOException {424Hashtable<String, PropertyChangeSupport> children = null;425PropertyChangeListener[] listeners = null;426synchronized (this.map) {427for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) {428String property = entry.getKey();429if (property == null) {430listeners = entry.getValue();431} else {432if (children == null) {433children = new Hashtable<>();434}435PropertyChangeSupport pcs = new PropertyChangeSupport(this.source);436pcs.map.set(null, entry.getValue());437children.put(property, pcs);438}439}440}441ObjectOutputStream.PutField fields = s.putFields();442fields.put("children", children);443fields.put("source", this.source);444fields.put("propertyChangeSupportSerializedDataVersion", 2);445s.writeFields();446447if (listeners != null) {448for (PropertyChangeListener l : listeners) {449if (l instanceof Serializable) {450s.writeObject(l);451}452}453}454s.writeObject(null);455}456457private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {458this.map = new PropertyChangeListenerMap();459460ObjectInputStream.GetField fields = s.readFields();461462@SuppressWarnings("unchecked")463Hashtable<String, PropertyChangeSupport> children = (Hashtable<String, PropertyChangeSupport>) fields.get("children", null);464this.source = fields.get("source", null);465fields.get("propertyChangeSupportSerializedDataVersion", 2);466467Object listenerOrNull;468while (null != (listenerOrNull = s.readObject())) {469this.map.add(null, (PropertyChangeListener)listenerOrNull);470}471if (children != null) {472for (Entry<String, PropertyChangeSupport> entry : children.entrySet()) {473for (PropertyChangeListener listener : entry.getValue().getPropertyChangeListeners()) {474this.map.add(entry.getKey(), listener);475}476}477}478}479480/**481* The object to be provided as the "source" for any generated events.482*/483private Object source;484485/**486* @serialField children Hashtable487* @serialField source Object488* @serialField propertyChangeSupportSerializedDataVersion int489*/490private static final ObjectStreamField[] serialPersistentFields = {491new ObjectStreamField("children", Hashtable.class),492new ObjectStreamField("source", Object.class),493new ObjectStreamField("propertyChangeSupportSerializedDataVersion", Integer.TYPE)494};495496/**497* Serialization version ID, so we're compatible with JDK 1.1498*/499static final long serialVersionUID = 6401253773779951803L;500501/**502* This is a {@link ChangeListenerMap ChangeListenerMap} implementation503* that works with {@link PropertyChangeListener PropertyChangeListener} objects.504*/505private static final class PropertyChangeListenerMap extends ChangeListenerMap<PropertyChangeListener> {506private static final PropertyChangeListener[] EMPTY = {};507508/**509* Creates an array of {@link PropertyChangeListener PropertyChangeListener} objects.510* This method uses the same instance of the empty array511* when {@code length} equals {@code 0}.512*513* @param length the array length514* @return an array with specified length515*/516@Override517protected PropertyChangeListener[] newArray(int length) {518return (0 < length)519? new PropertyChangeListener[length]520: EMPTY;521}522523/**524* Creates a {@link PropertyChangeListenerProxy PropertyChangeListenerProxy}525* object for the specified property.526*527* @param name the name of the property to listen on528* @param listener the listener to process events529* @return a {@code PropertyChangeListenerProxy} object530*/531@Override532protected PropertyChangeListener newProxy(String name, PropertyChangeListener listener) {533return new PropertyChangeListenerProxy(name, listener);534}535536/**537* {@inheritDoc}538*/539public final PropertyChangeListener extract(PropertyChangeListener listener) {540while (listener instanceof PropertyChangeListenerProxy) {541listener = ((PropertyChangeListenerProxy) listener).getListener();542}543return listener;544}545}546}547548549