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

eu.hansolo.toolbox.observables.ObservableMap Maven / Gradle / Ivy

/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2022 Gerrit Grunwald.
 *
 * 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
 *
 *     https://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 eu.hansolo.toolbox.observables;

import eu.hansolo.toolbox.evt.EvtObserver;
import eu.hansolo.toolbox.evt.EvtType;
import eu.hansolo.toolbox.evt.type.MapChangeEvt;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentHashMap.KeySetView;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.DoubleBinaryOperator;
import java.util.function.Function;
import java.util.function.IntBinaryOperator;
import java.util.function.LongBinaryOperator;
import java.util.function.ToDoubleBiFunction;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntBiFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongBiFunction;
import java.util.function.ToLongFunction;


public class ObservableMap implements Map, Cloneable {
    private static final int                                                DEFAULT_CAPACITY    = 16;
    private static final float                                              DEFAULT_LOAD_FACTOR = 0.75f;
    private        final ConcurrentHashMap                             map;
    private              Map>>> observers;


    // ******************** Constructors **************************************
    public ObservableMap() {
        this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    public ObservableMap(final int capacity) {
        this(capacity, DEFAULT_LOAD_FACTOR);
    }
    public ObservableMap(final int capacity, final float loadFactor) {
        this.map       = new ConcurrentHashMap<>(capacity, loadFactor);
        this.observers = new ConcurrentHashMap<>();
    }
    public ObservableMap(final int capacity, final float loadFactor, final int concurrencyLevel) {
        this.map       = new ConcurrentHashMap<>(capacity, loadFactor, concurrencyLevel);
        this.observers = new ConcurrentHashMap<>();
    }
    public ObservableMap(final Map map) {
        this.map       = new ConcurrentHashMap<>(map);
        this.observers = new ConcurrentHashMap<>();
    }


    // ******************** Methods *******************************************
    public V get(final Object key) { return this.map.get(key); }

    public V put(K key, V value) {
        final V result;
        if (this.map.containsKey(key)) {
            result = this.map.put(key, value);
            fireMapChangeEvt(new MapChangeEvt<>(ObservableMap.this, MapChangeEvt.MODIFIED, List.of(), List.of(Map.entry(key, value)), List.of()));
            return result;
        } else {
            result = this.map.put(key, value);
            fireMapChangeEvt(new MapChangeEvt<>(ObservableMap.this, MapChangeEvt.ADDED, List.of(Map.entry(key, value)), List.of(), List.of()));
            return result;
        }
    }

    public void putAll(final Map map) {
        //this.map.putAll(map);
        map.entrySet().forEach(entry -> put(entry.getKey(), entry.getValue()));
    }

    public V remove(final Object key) {
        final V result;
        if (map.containsKey(key)) {
            final Entry removedEntry = Map.entry((K) key, map.get(key));
            result = map.remove(key);
            fireMapChangeEvt(new MapChangeEvt<>(ObservableMap.this, MapChangeEvt.REMOVED, List.of(), List.of(), List.of(removedEntry)));
        } else {
            result = map.remove(key);
        }
        return result;
    }

    public void clear() {
        List> removedEntries = new ArrayList<>(entrySet());
        map.clear();
        fireMapChangeEvt(new MapChangeEvt(ObservableMap.this, MapChangeEvt.REMOVED, List.of(), List.of(), removedEntries));
    }


    public boolean containsKey(final Object key) { return this.map.containsKey(key); }

    public boolean containsValue(final Object value) { return map.containsValue(value); }

    public boolean contains(final Object value) { return containsValue(value); }


    public KeySetView keySet() { return map.keySet(); }

    public KeySetView keySet(final V mappedValue) { return map.keySet(mappedValue); }

    public Collection values() { return map.values(); }

    public Set> entrySet() { return map.entrySet(); }

    public Enumeration keys() { return map.keys(); }

    public Enumeration elements() { return map.elements(); }

    public long mappingCount() { return map.mappingCount(); }


    public int size() { return this.map.size(); }

    public boolean isEmpty() { return this.map.isEmpty(); }


    @Override public V getOrDefault(final Object key, final V defaultValue) { return getOrDefault(key, defaultValue); }

    @Override public V putIfAbsent(final K key, final V value) {
        final V result = map.putIfAbsent(key, value);
        if (null == result) {
            fireMapChangeEvt(new MapChangeEvt<>(ObservableMap.this, MapChangeEvt.ADDED, List.of(Map.entry(key, value)), List.of(), List.of()));
        } else {
            fireMapChangeEvt(new MapChangeEvt<>(ObservableMap.this, MapChangeEvt.MODIFIED, List.of(), List.of(Map.entry(key, result)), List.of()));
        }
        return result;
    }

    @Override public boolean remove(final Object key, final Object value) {
        final boolean result = map.remove(key, value);
        if (result) {
            fireMapChangeEvt(new MapChangeEvt(ObservableMap.this, MapChangeEvt.REMOVED, List.of(), List.of(), List.of((Entry) Map.entry(key, value))));
        }
        return result;
    }

    @Override public boolean replace(final K key, final V oldValue, final V newValue) {
        final boolean result = map.replace(key, oldValue, newValue);
        if (result) {
            fireMapChangeEvt(new MapChangeEvt<>(ObservableMap.this, MapChangeEvt.MODIFIED, List.of(), List.of(Map.entry(key, newValue)), List.of()));
        }
        return result;
    }

    @Override public V replace(final K key, final V value) {
        final V result = map.replace(key, value);
        if (map.containsKey(key)) {
            fireMapChangeEvt(new MapChangeEvt<>(ObservableMap.this, MapChangeEvt.MODIFIED, List.of(), List.of(Map.entry(key, value)), List.of()));
        }
        return result;
    }

    @Override public V computeIfAbsent(final K key, final Function mappingFunction) { return map.computeIfAbsent(key, mappingFunction); }

    @Override public V computeIfPresent(final K key, final BiFunction remappingFunction) { return map.computeIfPresent(key, remappingFunction); }

    @Override public V compute(final K key, final BiFunction remappingFunction) { return map.compute(key, remappingFunction); }

    @Override public V merge(final K key, final V value, final BiFunction remappingFunction) { return map.merge(key, value, remappingFunction); }

    @Override public void forEach(final BiConsumer action) { map.forEach(action); }

    public void forEach(final long parallelismThreshold, BiConsumer action) { map.forEach(action); }

    public  void forEach(final long parallelismThreshold, final BiFunction transformer, final Consumer action) {
        map.forEach(parallelismThreshold, transformer, action);
    }

    public  U search(final long parallelismThreshold, final BiFunction searchFunction) {
        return map.search(parallelismThreshold, searchFunction);
    }

    public  U reduce(final long parallelismThreshold, final BiFunction transformer, final BiFunction reducer) {
        return map.reduce(parallelismThreshold, transformer, reducer);
    }

    public double reduceToDouble(final long parallelismThreshold, final ToDoubleBiFunction transformer, final double basis, final DoubleBinaryOperator reducer) {
        return map.reduceToDouble(parallelismThreshold, transformer, basis, reducer);
    }

    public long reduceToLong(final long parallelismThreshold, final ToLongBiFunction transformer, final long basis, final LongBinaryOperator reducer) {
        return map.reduceToLong(parallelismThreshold, transformer, basis, reducer);
    }

    public int reduceToInt(final long parallelismThreshold, final ToIntBiFunction transformer, final int basis, final IntBinaryOperator reducer) {
        return map.reduceToInt(parallelismThreshold, transformer, basis, reducer);
    }

    public void forEachKey(final long parallelismThreshold, final Consumer action) { map.forEachKey(parallelismThreshold, action); }

    public  void forEachKey(final long parallelismThreshold, final Function transformer, final Consumer action) { map.forEachKey(parallelismThreshold, transformer, action); }

    public  U searchKeys(final long parallelismThreshold, final Function searchFunction) { return map.searchKeys(parallelismThreshold, searchFunction); }

    public K reduceKeys(final long parallelismThreshold, final BiFunction reducer) { return map.reduceKeys(parallelismThreshold, reducer); }

    public  U reduceKeys(final long parallelismThreshold, final Function transformer, final BiFunction reducer) {
        return map.reduceKeys(parallelismThreshold, transformer, reducer);
    }

    public double reduceKeysToDouble(final long parallelismThreshold, final ToDoubleFunction transformer, final double basis, final DoubleBinaryOperator reducer) {
        return map.reduceKeysToDouble(parallelismThreshold, transformer, basis, reducer);
    }

    public long reduceKeysToLong(final long parallelismThreshold, final ToLongFunction transformer, final long basis, final LongBinaryOperator reducer) {
        return map.reduceKeysToLong(parallelismThreshold, transformer, basis, reducer);
    }

    public int reduceKeysToInt(final long parallelismThreshold, final ToIntFunction transformer, final int basis, final IntBinaryOperator reducer) {
        return map.reduceKeysToInt(parallelismThreshold, transformer, basis, reducer);
    }

    public void forEachValue(final long parallelismThreshold, final Consumer action) { map.forEachValue(parallelismThreshold, action); }

    public  void forEachValue(final long parallelismThreshold, final Function transformer, final Consumer action) {
        map.forEachValue(parallelismThreshold, transformer, action);
    }

    public  U searchValues(final long parallelismThreshold, final Function searchFunction) {
        return map.searchValues(parallelismThreshold, searchFunction);
    }

    public V reduceValues(final long parallelismThreshold, final BiFunction reducer) {
        return map.reduceValues(parallelismThreshold, reducer);
    }

    public  U reduceValues(final long parallelismThreshold, final Function transformer, final BiFunction reducer) {
        return map.reduceValues(parallelismThreshold, transformer, reducer);
    }

    public double reduceValuesToDouble(final long parallelismThreshold, final ToDoubleFunction transformer, final double basis, final DoubleBinaryOperator reducer) {
        return map.reduceValuesToDouble(parallelismThreshold, transformer, basis, reducer);
    }

    public long reduceValuesToLong(final long parallelismThreshold, final ToLongFunction transformer, final long basis, final LongBinaryOperator reducer) {
        return map.reduceValuesToLong(parallelismThreshold, transformer, basis, reducer);
    }

    public int reduceValuesToInt(final long parallelismThreshold, final ToIntFunction transformer, final int basis, final IntBinaryOperator reducer) {
        return map.reduceValuesToInt(parallelismThreshold, transformer, basis, reducer);
    }

    public void forEachEntry(final long parallelismThreshold, final Consumer> action) { map.forEachEntry(parallelismThreshold, action); }

    public  void forEachEntry(final long parallelismThreshold, final Function, ? extends U> transformer, final Consumer action) {
        map.forEachEntry(parallelismThreshold, transformer, action);
    }

    public  U searchEntries(final long parallelismThreshold, final Function, ? extends U> searchFunction) {
        return map.searchEntries(parallelismThreshold, searchFunction);
    }

    public Map.Entry reduceEntries(final long parallelismThreshold, final BiFunction, Map.Entry, ? extends Map.Entry> reducer) {
        return map.reduceEntries(parallelismThreshold, reducer);
    }

    public  U reduceEntries(final long parallelismThreshold, final Function, ? extends U> transformer, final BiFunction reducer) {
        return map.reduceEntries(parallelismThreshold, transformer, reducer);
    }

    public double reduceEntriesToDouble(final long parallelismThreshold, final ToDoubleFunction> transformer, final double basis, final DoubleBinaryOperator reducer) {
        return map.reduceEntriesToDouble(parallelismThreshold, transformer, basis, reducer);
    }

    public long reduceEntriesToLong(final long parallelismThreshold, final ToLongFunction> transformer, final long basis, final LongBinaryOperator reducer) {
        return map.reduceEntriesToLong(parallelismThreshold, transformer, basis, reducer);
    }

    public int reduceEntriesToInt(final long parallelismThreshold, final ToIntFunction> transformer, final int basis, final IntBinaryOperator reducer) {
        return map.reduceEntriesToInt(parallelismThreshold, transformer, basis, reducer);
    }

    @Override public void replaceAll(final BiFunction function) { map.replaceAll(function); }

    @Override public ObservableMap clone() {
        try {
            ObservableMap clone = (ObservableMap) super.clone();
            clone.putAll(map);
            return clone;
        } catch (Exception e) {
            throw new InternalError(e);
        }
    }

    @Override public boolean equals(final Object obj) { return map.equals(obj); }

    @Override public int hashCode() { return map.hashCode(); }

    @Override public String toString() { return map.toString(); }


    // ******************** Event Handling ************************************
    public void addMapChangeObserver(final EvtType type, final EvtObserver> observer) {
        if (!observers.containsKey(type)) { observers.put(type, new CopyOnWriteArrayList<>()); }
        if (observers.get(type).contains(observer)) { return; }
        observers.get(type).add(observer);
    }
    public void removeMapChangeObserver(final EvtType type, final EvtObserver> observer) {
        if (observers.containsKey(type)) {
            if (observers.get(type).contains(observer)) {
                observers.get(type).remove(observer);
            }
        }
    }
    public void removeAllMapChangeObservers() { observers.clear(); }

    public void fireMapChangeEvt(final MapChangeEvt evt) {
        final EvtType type = evt.getEvtType();
        observers.entrySet().stream().filter(entry -> entry.getKey().equals(MapChangeEvt.ANY)).forEach(entry -> entry.getValue().forEach(observer -> observer.handle(evt)));
        if (observers.containsKey(type) && !type.equals(MapChangeEvt.ANY)) {
            observers.get(type).forEach(observer -> observer.handle(evt));
        }
    }
}