All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.util.ObservableMap Maven / Gradle / Ivy

There is a newer version: 3.0.23
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package groovy.util;

import groovy.lang.Closure;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Map decorator that will trigger PropertyChangeEvents when a value changes.
* An optional Closure may be specified and will work as a filter, if it returns * true the property will trigger an event (if the value indeed changed), * otherwise it won't. The Closure may receive 1 or 2 parameters, the single one * being the value, the other one both the key and value, for example: *
 * // skip all properties whose value is a closure
 * def map = new ObservableMap( {!(it instanceof Closure)} )
 *
 * // skip all properties whose name matches a regex
 * def map = new ObservableMap( { name, value -> !(name =~ /[A-Z+]/) } )
 * 
* The current implementation will trigger specialized events in the following scenarios, * you need not register a different listener as those events extend from PropertyChangeEvent *
    *
  • ObservableMap.PropertyAddedEvent - a new property is added to the map
  • *
  • ObservableMap.PropertyRemovedEvent - a property is removed from the map
  • *
  • ObservableMap.PropertyUpdatedEvent - a property changes value (same as regular PropertyChangeEvent)
  • *
  • ObservableMap.PropertyClearedEvent - all properties have been removed from the map
  • *
  • ObservableMap.MultiPropertyEvent - triggered by calling map.putAll(), contains Added|Updated events
  • *
*

* Bound properties *

    *
  • content - read-only.
  • *
  • size - read-only.
  • *
