| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package java.beans; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.ObjectStreamField; |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.EventListener; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import libcore.util.Objects; |
| |
| /** |
| * Manages a list of listeners to be notified when a property changes. Listeners |
| * subscribe to be notified of all property changes, or of changes to a single |
| * named property. |
| * |
| * <p>This class is thread safe. No locking is necessary when subscribing or |
| * unsubscribing listeners, or when publishing events. Callers should be careful |
| * when publishing events because listeners may not be thread safe. |
| */ |
| public class PropertyChangeSupport implements Serializable { |
| |
| private static final long serialVersionUID = 6401253773779951803l; |
| private static final ObjectStreamField[] serialPersistentFields = { |
| new ObjectStreamField("source", Object.class), |
| new ObjectStreamField("children", Object.class), |
| new ObjectStreamField("propertyChangeSupportSerializedDataVersion", int.class), |
| }; |
| |
| private transient Object sourceBean; |
| |
| /** |
| * All listeners, including PropertyChangeListenerProxy listeners that are |
| * only be notified when the assigned property is changed. This list may be |
| * modified concurrently! |
| */ |
| private transient List<PropertyChangeListener> listeners |
| = new CopyOnWriteArrayList<PropertyChangeListener>(); |
| |
| /** |
| * Creates a new instance that uses the source bean as source for any event. |
| * |
| * @param sourceBean |
| * the bean used as source for all events. |
| */ |
| public PropertyChangeSupport(Object sourceBean) { |
| if (sourceBean == null) { |
| throw new NullPointerException("sourceBean == null"); |
| } |
| this.sourceBean = sourceBean; |
| } |
| |
| /** |
| * Fires a {@link PropertyChangeEvent} with the given name, old value and |
| * new value. As source the bean used to initialize this instance is used. |
| * If the old value and the new value are not null and equal the event will |
| * not be fired. |
| * |
| * @param propertyName |
| * the name of the property |
| * @param oldValue |
| * the old value of the property |
| * @param newValue |
| * the new value of the property |
| */ |
| public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { |
| firePropertyChange(new PropertyChangeEvent(sourceBean, propertyName, oldValue, newValue)); |
| } |
| |
| /** |
| * Fires an {@link IndexedPropertyChangeEvent} with the given name, old |
| * value, new value and index. As source the bean used to initialize this |
| * instance is used. If the old value and the new value are not null and |
| * equal the event will not be fired. |
| * |
| * @param propertyName |
| * the name of the property |
| * @param index |
| * the index |
| * @param oldValue |
| * the old value of the property |
| * @param newValue |
| * the new value of the property |
| */ |
| public void fireIndexedPropertyChange(String propertyName, int index, |
| Object oldValue, Object newValue) { |
| firePropertyChange(new IndexedPropertyChangeEvent(sourceBean, |
| propertyName, oldValue, newValue, index)); |
| } |
| |
| /** |
| * Unsubscribes {@code listener} from change notifications for the property |
| * named {@code propertyName}. If multiple subscriptions exist for {@code |
| * listener}, it will receive one fewer notifications when the property |
| * changes. If the property name or listener is null or not subscribed, this |
| * method silently does nothing. |
| */ |
| public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { |
| for (PropertyChangeListener p : listeners) { |
| if (equals(propertyName, listener, p)) { |
| listeners.remove(p); |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Returns true if two chains of PropertyChangeListenerProxies have the same |
| * names in the same order and bottom out in the same event listener. This |
| * method's signature is asymmetric to avoid allocating a proxy: if |
| * non-null, {@code aName} represents the first property name and {@code a} |
| * is its listener. |
| */ |
| private boolean equals(String aName, EventListener a, EventListener b) { |
| /* |
| * Each iteration of the loop attempts to match a pair of property names |
| * from a and b. If they don't match, the chains must not be equal! |
| */ |
| while (b instanceof PropertyChangeListenerProxy) { |
| PropertyChangeListenerProxy bProxy = (PropertyChangeListenerProxy) b; // unwrap b |
| String bName = bProxy.getPropertyName(); |
| b = bProxy.getListener(); |
| if (aName == null) { |
| if (!(a instanceof PropertyChangeListenerProxy)) { |
| return false; |
| } |
| PropertyChangeListenerProxy aProxy = (PropertyChangeListenerProxy) a; // unwrap a |
| aName = aProxy.getPropertyName(); |
| a = aProxy.getListener(); |
| } |
| if (!Objects.equal(aName, bName)) { |
| return false; // not equal; a and b subscribe to different properties |
| } |
| aName = null; |
| } |
| return aName == null && Objects.equal(a, b); |
| } |
| |
| /** |
| * Subscribes {@code listener} to change notifications for the property |
| * named {@code propertyName}. If the listener is already subscribed, it |
| * will receive an additional notification when the property changes. If the |
| * property name or listener is null, this method silently does nothing. |
| */ |
| public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { |
| if (listener != null && propertyName != null) { |
| listeners.add(new PropertyChangeListenerProxy(propertyName, listener)); |
| } |
| } |
| |
| /** |
| * Returns the subscribers to be notified when {@code propertyName} changes. |
| * This includes both listeners subscribed to all property changes and |
| * listeners subscribed to the named property only. |
| */ |
| public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { |
| List<PropertyChangeListener> result = new ArrayList<PropertyChangeListener>(); |
| for (PropertyChangeListener p : listeners) { |
| if (p instanceof PropertyChangeListenerProxy && Objects.equal( |
| propertyName, ((PropertyChangeListenerProxy) p).getPropertyName())) { |
| result.add(p); |
| } |
| } |
| return result.toArray(new PropertyChangeListener[result.size()]); |
| } |
| |
| /** |
| * Fires a property change of a boolean property with the given name. If the |
| * old value and the new value are not null and equal the event will not be |
| * fired. |
| * |
| * @param propertyName |
| * the property name |
| * @param oldValue |
| * the old value |
| * @param newValue |
| * the new value |
| */ |
| public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { |
| firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); |
| } |
| |
| /** |
| * Fires a property change of a boolean property with the given name. If the |
| * old value and the new value are not null and equal the event will not be |
| * fired. |
| * |
| * @param propertyName |
| * the property name |
| * @param index |
| * the index of the changed property |
| * @param oldValue |
| * the old value |
| * @param newValue |
| * the new value |
| */ |
| public void fireIndexedPropertyChange(String propertyName, int index, |
| boolean oldValue, boolean newValue) { |
| if (oldValue != newValue) { |
| fireIndexedPropertyChange(propertyName, index, |
| Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); |
| } |
| } |
| |
| /** |
| * Fires a property change of an integer property with the given name. If |
| * the old value and the new value are not null and equal the event will not |
| * be fired. |
| * |
| * @param propertyName |
| * the property name |
| * @param oldValue |
| * the old value |
| * @param newValue |
| * the new value |
| */ |
| public void firePropertyChange(String propertyName, int oldValue, int newValue) { |
| firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue)); |
| } |
| |
| /** |
| * Fires a property change of an integer property with the given name. If |
| * the old value and the new value are not null and equal the event will not |
| * be fired. |
| * |
| * @param propertyName |
| * the property name |
| * @param index |
| * the index of the changed property |
| * @param oldValue |
| * the old value |
| * @param newValue |
| * the new value |
| */ |
| public void fireIndexedPropertyChange(String propertyName, int index, |
| int oldValue, int newValue) { |
| if (oldValue != newValue) { |
| fireIndexedPropertyChange(propertyName, index, |
| Integer.valueOf(oldValue), Integer.valueOf(newValue)); |
| } |
| } |
| |
| /** |
| * Returns true if there are listeners registered to the property with the |
| * given name. |
| * |
| * @param propertyName |
| * the name of the property |
| * @return true if there are listeners registered to that property, false |
| * otherwise. |
| */ |
| public boolean hasListeners(String propertyName) { |
| for (PropertyChangeListener p : listeners) { |
| if (!(p instanceof PropertyChangeListenerProxy) || Objects.equal( |
| propertyName, ((PropertyChangeListenerProxy) p).getPropertyName())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Unsubscribes {@code listener} from change notifications for all |
| * properties. If the listener has multiple subscriptions, it will receive |
| * one fewer notification when properties change. If the property name or |
| * listener is null or not subscribed, this method silently does nothing. |
| */ |
| public void removePropertyChangeListener(PropertyChangeListener listener) { |
| for (PropertyChangeListener p : listeners) { |
| if (equals(null, listener, p)) { |
| listeners.remove(p); |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Subscribes {@code listener} to change notifications for all properties. |
| * If the listener is already subscribed, it will receive an additional |
| * notification. If the listener is null, this method silently does nothing. |
| */ |
| public void addPropertyChangeListener(PropertyChangeListener listener) { |
| if (listener != null) { |
| listeners.add(listener); |
| } |
| } |
| |
| /** |
| * Returns all subscribers. This includes both listeners subscribed to all |
| * property changes and listeners subscribed to a single property. |
| */ |
| public PropertyChangeListener[] getPropertyChangeListeners() { |
| return listeners.toArray(new PropertyChangeListener[0]); // 0 to avoid synchronization |
| } |
| |
| private void writeObject(ObjectOutputStream out) throws IOException { |
| /* |
| * The serialized form of this class uses PropertyChangeSupport to group |
| * PropertyChangeListeners subscribed to the same property name. |
| */ |
| Map<String, PropertyChangeSupport> map = new Hashtable<String, PropertyChangeSupport>(); |
| for (PropertyChangeListener p : listeners) { |
| if (p instanceof PropertyChangeListenerProxy && !(p instanceof Serializable)) { |
| PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) p; |
| PropertyChangeListener listener = (PropertyChangeListener) proxy.getListener(); |
| if (listener instanceof Serializable) { |
| PropertyChangeSupport list = map.get(proxy.getPropertyName()); |
| if (list == null) { |
| list = new PropertyChangeSupport(sourceBean); |
| map.put(proxy.getPropertyName(), list); |
| } |
| list.listeners.add(listener); |
| } |
| } |
| } |
| |
| ObjectOutputStream.PutField putFields = out.putFields(); |
| putFields.put("source", sourceBean); |
| putFields.put("children", map); |
| out.writeFields(); |
| |
| for (PropertyChangeListener p : listeners) { |
| if (p instanceof Serializable) { |
| out.writeObject(p); |
| } |
| } |
| out.writeObject(null); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| ObjectInputStream.GetField readFields = in.readFields(); |
| sourceBean = readFields.get("source", null); |
| listeners = new CopyOnWriteArrayList<PropertyChangeListener>(); |
| |
| Map<String, PropertyChangeSupport> children |
| = (Map<String, PropertyChangeSupport>) readFields.get("children", null); |
| if (children != null) { |
| for (Map.Entry<String, PropertyChangeSupport> entry : children.entrySet()) { |
| for (PropertyChangeListener p : entry.getValue().listeners) { |
| listeners.add(new PropertyChangeListenerProxy(entry.getKey(), p)); |
| } |
| } |
| } |
| |
| PropertyChangeListener listener; |
| while ((listener = (PropertyChangeListener) in.readObject()) != null) { |
| listeners.add(listener); |
| } |
| } |
| |
| /** |
| * Publishes a property change event to all listeners of that property. If |
| * the event's old and new values are equal (but non-null), no event will be |
| * published. |
| */ |
| public void firePropertyChange(PropertyChangeEvent event) { |
| String propertyName = event.getPropertyName(); |
| Object oldValue = event.getOldValue(); |
| Object newValue = event.getNewValue(); |
| if (newValue != null && oldValue != null && newValue.equals(oldValue)) { |
| return; |
| } |
| |
| notifyEachListener: |
| for (PropertyChangeListener p : listeners) { |
| // unwrap listener proxies until we get a mismatched name or the real listener |
| while (p instanceof PropertyChangeListenerProxy) { |
| PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) p; |
| if (!Objects.equal(proxy.getPropertyName(), propertyName)) { |
| continue notifyEachListener; |
| } |
| p = (PropertyChangeListener) proxy.getListener(); |
| } |
| p.propertyChange(event); |
| } |
| } |
| } |