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
26package jdk.nashorn.internal.runtime;
27
28import java.util.Map;
29import java.util.Set;
30import java.util.WeakHashMap;
31import java.util.concurrent.atomic.LongAdder;
32
33/**
34 * Helper class to manage property listeners and notification.
35 */
36public 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}
261