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