blob: 3a46bc3b0e3e8c98821442689a968fb53e192c66 [file] [log] [blame]
/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.runtime;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.LongAdder;
/**
* Helper class to manage property listeners and notification.
*/
public class PropertyListeners {
private Map<String, WeakPropertyMapSet> listeners;
// These counters are updated in debug mode
private static LongAdder listenersAdded;
private static LongAdder listenersRemoved;
static {
if (Context.DEBUG) {
listenersAdded = new LongAdder();
listenersRemoved = new LongAdder();
}
}
/**
* Copy constructor
* @param listener listener to copy
*/
PropertyListeners(final PropertyListeners listener) {
if (listener != null && listener.listeners != null) {
this.listeners = new WeakHashMap<>();
// We need to copy the nested weak sets in order to avoid concurrent modification issues, see JDK-8146274
synchronized (listener) {
for (final Map.Entry<String, WeakPropertyMapSet> entry : listener.listeners.entrySet()) {
this.listeners.put(entry.getKey(), new WeakPropertyMapSet(entry.getValue()));
}
}
}
}
/**
* Return aggregate listeners added to all PropertyListenerManagers
* @return the listenersAdded
*/
public static long getListenersAdded() {
return listenersAdded.longValue();
}
/**
* Return aggregate listeners removed from all PropertyListenerManagers
* @return the listenersRemoved
*/
public static long getListenersRemoved() {
return listenersRemoved.longValue();
}
/**
* Return number of listeners added to a ScriptObject.
* @param obj the object
* @return the listener count
*/
public static int getListenerCount(final ScriptObject obj) {
return obj.getMap().getListenerCount();
}
/**
* Return the number of listeners added to this PropertyListeners instance.
* @return the listener count;
*/
public int getListenerCount() {
return listeners == null ? 0 : listeners.size();
}
// Property listener management methods
/**
* Add {@code propertyMap} as property listener to {@code listeners} using key {@code key} by
* creating and returning a new {@code PropertyListeners} instance.
*
* @param listeners the original property listeners instance, may be null
* @param key the property key
* @param propertyMap the property map
* @return the new property map
*/
public static PropertyListeners addListener(final PropertyListeners listeners, final String key, final PropertyMap propertyMap) {
final PropertyListeners newListeners;
if (listeners == null || !listeners.containsListener(key, propertyMap)) {
newListeners = new PropertyListeners(listeners);
newListeners.addListener(key, propertyMap);
return newListeners;
}
return listeners;
}
/**
* Checks whether {@code propertyMap} is registered as listener with {@code key}.
*
* @param key the property key
* @param propertyMap the property map
* @return true if property map is registered with property key
*/
synchronized boolean containsListener(final String key, final PropertyMap propertyMap) {
if (listeners == null) {
return false;
}
final WeakPropertyMapSet set = listeners.get(key);
return set != null && set.contains(propertyMap);
}
/**
* Add a property listener to this object.
*
* @param propertyMap The property listener that is added.
*/
synchronized final void addListener(final String key, final PropertyMap propertyMap) {
if (Context.DEBUG) {
listenersAdded.increment();
}
if (listeners == null) {
listeners = new WeakHashMap<>();
}
WeakPropertyMapSet set = listeners.get(key);
if (set == null) {
set = new WeakPropertyMapSet();
listeners.put(key, set);
}
if (!set.contains(propertyMap)) {
set.add(propertyMap);
}
}
/**
* A new property is being added.
*
* @param prop The new Property added.
*/
public synchronized void propertyAdded(final Property prop) {
if (listeners != null) {
final WeakPropertyMapSet set = listeners.get(prop.getKey());
if (set != null) {
for (final PropertyMap propertyMap : set.elements()) {
propertyMap.propertyAdded(prop, false);
}
listeners.remove(prop.getKey());
if (Context.DEBUG) {
listenersRemoved.increment();
}
}
}
}
/**
* An existing property is being deleted.
*
* @param prop The property being deleted.
*/
public synchronized void propertyDeleted(final Property prop) {
if (listeners != null) {
final WeakPropertyMapSet set = listeners.get(prop.getKey());
if (set != null) {
for (final PropertyMap propertyMap : set.elements()) {
propertyMap.propertyDeleted(prop, false);
}
listeners.remove(prop.getKey());
if (Context.DEBUG) {
listenersRemoved.increment();
}
}
}
}
/**
* An existing Property is being replaced with a new Property.
*
* @param oldProp The old property that is being replaced.
* @param newProp The new property that replaces the old property.
*
*/
public synchronized void propertyModified(final Property oldProp, final Property newProp) {
if (listeners != null) {
final WeakPropertyMapSet set = listeners.get(oldProp.getKey());
if (set != null) {
for (final PropertyMap propertyMap : set.elements()) {
propertyMap.propertyModified(oldProp, newProp, false);
}
listeners.remove(oldProp.getKey());
if (Context.DEBUG) {
listenersRemoved.increment();
}
}
}
}
/**
* Callback for when a proto is changed
*/
public synchronized void protoChanged() {
if (listeners != null) {
for (final WeakPropertyMapSet set : listeners.values()) {
for (final PropertyMap propertyMap : set.elements()) {
propertyMap.protoChanged(false);
}
}
listeners.clear();
}
}
private static class WeakPropertyMapSet {
private final WeakHashMap<PropertyMap, Boolean> map;
WeakPropertyMapSet() {
this.map = new WeakHashMap<>();
}
WeakPropertyMapSet(final WeakPropertyMapSet set) {
this.map = new WeakHashMap<>(set.map);
}
void add(final PropertyMap propertyMap) {
map.put(propertyMap, Boolean.TRUE);
}
boolean contains(final PropertyMap propertyMap) {
return map.containsKey(propertyMap);
}
Set<PropertyMap> elements() {
return map.keySet();
}
}
}