PropertyListeners.java revision 1508:a661018d34b8
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<>(listener.listeners);
58        }
59    }
60
61    /**
62     * Return aggregate listeners added to all PropertyListenerManagers
63     * @return the listenersAdded
64     */
65    public static long getListenersAdded() {
66        return listenersAdded.longValue();
67    }
68
69    /**
70     * Return aggregate listeners removed from all PropertyListenerManagers
71     * @return the listenersRemoved
72     */
73    public static long getListenersRemoved() {
74        return listenersRemoved.longValue();
75    }
76
77    /**
78     * Return number of listeners added to a ScriptObject.
79     * @param obj the object
80     * @return the listener count
81     */
82    public static int getListenerCount(final ScriptObject obj) {
83        return obj.getMap().getListenerCount();
84    }
85
86    /**
87     * Return the number of listeners added to this PropertyListeners instance.
88     * @return the listener count;
89     */
90    public int getListenerCount() {
91        return listeners == null ? 0 : listeners.size();
92    }
93
94    // Property listener management methods
95
96    /**
97     * Add {@code propertyMap} as property listener to {@code listeners} using key {@code key} by
98     * creating and returning a new {@code PropertyListeners} instance.
99     *
100     * @param listeners the original property listeners instance, may be null
101     * @param key the property key
102     * @param propertyMap the property map
103     * @return the new property map
104     */
105    public static PropertyListeners addListener(final PropertyListeners listeners, final String key, final PropertyMap propertyMap) {
106        final PropertyListeners newListeners;
107        if (listeners == null || !listeners.containsListener(key, propertyMap)) {
108            newListeners = new PropertyListeners(listeners);
109            newListeners.addListener(key, propertyMap);
110            return newListeners;
111        }
112        return listeners;
113    }
114
115    /**
116     * Checks whether {@code propertyMap} is registered as listener with {@code key}.
117     *
118     * @param key the property key
119     * @param propertyMap the property map
120     * @return true if property map is registered with property key
121     */
122    synchronized boolean containsListener(final String key, final PropertyMap propertyMap) {
123        if (listeners == null) {
124            return false;
125        }
126        final WeakPropertyMapSet set = listeners.get(key);
127        return set != null && set.contains(propertyMap);
128    }
129
130    /**
131     * Add a property listener to this object.
132     *
133     * @param propertyMap The property listener that is added.
134     */
135    synchronized final void addListener(final String key, final PropertyMap propertyMap) {
136        if (Context.DEBUG) {
137            listenersAdded.increment();
138        }
139        if (listeners == null) {
140            listeners = new WeakHashMap<>();
141        }
142
143        WeakPropertyMapSet set = listeners.get(key);
144        if (set == null) {
145            set = new WeakPropertyMapSet();
146            listeners.put(key, set);
147        }
148        if (!set.contains(propertyMap)) {
149            set.add(propertyMap);
150        }
151    }
152
153    /**
154     * A new property is being added.
155     *
156     * @param prop The new Property added.
157     */
158    public synchronized void propertyAdded(final Property prop) {
159        if (listeners != null) {
160            final WeakPropertyMapSet set = listeners.get(prop.getKey());
161            if (set != null) {
162                for (final PropertyMap propertyMap : set.elements()) {
163                    propertyMap.propertyAdded(prop, false);
164                }
165                listeners.remove(prop.getKey());
166                if (Context.DEBUG) {
167                    listenersRemoved.increment();
168                }
169            }
170        }
171    }
172
173    /**
174     * An existing property is being deleted.
175     *
176     * @param prop The property being deleted.
177     */
178    public synchronized void propertyDeleted(final Property prop) {
179        if (listeners != null) {
180            final WeakPropertyMapSet set = listeners.get(prop.getKey());
181            if (set != null) {
182                for (final PropertyMap propertyMap : set.elements()) {
183                    propertyMap.propertyDeleted(prop, false);
184                }
185                listeners.remove(prop.getKey());
186                if (Context.DEBUG) {
187                    listenersRemoved.increment();
188                }
189            }
190        }
191    }
192
193    /**
194     * An existing Property is being replaced with a new Property.
195     *
196     * @param oldProp The old property that is being replaced.
197     * @param newProp The new property that replaces the old property.
198     *
199     */
200    public synchronized void propertyModified(final Property oldProp, final Property newProp) {
201        if (listeners != null) {
202            final WeakPropertyMapSet set = listeners.get(oldProp.getKey());
203            if (set != null) {
204                for (final PropertyMap propertyMap : set.elements()) {
205                    propertyMap.propertyModified(oldProp, newProp, false);
206                }
207                listeners.remove(oldProp.getKey());
208                if (Context.DEBUG) {
209                    listenersRemoved.increment();
210                }
211            }
212        }
213    }
214
215    /**
216     * Callback for when a proto is changed
217     */
218    public synchronized void protoChanged() {
219        if (listeners != null) {
220            for (final WeakPropertyMapSet set : listeners.values()) {
221                for (final PropertyMap propertyMap : set.elements()) {
222                    propertyMap.protoChanged(false);
223                }
224            }
225            listeners.clear();
226        }
227    }
228
229    private static class WeakPropertyMapSet {
230
231        private final WeakHashMap<PropertyMap, Boolean> map = new WeakHashMap<>();
232
233        void add(final PropertyMap propertyMap) {
234            map.put(propertyMap, Boolean.TRUE);
235        }
236
237        boolean contains(final PropertyMap propertyMap) {
238            return map.containsKey(propertyMap);
239        }
240
241        Set<PropertyMap> elements() {
242            return map.keySet();
243        }
244
245    }
246}
247