1 /*
   2  * Copyright (c) 2010, 2013, 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 
  26 package jdk.nashorn.internal.runtime;
  27 
  28 import java.util.Map;
  29 import java.util.Set;
  30 import java.util.WeakHashMap;
  31 
  32 /**
  33  * Helper class to manage property listeners and notification.
  34  */
  35 public class PropertyListeners {
  36 
  37     private Map<String, WeakPropertyMapSet> listeners;
  38 
  39     // These counters are updated in debug mode
  40     private static int listenersAdded;
  41     private static int listenersRemoved;
  42 
  43     /**
  44      * Copy constructor
  45      * @param listener listener to copy
  46      */
  47     PropertyListeners(final PropertyListeners listener) {
  48         if (listener != null && listener.listeners != null) {
  49             this.listeners = new WeakHashMap<>(listener.listeners);
  50         }
  51     }
  52 
  53     /**
  54      * Return aggregate listeners added to all PropertyListenerManagers
  55      * @return the listenersAdded
  56      */
  57     public static int getListenersAdded() {
  58         return listenersAdded;
  59     }
  60 
  61     /**
  62      * Return aggregate listeners removed from all PropertyListenerManagers
  63      * @return the listenersRemoved
  64      */
  65     public static int getListenersRemoved() {
  66         return listenersRemoved;
  67     }
  68 
  69     /**
  70      * Return listeners added to this ScriptObject.
  71      * @param obj the object
  72      * @return the listener count
  73      */
  74     public static int getListenerCount(final ScriptObject obj) {
  75         final PropertyListeners propertyListeners = obj.getMap().getListeners();
  76         if (propertyListeners != null) {
  77             return propertyListeners.listeners == null ? 0 : propertyListeners.listeners.size();
  78         }
  79         return 0;
  80     }
  81 
  82     // Property listener management methods
  83 
  84     /**
  85      * Add {@code propertyMap} as property listener to {@code listeners} using key {@code key} by
  86      * creating and returning a new {@code PropertyListeners} instance.
  87      *
  88      * @param listeners the original property listeners instance, may be null
  89      * @param key the property key
  90      * @param propertyMap the property map
  91      * @return the new property map
  92      */
  93     public static PropertyListeners addListener(final PropertyListeners listeners, final String key, final PropertyMap propertyMap) {
  94         final PropertyListeners newListeners;
  95         if (listeners == null || !listeners.containsListener(key, propertyMap)) {
  96             newListeners = new PropertyListeners(listeners);
  97             newListeners.addListener(key, propertyMap);
  98             return newListeners;
  99         }
 100         return listeners;
 101     }
 102 
 103     /**
 104      * Checks whether {@code propertyMap} is registered as listener with {@code key}.
 105      *
 106      * @param key the property key
 107      * @param propertyMap the property map
 108      * @return true if property map is registered with property key
 109      */
 110     synchronized boolean containsListener(final String key, final PropertyMap propertyMap) {
 111         if (listeners == null) {
 112             return false;
 113         }
 114         WeakPropertyMapSet set = listeners.get(key);
 115         return set != null && set.contains(propertyMap);
 116     }
 117 
 118     /**
 119      * Add a property listener to this object.
 120      *
 121      * @param propertyMap The property listener that is added.
 122      */
 123     synchronized final void addListener(final String key, final PropertyMap propertyMap) {
 124         if (Context.DEBUG) {
 125             listenersAdded++;
 126         }
 127         if (listeners == null) {
 128             listeners = new WeakHashMap<>();
 129         }
 130 
 131         WeakPropertyMapSet set = listeners.get(key);
 132         if (set == null) {
 133             set = new WeakPropertyMapSet();
 134             listeners.put(key, set);
 135         }
 136         if (!set.contains(propertyMap)) {
 137             set.add(propertyMap);
 138         }
 139     }
 140 
 141     /**
 142      * A new property is being added.
 143      *
 144      * @param prop The new Property added.
 145      */
 146     public synchronized void propertyAdded(final Property prop) {
 147         if (listeners != null) {
 148             WeakPropertyMapSet set = listeners.get(prop.getKey());
 149             if (set != null) {
 150                 for (PropertyMap propertyMap : set.elements()) {
 151                     propertyMap.propertyAdded(prop);
 152                 }
 153                 listeners.remove(prop.getKey());
 154             }
 155         }
 156     }
 157 
 158     /**
 159      * An existing property is being deleted.
 160      *
 161      * @param prop The property being deleted.
 162      */
 163     public synchronized void propertyDeleted(final Property prop) {
 164         if (listeners != null) {
 165             WeakPropertyMapSet set = listeners.get(prop.getKey());
 166             if (set != null) {
 167                 for (PropertyMap propertyMap : set.elements()) {
 168                     propertyMap.propertyDeleted(prop);
 169                 }
 170                 listeners.remove(prop.getKey());
 171             }
 172         }
 173     }
 174 
 175     /**
 176      * An existing Property is being replaced with a new Property.
 177      *
 178      * @param oldProp The old property that is being replaced.
 179      * @param newProp The new property that replaces the old property.
 180      *
 181      */
 182     public synchronized void propertyModified(final Property oldProp, final Property newProp) {
 183         if (listeners != null) {
 184             WeakPropertyMapSet set = listeners.get(oldProp.getKey());
 185             if (set != null) {
 186                 for (PropertyMap propertyMap : set.elements()) {
 187                     propertyMap.propertyModified(oldProp, newProp);
 188                 }
 189                 listeners.remove(oldProp.getKey());
 190             }
 191         }
 192     }
 193 
 194     public synchronized void protoChanged() {
 195         if (listeners != null) {
 196             for (WeakPropertyMapSet set : listeners.values()) {
 197                 for (PropertyMap propertyMap : set.elements()) {
 198                     propertyMap.protoChanged();
 199                 }
 200             }
 201             listeners.clear();
 202         }
 203     }
 204 
 205     private static class WeakPropertyMapSet {
 206 
 207         private WeakHashMap<PropertyMap, Boolean> map = new WeakHashMap<>();
 208 
 209         void add(final PropertyMap propertyMap) {
 210             map.put(propertyMap, Boolean.TRUE);
 211         }
 212 
 213         boolean contains(final PropertyMap propertyMap) {
 214             return map.containsKey(propertyMap);
 215         }
 216 
 217         Set<PropertyMap> elements() {
 218             return map.keySet();
 219         }
 220 
 221     }
 222 }