*/ public class ObservableMap implements Map { private final Map delegate; private final PropertyChangeSupport pcs; private final Closure test; public static final String SIZE_PROPERTY = "size"; public static final String CONTENT_PROPERTY = "content"; public static final String CLEARED_PROPERTY = "cleared"; public ObservableMap() { this(new LinkedHashMap(), null); } public ObservableMap(Closure test) { this(new LinkedHashMap(), test); } public ObservableMap(Map delegate) { this(delegate, null); } public ObservableMap(Map delegate, Closure test) { this.delegate = delegate; this.test = test; pcs = new PropertyChangeSupport(this); } protected Map getMapDelegate() { return delegate; } protected Closure getTest() { return test; } public Map getContent() { return Collections.unmodifiableMap(delegate); } protected void firePropertyClearedEvent(Map values) { firePropertyEvent(new PropertyClearedEvent(this, values)); } protected void firePropertyAddedEvent(Object key, Object value) { firePropertyEvent(new PropertyAddedEvent(this, String.valueOf(key), value)); } protected void firePropertyUpdatedEvent(Object key, Object oldValue, Object newValue) { firePropertyEvent(new PropertyUpdatedEvent(this, String.valueOf(key), oldValue, newValue)); } protected void fireMultiPropertyEvent(List events) { firePropertyEvent(new MultiPropertyEvent(this, (PropertyEvent[]) events.toArray(new PropertyEvent[0]))); } protected void fireMultiPropertyEvent(PropertyEvent[] events) { firePropertyEvent(new MultiPropertyEvent(this, events)); } protected void firePropertyRemovedEvent(Object key, Object value) { firePropertyEvent(new PropertyRemovedEvent(this, String.valueOf(key), value)); } protected void firePropertyEvent(PropertyEvent event) { pcs.firePropertyChange(event); } protected void fireSizeChangedEvent(int oldValue, int newValue) { pcs.firePropertyChange(new PropertyChangeEvent(this, SIZE_PROPERTY, oldValue, newValue)); } // Map interface public void clear() { int oldSize = size(); Map values = new HashMap(); if (!delegate.isEmpty()) { values.putAll(delegate); } delegate.clear(); firePropertyClearedEvent(values); fireSizeChangedEvent(oldSize, size()); } public boolean containsKey(Object key) { return delegate.containsKey(key); } public boolean containsValue(Object value) { return delegate.containsValue(value); } public Set entrySet() { return delegate.entrySet(); } public boolean equals(Object o) { return delegate.equals(o); } public Object get(Object key) { return delegate.get(key); } public int hashCode() { return delegate.hashCode(); } public boolean isEmpty() { return delegate.isEmpty(); } public Set keySet() { return delegate.keySet(); } public Object put(Object key, Object value) { int oldSize = size(); Object oldValue = null; boolean newKey = !delegate.containsKey(key); if (test != null) { oldValue = delegate.put(key, value); Object result = null; if (test.getMaximumNumberOfParameters() == 2) { result = test.call(key, value); } else { result = test.call(value); } if (result instanceof Boolean && (Boolean) result) { if (newKey) { firePropertyAddedEvent(key, value); fireSizeChangedEvent(oldSize, size()); } else if (oldValue != value) { firePropertyUpdatedEvent(key, oldValue, value); } } } else { oldValue = delegate.put(key, value); if (newKey) { firePropertyAddedEvent(key, value); fireSizeChangedEvent(oldSize, size()); } else if (oldValue != value) { firePropertyUpdatedEvent(key, oldValue, value); } } return oldValue; } public void putAll(Map map) { int oldSize = size(); if (map != null) { List events = new ArrayList(); for (Object o : map.entrySet()) { Entry entry = (Entry) o; String key = String.valueOf(entry.getKey()); Object newValue = entry.getValue(); Object oldValue = null; boolean newKey = !delegate.containsKey(key); if (test != null) { oldValue = delegate.put(key, newValue); Object result = null; if (test.getMaximumNumberOfParameters() == 2) { result = test.call(key, newValue); } else { result = test.call(newValue); } if (result instanceof Boolean && (Boolean) result) { if (newKey) { events.add(new PropertyAddedEvent(this, key, newValue)); } else if (oldValue != newValue) { events.add(new PropertyUpdatedEvent(this, key, oldValue, newValue)); } } } else { oldValue = delegate.put(key, newValue); if (newKey) { events.add(new PropertyAddedEvent(this, key, newValue)); } else if (oldValue != newValue) { events.add(new PropertyUpdatedEvent(this, key, oldValue, newValue)); } } } if (!events.isEmpty()) { fireMultiPropertyEvent(events); fireSizeChangedEvent(oldSize, size()); } } } public Object remove(Object key) { int oldSize = size(); Object result = delegate.remove(key); if (key != null) { firePropertyRemovedEvent(key, result); fireSizeChangedEvent(oldSize, size()); } return result; } public int size() { return delegate.size(); } public int getSize() { return size(); } public Collection values() { return delegate.values(); } // observable interface public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { pcs.addPropertyChangeListener(propertyName, listener); } public PropertyChangeListener[] getPropertyChangeListeners() { return pcs.getPropertyChangeListeners(); } public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { return pcs.getPropertyChangeListeners(propertyName); } public void removePropertyChangeListener(PropertyChangeListener listener) { pcs.removePropertyChangeListener(listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { pcs.removePropertyChangeListener(propertyName, listener); } public boolean hasListeners(String propertyName) { return pcs.hasListeners(propertyName); } public enum ChangeType { ADDED, UPDATED, REMOVED, CLEARED, MULTI, NONE; public static final Object oldValue = new Object[0]; public static final Object newValue = new Object[0]; public static ChangeType resolve(int ordinal) { switch (ordinal) { case 0: return ADDED; case 2: return REMOVED; case 3: return CLEARED; case 4: return MULTI; case 5: return NONE; case 1: default: return UPDATED; } } } public abstract static class PropertyEvent extends PropertyChangeEvent { private static final long serialVersionUID = -8328412226044328674L; private ChangeType type; public PropertyEvent(Object source, String propertyName, Object oldValue, Object newValue, ChangeType type) { super(source, propertyName, oldValue, newValue); this.type = type; } public int getType() { return type.ordinal(); } public ChangeType getChangeType() { return type; } public String getTypeAsString() { return type.name().toUpperCase(); } } public static class PropertyAddedEvent extends PropertyEvent { private static final long serialVersionUID = -5761685843732329868L; public PropertyAddedEvent(Object source, String propertyName, Object newValue) { super(source, propertyName, null, newValue, ChangeType.ADDED); } } public static class PropertyUpdatedEvent extends PropertyEvent { private static final long serialVersionUID = -1104637722950032690L; public PropertyUpdatedEvent(Object source, String propertyName, Object oldValue, Object newValue) { super(source, propertyName, oldValue, newValue, ChangeType.UPDATED); } } public static class PropertyRemovedEvent extends PropertyEvent { private static final long serialVersionUID = 1882656655856158470L; public PropertyRemovedEvent(Object source, String propertyName, Object oldValue) { super(source, propertyName, oldValue, null, ChangeType.REMOVED); } } public static class PropertyClearedEvent extends PropertyEvent { private static final long serialVersionUID = -1472110679547513634L; private Map values = new HashMap(); public PropertyClearedEvent(Object source, Map values) { super(source, ObservableMap.CLEARED_PROPERTY, values, null, ChangeType.CLEARED); if (values != null) { this.values.putAll(values); } } public Map getValues() { return Collections.unmodifiableMap(values); } } public static class MultiPropertyEvent extends PropertyEvent { public static final String MULTI_PROPERTY = "groovy_util_ObservableMap_MultiPropertyEvent_MULTI"; private static final PropertyEvent[] EMPTY_PROPERTY_EVENTS = new PropertyEvent[0]; private static final long serialVersionUID = 3925136810810084267L; private final PropertyEvent[] events; public MultiPropertyEvent(Object source, PropertyEvent[] events) { super(source, MULTI_PROPERTY, ChangeType.oldValue, ChangeType.newValue, ChangeType.MULTI); if (events != null && events.length > 0) { this.events = new PropertyEvent[events.length]; System.arraycopy(events, 0, this.events, 0, events.length); } else { this.events = EMPTY_PROPERTY_EVENTS; } } public PropertyEvent[] getEvents() { PropertyEvent[] copy = new PropertyEvent[events.length]; System.arraycopy(events, 0, copy, 0, events.length); return copy; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy