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

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 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