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