
org.aksw.commons.collection.observable.ObservableMapImpl Maven / Gradle / Ivy
package org.aksw.commons.collection.observable;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.aksw.commons.collections.MapUtils;
import org.aksw.commons.collections.SinglePrefetchIterator;
import com.google.common.collect.ForwardingSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* A map the fires events on changes. Changes are also triggered on operations
* on the {@link #entrySet()} and .entrySet().iterater().remove().
*
* Changes are tracked in a {@link CollectionChangedEventImpl} instance which implements {@link PropertyChangeEvent}.
* The old value is the this map before the change, and the new value is a view with the pending changes applied.
* Hence, no needless copying in performed. Event handlers need to to make copies if they want a static
* snapshot using e.g. new LinkedHashMap<>((Map)event.getNewValue())
*
* TODO keySet() entrySt and values() should return ObservableCollections.
*
* @author raven
*
* @param
* @param
*/
public class ObservableMapImpl
extends AbstractMap
implements ObservableMap
{
protected Map delegate;
protected VetoableChangeSupport vcs = new VetoableChangeSupport(this);
protected PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public ObservableMapImpl(Map delegate) {
super();
this.delegate = delegate;
}
public static ObservableMap decorate(Map delegate) {
return new ObservableMapImpl<>(delegate);
}
@Override
public Runnable addVetoableChangeListener(VetoableChangeListener listener) {
vcs.addVetoableChangeListener(listener);
return () -> vcs.removeVetoableChangeListener(listener);
}
@Override
public Registration addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
// return () -> pcs.removePropertyChangeListener(listener);
return Registration.from(
() -> listener.propertyChange(new CollectionChangedEventImpl<>(
this, this, this,
Collections.emptySet(), Collections.emptySet(), Collections.emptySet())),
() -> pcs.removePropertyChangeListener(listener));
}
@Override
public V put(K key, V value) {
Map newItem = Collections.singletonMap(key, value);
Map removedItem = Collections.emptyMap();
if (delegate.containsKey(key)) {
removedItem = Collections.singletonMap(key, delegate.get(key));
}
{
Map oldValue = this;
Map newValue = MapUtils.union(oldValue, newItem);
try {
vcs.fireVetoableChange(new CollectionChangedEventImpl<>(
this, oldValue, newValue,
newItem.entrySet(), removedItem.entrySet(), Collections.emptySet()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
V result = delegate.put(key, value);
{
// FIXME The following line breaks the semantics of Map.containsKey
// when the key of the removedItem was not present before
Map oldValue = removedItem.isEmpty()
? Maps.filterKeys(this, k -> !Objects.equals(k, key))
: MapUtils.union(this, removedItem);
Map newValue = this;
pcs.firePropertyChange(new CollectionChangedEventImpl<>(
this, oldValue, newValue,
newItem.entrySet(), removedItem.entrySet(), Collections.emptySet()));
}
return result;
}
@Override
public V remove(Object key) {
V result = null;
if (delegate.containsKey(key)) {
V v = delegate.get(key);
@SuppressWarnings("unchecked")
K k = (K)key;
doRemoveEntry(k, v);
}
return result;
}
protected V doRemoveEntry(K key, V v) {
Map removedItem = Collections.singletonMap(key, v);
{
Map oldValue = this;
Map newValue = Maps.filterKeys(delegate, k -> !Objects.equals(k, key));
try {
vcs.fireVetoableChange(new CollectionChangedEventImpl<>(this, oldValue, newValue, Collections.emptySet(), removedItem.entrySet(), Collections.emptySet()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
V result = delegate.remove(key);
{
Map oldValue = MapUtils.union(this, removedItem);
Map newValue = this;
pcs.firePropertyChange(new CollectionChangedEventImpl<>(this, oldValue, newValue, Collections.emptySet(), removedItem.entrySet(), Collections.emptySet()));
}
return result;
}
protected void notifyClear() {
pcs.firePropertyChange(new CollectionChangedEventImpl<>(this,
this, Collections.emptyMap(),
Collections.emptySet(), this.entrySet(), Collections.emptySet()));
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public void clear() {
notifyClear();
delegate.clear();
}
/**
* TODO The result should be an ObservableSet
*
*/
@Override
public Set> entrySet() {
return new AbstractSet>() {
@Override
public void clear() {
notifyClear();
delegate.clear();
}
@Override
public boolean add(Entry e) {
boolean result = !contains(e);
if (!result) {
put(e.getKey(), e.getValue());
}
return result;
}
@Override
public boolean remove(Object o) {
boolean result = false;
if (o instanceof Entry) {
Entry, ?> e = (Entry, ?>)o;
result = ObservableMapImpl.this.remove(e.getKey(), e.getValue());
}
return result;
}
@Override
public boolean contains(Object o) {
boolean result = false;
if (o instanceof Entry) {
Entry, ?> e = (Entry, ?>)o;
Object k = e.getKey();
result = ObservableMapImpl.this.containsKey(k) &&
Objects.equals(e.getValue(), ObservableMapImpl.this.get(k));
}
return result;
}
@Override
public Iterator> iterator() {
Iterator> baseIt = delegate.entrySet().iterator();
return new SinglePrefetchIterator>() {
@Override
protected Entry prefetch() throws Exception {
return baseIt.hasNext() ? baseIt.next() : finish();
}
protected void doRemove(Entry item) {
doRemoveEntry(item.getKey(), item.getValue());
baseIt.remove();
}
};
}
@Override
public int size() {
return delegate.size();
}
};
}
// public static
public class ObservableKeySet
extends ForwardingSet
implements ObservableSet
{
@Override
protected Set delegate() {
return delegate.keySet();
}
public CollectionChangedEvent convertEvent(PropertyChangeEvent event) {
CollectionChangedEvent> ev = (CollectionChangedEvent>)event;
// Set> additions = (Set>)ev.getAdditions();
// Set> deletions = (Set>)ev.getDeletions();
@SuppressWarnings("unchecked")
Set oldKeySet = ((Map)ev.getOldValue()).keySet();
@SuppressWarnings("unchecked")
Set newKeySet = ((Map)ev.getNewValue()).keySet();
CollectionChangedEvent result = new CollectionChangedEventImpl<>(
this,
oldKeySet,
newKeySet,
Sets.difference(newKeySet, oldKeySet),
Sets.difference(oldKeySet, newKeySet),
Collections.emptySet());
return result;
}
@Override
public Runnable addVetoableChangeListener(VetoableChangeListener listener) {
return ObservableMapImpl.this.addVetoableChangeListener(event -> {
CollectionChangedEvent newEv = convertEvent(event);
if (newEv.hasChanges()) {
listener.vetoableChange(newEv);
}
});
}
@Override
public Registration addPropertyChangeListener(PropertyChangeListener listener) {
return ObservableMapImpl.this.addPropertyChangeListener(event -> {
CollectionChangedEvent newEv = convertEvent(event);
if (newEv.hasChanges()) {
listener.propertyChange(newEv);
}
});
}
@Override
public boolean delta(Collection extends K> additions, Collection> removals) {
throw new UnsupportedOperationException();
}
}
@Override
public ObservableSet keySet() {
return new ObservableKeySet();
}
@Override
public Collection values() {
return super.values();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy