jaroslav@601: /* jaroslav@601: * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. jaroslav@601: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jaroslav@601: * jaroslav@601: * This code is free software; you can redistribute it and/or modify it jaroslav@601: * under the terms of the GNU General Public License version 2 only, as jaroslav@601: * published by the Free Software Foundation. Oracle designates this jaroslav@601: * particular file as subject to the "Classpath" exception as provided jaroslav@601: * by Oracle in the LICENSE file that accompanied this code. jaroslav@601: * jaroslav@601: * This code is distributed in the hope that it will be useful, but WITHOUT jaroslav@601: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jaroslav@601: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jaroslav@601: * version 2 for more details (a copy is included in the LICENSE file that jaroslav@601: * accompanied this code). jaroslav@601: * jaroslav@601: * You should have received a copy of the GNU General Public License version jaroslav@601: * 2 along with this work; if not, write to the Free Software Foundation, jaroslav@601: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jaroslav@601: * jaroslav@601: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jaroslav@601: * or visit www.oracle.com if you need additional information or have any jaroslav@601: * questions. jaroslav@601: */ jaroslav@601: package java.beans; jaroslav@601: jaroslav@601: import java.util.ArrayList; jaroslav@601: import java.util.Collections; jaroslav@601: import java.util.EventListener; jaroslav@601: import java.util.EventListenerProxy; jaroslav@601: import java.util.HashMap; jaroslav@601: import java.util.List; jaroslav@601: import java.util.Map; jaroslav@601: import java.util.Map.Entry; jaroslav@601: import java.util.Set; jaroslav@604: import org.apidesign.bck2brwsr.emul.lang.System; jaroslav@601: jaroslav@601: /** jaroslav@601: * This is an abstract class that provides base functionality jaroslav@601: * for the {@link PropertyChangeSupport PropertyChangeSupport} class jaroslav@601: * and the {@link VetoableChangeSupport VetoableChangeSupport} class. jaroslav@601: * jaroslav@601: * @see PropertyChangeListenerMap jaroslav@601: * @see VetoableChangeListenerMap jaroslav@601: * jaroslav@601: * @author Sergey A. Malenkov jaroslav@601: */ jaroslav@601: abstract class ChangeListenerMap { jaroslav@601: private Map map; jaroslav@601: jaroslav@601: /** jaroslav@601: * Creates an array of listeners. jaroslav@601: * This method can be optimized by using jaroslav@601: * the same instance of the empty array jaroslav@601: * when {@code length} is equal to {@code 0}. jaroslav@601: * jaroslav@601: * @param length the array length jaroslav@601: * @return an array with specified length jaroslav@601: */ jaroslav@601: protected abstract L[] newArray(int length); jaroslav@601: jaroslav@601: /** jaroslav@601: * Creates a proxy listener for the specified property. jaroslav@601: * jaroslav@601: * @param name the name of the property to listen on jaroslav@601: * @param listener the listener to process events jaroslav@601: * @return a proxy listener jaroslav@601: */ jaroslav@601: protected abstract L newProxy(String name, L listener); jaroslav@601: jaroslav@601: /** jaroslav@601: * Adds a listener to the list of listeners for the specified property. jaroslav@601: * This listener is called as many times as it was added. jaroslav@601: * jaroslav@601: * @param name the name of the property to listen on jaroslav@601: * @param listener the listener to process events jaroslav@601: */ jaroslav@601: public final synchronized void add(String name, L listener) { jaroslav@601: if (this.map == null) { jaroslav@601: this.map = new HashMap(); jaroslav@601: } jaroslav@601: L[] array = this.map.get(name); jaroslav@601: int size = (array != null) jaroslav@601: ? array.length jaroslav@601: : 0; jaroslav@601: jaroslav@601: L[] clone = newArray(size + 1); jaroslav@601: clone[size] = listener; jaroslav@601: if (array != null) { jaroslav@601: System.arraycopy(array, 0, clone, 0, size); jaroslav@601: } jaroslav@601: this.map.put(name, clone); jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Removes a listener from the list of listeners for the specified property. jaroslav@601: * If the listener was added more than once to the same event source, jaroslav@601: * this listener will be notified one less time after being removed. jaroslav@601: * jaroslav@601: * @param name the name of the property to listen on jaroslav@601: * @param listener the listener to process events jaroslav@601: */ jaroslav@601: public final synchronized void remove(String name, L listener) { jaroslav@601: if (this.map != null) { jaroslav@601: L[] array = this.map.get(name); jaroslav@601: if (array != null) { jaroslav@601: for (int i = 0; i < array.length; i++) { jaroslav@601: if (listener.equals(array[i])) { jaroslav@601: int size = array.length - 1; jaroslav@601: if (size > 0) { jaroslav@601: L[] clone = newArray(size); jaroslav@601: System.arraycopy(array, 0, clone, 0, i); jaroslav@601: System.arraycopy(array, i + 1, clone, i, size - i); jaroslav@601: this.map.put(name, clone); jaroslav@601: } jaroslav@601: else { jaroslav@601: this.map.remove(name); jaroslav@601: if (this.map.isEmpty()) { jaroslav@601: this.map = null; jaroslav@601: } jaroslav@601: } jaroslav@601: break; jaroslav@601: } jaroslav@601: } jaroslav@601: } jaroslav@601: } jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Returns the list of listeners for the specified property. jaroslav@601: * jaroslav@601: * @param name the name of the property jaroslav@601: * @return the corresponding list of listeners jaroslav@601: */ jaroslav@601: public final synchronized L[] get(String name) { jaroslav@601: return (this.map != null) jaroslav@601: ? this.map.get(name) jaroslav@601: : null; jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Sets new list of listeners for the specified property. jaroslav@601: * jaroslav@601: * @param name the name of the property jaroslav@601: * @param listeners new list of listeners jaroslav@601: */ jaroslav@601: public final void set(String name, L[] listeners) { jaroslav@601: if (listeners != null) { jaroslav@601: if (this.map == null) { jaroslav@601: this.map = new HashMap(); jaroslav@601: } jaroslav@601: this.map.put(name, listeners); jaroslav@601: } jaroslav@601: else if (this.map != null) { jaroslav@601: this.map.remove(name); jaroslav@601: if (this.map.isEmpty()) { jaroslav@601: this.map = null; jaroslav@601: } jaroslav@601: } jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Returns all listeners in the map. jaroslav@601: * jaroslav@601: * @return an array of all listeners jaroslav@601: */ jaroslav@601: public final synchronized L[] getListeners() { jaroslav@601: if (this.map == null) { jaroslav@601: return newArray(0); jaroslav@601: } jaroslav@601: List list = new ArrayList(); jaroslav@601: jaroslav@601: L[] listeners = this.map.get(null); jaroslav@601: if (listeners != null) { jaroslav@601: for (L listener : listeners) { jaroslav@601: list.add(listener); jaroslav@601: } jaroslav@601: } jaroslav@601: for (Entry entry : this.map.entrySet()) { jaroslav@601: String name = entry.getKey(); jaroslav@601: if (name != null) { jaroslav@601: for (L listener : entry.getValue()) { jaroslav@601: list.add(newProxy(name, listener)); jaroslav@601: } jaroslav@601: } jaroslav@601: } jaroslav@601: return list.toArray(newArray(list.size())); jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Returns listeners that have been associated with the named property. jaroslav@601: * jaroslav@601: * @param name the name of the property jaroslav@601: * @return an array of listeners for the named property jaroslav@601: */ jaroslav@601: public final L[] getListeners(String name) { jaroslav@601: if (name != null) { jaroslav@601: L[] listeners = get(name); jaroslav@601: if (listeners != null) { jaroslav@601: return listeners.clone(); jaroslav@601: } jaroslav@601: } jaroslav@601: return newArray(0); jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Indicates whether the map contains jaroslav@601: * at least one listener to be notified. jaroslav@601: * jaroslav@601: * @param name the name of the property jaroslav@601: * @return {@code true} if at least one listener exists or jaroslav@601: * {@code false} otherwise jaroslav@601: */ jaroslav@601: public final synchronized boolean hasListeners(String name) { jaroslav@601: if (this.map == null) { jaroslav@601: return false; jaroslav@601: } jaroslav@601: L[] array = this.map.get(null); jaroslav@601: return (array != null) || ((name != null) && (null != this.map.get(name))); jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Returns a set of entries from the map. jaroslav@601: * Each entry is a pair consisted of the property name jaroslav@601: * and the corresponding list of listeners. jaroslav@601: * jaroslav@601: * @return a set of entries from the map jaroslav@601: */ jaroslav@601: public final Set> getEntries() { jaroslav@601: return (this.map != null) jaroslav@601: ? this.map.entrySet() jaroslav@601: : Collections.>emptySet(); jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Extracts a real listener from the proxy listener. jaroslav@601: * It is necessary because default proxy class is not serializable. jaroslav@601: * jaroslav@601: * @return a real listener jaroslav@601: */ jaroslav@601: public final L extract(L listener) { jaroslav@601: while (listener instanceof EventListenerProxy) { jaroslav@601: EventListenerProxy proxy = (EventListenerProxy) listener; jaroslav@601: listener = proxy.getListener(); jaroslav@601: } jaroslav@601: return listener; jaroslav@601: } jaroslav@601: }