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<String, 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      */
PropertyListeners(final PropertyListeners listener)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<String, 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      */
getListenersAdded()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      */
getListenersRemoved()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      */
getListenerCount(final ScriptObject obj)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      */
getListenerCount()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      */
addListener(final PropertyListeners listeners, final String key, final PropertyMap propertyMap)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      */
containsListener(final String key, final PropertyMap propertyMap)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      */
addListener(final String key, final PropertyMap propertyMap)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      */
propertyAdded(final Property prop)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      */
propertyDeleted(final Property prop)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      */
propertyModified(final Property oldProp, final Property newProp)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      */
protoChanged()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 
WeakPropertyMapSet()239         WeakPropertyMapSet() {
240             this.map = new WeakHashMap<>();
241         }
242 
WeakPropertyMapSet(final WeakPropertyMapSet set)243         WeakPropertyMapSet(final WeakPropertyMapSet set) {
244             this.map = new WeakHashMap<>(set.map);
245         }
246 
add(final PropertyMap propertyMap)247         void add(final PropertyMap propertyMap) {
248             map.put(propertyMap, Boolean.TRUE);
249         }
250 
contains(final PropertyMap propertyMap)251         boolean contains(final PropertyMap propertyMap) {
252             return map.containsKey(propertyMap);
253         }
254 
elements()255         Set<PropertyMap> elements() {
256             return map.keySet();
257         }
258 
259     }
260 }
261