com.doctusoft.bean.binding.observable.ObservableMap.java Source code

Java tutorial

Introduction

Here is the source code for com.doctusoft.bean.binding.observable.ObservableMap.java

Source

package com.doctusoft.bean.binding.observable;

/*
 * #%L
 * ds-bean-binding
 * %%
 * Copyright (C) 2014 Doctusoft Ltd.
 * %%
 * Licensed 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.
 * #L%
 */

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

import com.doctusoft.bean.GenericListeners;
import com.doctusoft.bean.ListenerRegistration;
import com.doctusoft.bean.binding.observable.ObservableList.ListElementRemovedListener;
import com.doctusoft.bean.binding.observable.ObservableSet.SetElementRemovedListener;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class ObservableMap<K, V> extends ForwardingMap<K, V> implements Serializable {

    protected InsertListeners<K, V> insertListeners = new InsertListeners<K, V>();
    protected RemoveListeners<K, V> removeListeners = new RemoveListeners<K, V>();

    protected Map<K, V> delegate;

    @Override
    protected Map<K, V> delegate() {
        return delegate;
    }

    public ObservableMap() {
        delegate = Maps.newHashMap();
        initReflectingSets();
    }

    public ObservableMap(int size) {
        delegate = Maps.newHashMapWithExpectedSize(size);
        initReflectingSets();
    }

    /**
     * These reflecting set need to be initiated before any other listeners get attached.
     * If an attached insertlistener would rely on entrySet() for example, then entrySet() would be called after registering this insert listener.
     * This would cause that the reflecting entry set gets updated after the specific insert listener is invoked, so that listener wouldn't yet see the inserted item in entrySet()
     */
    private void initReflectingSets() {
        entrySet();
        keySet();
        values();
    }

    public ListenerRegistration addInsertListener(MapElementInsertedListener<K, V> listener) {
        return insertListeners.addListener(listener);
    }

    public ListenerRegistration addDeleteListener(MapElementRemovedListener<K, V> listener) {
        return removeListeners.addListener(listener);
    }

    public interface MapElementInsertedListener<K, V> extends Serializable {
        public void inserted(ObservableMap<K, V> map, K key, V element);
    }

    public interface MapElementRemovedListener<K, V> extends Serializable {
        public void removed(ObservableMap<K, V> map, K key, V element);
    }

    protected class InsertListeners<K, V> extends GenericListeners<ObservableMap.MapElementInsertedListener<K, V>> {
        public void fireEvent(final ObservableMap<K, V> map, final K key, final V element) {
            forEachListener(new ListenerCallback<ObservableMap.MapElementInsertedListener<K, V>>() {
                public void apply(ObservableMap.MapElementInsertedListener<K, V> listener) {
                    listener.inserted(map, key, element);
                };
            });
        }
    };

    protected class RemoveListeners<K, V> extends GenericListeners<ObservableMap.MapElementRemovedListener<K, V>> {
        public void fireEvent(final ObservableMap<K, V> map, final K key, final V element) {
            forEachListener(new ListenerCallback<ObservableMap.MapElementRemovedListener<K, V>>() {
                public void apply(ObservableMap.MapElementRemovedListener<K, V> listener) {
                    listener.removed(map, key, element);
                };
            });
        }
    };

    // overriding the Map interface methods that change to content:

    @Override
    public void clear() {
        for (K key : Sets.newHashSet(this.keySet())) {
            remove(key);
        }
    }

    @Override
    public V put(K key, V value) {
        boolean contained = super.containsKey(key);
        V previous = super.get(key);
        if (contained) {
            super.remove(key); // it's important to remove it first, not just replace it with put() in one step! otherwise the keySet will call back and remove the newly placed element
            removeListeners.fireEvent(this, key, previous);
        }
        super.put(key, value);
        insertListeners.fireEvent(this, key, value);
        return previous;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public V remove(Object key) {
        boolean contained = super.containsKey(key);
        V removed = super.remove(key);
        if (contained) {
            removeListeners.fireEvent(this, (K) key, removed);
        }
        return removed;
    }

    /* The keySet, values and entrySet collections must be such, that changes to them are reflected in the original collection and vica-versa
     * (And thus they should trigger the change handlers, but the default implementations avoid that, modifying the map under the hood, not by its public methods). */

    /* Since the change handlers broadcast to everyone, there is always a redundant callback to the original sender in case of two-way bindings like here between the map and its views.
     * However this doesn't cause an infinite loop in these cases, because the remove operations stop propagating when there is nothing to remove 
     * (e.g. map.remove() -> keySet.remove() -> map.remove(), but the last one returns null and doesn't fire any more handlers since there was no change to the collection) */

    private ObservableList<V> values;

    @Override
    public Collection<V> values() {
        if (values == null) {
            values = new ObservableList<V>(delegate.values());
            values.addDeleteListener(new ListElementRemovedListener<V>() {
                @Override
                public void removed(ObservableList<V> list, int index, V element) {
                    Set<K> keysToRemove = Sets.newHashSet();
                    for (Entry<K, V> entry : delegate.entrySet()) {
                        if (entry.getValue() == element) { // intentially == not equals
                            keysToRemove.add(entry.getKey());
                        }
                    }
                    for (K key : keysToRemove) {
                        remove(key);
                    }
                }
            });
            addInsertListener(new MapElementInsertedListener<K, V>() {
                @Override
                public void inserted(ObservableMap<K, V> map, K key, V element) {
                    values.add(element);
                }
            });
            addDeleteListener(new MapElementRemovedListener<K, V>() {
                @Override
                public void removed(ObservableMap<K, V> map, K key, V element) {
                    values.remove(element);
                }
            });
        }
        return values;
    }

    private ObservableSet<K> keySet;

    @Override
    public Set<K> keySet() {
        if (keySet == null) {
            keySet = new ObservableSet<K>(delegate.keySet());
            keySet.addDeleteListener(new SetElementRemovedListener<K>() {
                @Override
                public void removed(ObservableSet<K> set, K element) {
                    remove(element);
                }
            });
            // the keySet doesn't support adding elements, just removing them
            addInsertListener(new MapElementInsertedListener<K, V>() {
                @Override
                public void inserted(ObservableMap<K, V> map, K key, V element) {
                    keySet.add(key);
                }
            });
            addDeleteListener(new MapElementRemovedListener<K, V>() {
                @Override
                public void removed(ObservableMap<K, V> map, K key, V element) {
                    keySet.remove(key);
                }
            });
        }
        return keySet;
    }

    private ObservableSet<Entry<K, V>> entrySet;

    @Override
    public Set<Entry<K, V>> entrySet() {
        if (entrySet == null) {
            entrySet = new ObservableSet<Entry<K, V>>(delegate.entrySet());
            entrySet.addDeleteListener(new SetElementRemovedListener<Entry<K, V>>() {
                @Override
                public void removed(ObservableSet<Entry<K, V>> set, Entry<K, V> element) {
                    remove(element.getKey());
                }
            });
            // the entrySet doesn't support adding elements either
            addInsertListener(new MapElementInsertedListener<K, V>() {
                @Override
                public void inserted(ObservableMap<K, V> map, K key, V element) {
                    entrySet.add(Maps.immutableEntry(key, element));
                }
            });
            addDeleteListener(new MapElementRemovedListener<K, V>() {
                @Override
                public void removed(ObservableMap<K, V> map, K key, V element) {
                    entrySet.remove(Maps.immutableEntry(key, element));
                }
            });
        }
        return entrySet;
    }
}