1 /*
   2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package java.beans;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Collections;
  29 import java.util.EventListener;
  30 import java.util.EventListenerProxy;
  31 import java.util.HashMap;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.Map.Entry;
  35 import java.util.Set;
  36 
  37 /**
  38  * This is an abstract class that provides base functionality
  39  * for the {@link PropertyChangeSupport PropertyChangeSupport} class
  40  * and the {@link VetoableChangeSupport VetoableChangeSupport} class.
  41  *
  42  * @see PropertyChangeListenerMap
  43  * @see VetoableChangeListenerMap
  44  *
  45  * @author Sergey A. Malenkov
  46  */
  47 abstract class ChangeListenerMap<L extends EventListener> {
  48     private Map<String, L[]> map;
  49 
  50     /**
  51      * Creates an array of listeners.
  52      * This method can be optimized by using
  53      * the same instance of the empty array
  54      * when {@code length} is equal to {@code 0}.
  55      *
  56      * @param length  the array length
  57      * @return        an array with specified length
  58      */
  59     protected abstract L[] newArray(int length);
  60 
  61     /**
  62      * Creates a proxy listener for the specified property.
  63      *
  64      * @param name      the name of the property to listen on
  65      * @param listener  the listener to process events
  66      * @return          a proxy listener
  67      */
  68     protected abstract L newProxy(String name, L listener);
  69 
  70     /**
  71      * Adds a listener to the list of listeners for the specified property.
  72      * This listener is called as many times as it was added.
  73      *
  74      * @param name      the name of the property to listen on
  75      * @param listener  the listener to process events
  76      */
  77     public final synchronized void add(String name, L listener) {
  78         if (this.map == null) {
  79             this.map = new HashMap<>();
  80         }
  81         L[] array = this.map.get(name);
  82         int size = (array != null)
  83                 ? array.length
  84                 : 0;
  85 
  86         L[] clone = newArray(size + 1);
  87         clone[size] = listener;
  88         if (array != null) {
  89             System.arraycopy(array, 0, clone, 0, size);
  90         }
  91         this.map.put(name, clone);
  92     }
  93 
  94     /**
  95      * Removes a listener from the list of listeners for the specified property.
  96      * If the listener was added more than once to the same event source,
  97      * this listener will be notified one less time after being removed.
  98      *
  99      * @param name      the name of the property to listen on
 100      * @param listener  the listener to process events
 101      */
 102     public final synchronized void remove(String name, L listener) {
 103         if (this.map != null) {
 104             L[] array = this.map.get(name);
 105             if (array != null) {
 106                 for (int i = 0; i < array.length; i++) {
 107                     if (listener.equals(array[i])) {
 108                         int size = array.length - 1;
 109                         if (size > 0) {
 110                             L[] clone = newArray(size);
 111                             System.arraycopy(array, 0, clone, 0, i);
 112                             System.arraycopy(array, i + 1, clone, i, size - i);
 113                             this.map.put(name, clone);
 114                         }
 115                         else {
 116                             this.map.remove(name);
 117                             if (this.map.isEmpty()) {
 118                                 this.map = null;
 119                             }
 120                         }
 121                         break;
 122                     }
 123                 }
 124             }
 125         }
 126     }
 127 
 128     /**
 129      * Returns the list of listeners for the specified property.
 130      *
 131      * @param name  the name of the property
 132      * @return      the corresponding list of listeners
 133      */
 134     public final synchronized L[] get(String name) {
 135         return (this.map != null)
 136                 ? this.map.get(name)
 137                 : null;
 138     }
 139 
 140     /**
 141      * Sets new list of listeners for the specified property.
 142      *
 143      * @param name       the name of the property
 144      * @param listeners  new list of listeners
 145      */
 146     public final void set(String name, L[] listeners) {
 147         if (listeners != null) {
 148             if (this.map == null) {
 149                 this.map = new HashMap<>();
 150             }
 151             this.map.put(name, listeners);
 152         }
 153         else if (this.map != null) {
 154             this.map.remove(name);
 155             if (this.map.isEmpty()) {
 156                 this.map = null;
 157             }
 158         }
 159     }
 160 
 161     /**
 162      * Returns all listeners in the map.
 163      *
 164      * @return an array of all listeners
 165      */
 166     public final synchronized L[] getListeners() {
 167         if (this.map == null) {
 168             return newArray(0);
 169         }
 170         List<L> list = new ArrayList<>();
 171 
 172         L[] listeners = this.map.get(null);
 173         if (listeners != null) {
 174             for (L listener : listeners) {
 175                 list.add(listener);
 176             }
 177         }
 178         for (Entry<String, L[]> entry : this.map.entrySet()) {
 179             String name = entry.getKey();
 180             if (name != null) {
 181                 for (L listener : entry.getValue()) {
 182                     list.add(newProxy(name, listener));
 183                 }
 184             }
 185         }
 186         return list.toArray(newArray(list.size()));
 187     }
 188 
 189     /**
 190      * Returns listeners that have been associated with the named property.
 191      *
 192      * @param name  the name of the property
 193      * @return an array of listeners for the named property
 194      */
 195     public final L[] getListeners(String name) {
 196         if (name != null) {
 197             L[] listeners = get(name);
 198             if (listeners != null) {
 199                 return listeners.clone();
 200             }
 201         }
 202         return newArray(0);
 203     }
 204 
 205     /**
 206      * Indicates whether the map contains
 207      * at least one listener to be notified.
 208      *
 209      * @param name  the name of the property
 210      * @return      {@code true} if at least one listener exists or
 211      *              {@code false} otherwise
 212      */
 213     public final synchronized boolean hasListeners(String name) {
 214         if (this.map == null) {
 215             return false;
 216         }
 217         L[] array = this.map.get(null);
 218         return (array != null) || ((name != null) && (null != this.map.get(name)));
 219     }
 220 
 221     /**
 222      * Returns a set of entries from the map.
 223      * Each entry is a pair consisted of the property name
 224      * and the corresponding list of listeners.
 225      *
 226      * @return a set of entries from the map
 227      */
 228     public final Set<Entry<String, L[]>> getEntries() {
 229         return (this.map != null)
 230                 ? this.map.entrySet()
 231                 : Collections.<Entry<String, L[]>>emptySet();
 232     }
 233 
 234     /**
 235      * Extracts a real listener from the proxy listener.
 236      * It is necessary because default proxy class is not serializable.
 237      *
 238      * @return a real listener
 239      */
 240     public abstract L extract(L listener);
 241 }