/****************************************************************************** * Copyright (c) 2015, 2016 itemis AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef.common.collections; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.gef.common.collections.SetMultimapListenerHelper.ElementarySubChange; import com.google.common.collect.ForwardingSetMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import javafx.beans.InvalidationListener; /** * An {@link ObservableSetMultimapWrapper} is an {@link ObservableSetMultimap} * that wraps an underlying {@link SetMultimap}. * * @param <K> * The key type of the {@link SetMultimap}. * @param <V> * The value type of the {@link SetMultimap}. * * @author anyssen */ class ObservableSetMultimapWrapper<K, V> extends ForwardingSetMultimap<K, V> implements ObservableSetMultimap<K, V> { private SetMultimap<K, V> backingSetMultiMap; private SetMultimapListenerHelper<K, V> helper = new SetMultimapListenerHelper<>( this); /** * Creates a new {@link ObservableSetMultimap} wrapping the given * {@link SetMultimap}. * * @param setMultimap * The {@link SetMultimap} to wrap into the newly created * {@link ObservableSetMultimapWrapper}. */ public ObservableSetMultimapWrapper(SetMultimap<K, V> setMultimap) { this.backingSetMultiMap = setMultimap; } @Override public void addListener(InvalidationListener listener) { helper.addListener(listener); } @Override public void addListener( SetMultimapChangeListener<? super K, ? super V> listener) { helper.addListener(listener); } @Override public void clear() { SetMultimap<K, V> previousContents = delegateCopy(); super.clear(); if (!previousContents.isEmpty()) { List<ElementarySubChange<K, V>> elementaryChanges = new ArrayList<>(); for (K key : previousContents.keySet()) { elementaryChanges.add(new ElementarySubChange<>(key, previousContents.get(key), Collections.<V> emptySet())); } helper.fireValueChangedEvent( new SetMultimapListenerHelper.AtomicChange<>(this, previousContents, elementaryChanges)); } } @Override protected SetMultimap<K, V> delegate() { return backingSetMultiMap; } /** * Returns a copy of the delegate {@link SetMultimap}, which is used for * change notifications. * * @return A copy of the backing {@link SetMultimap}. */ protected SetMultimap<K, V> delegateCopy() { return HashMultimap.create(backingSetMultiMap); } @Override public boolean put(K key, V value) { SetMultimap<K, V> previousContents = delegateCopy(); if (super.put(key, value)) { helper.fireValueChangedEvent( new SetMultimapListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>(key, Collections.<V> emptySet(), Collections.singleton(value)))); return true; } return false; } @Override public boolean putAll(K key, Iterable<? extends V> values) { SetMultimap<K, V> previousContents = delegateCopy(); if (super.putAll(key, values)) { Set<V> removedValues = new HashSet<>(previousContents.get(key)); removedValues.removeAll(get(key)); Set<V> addedValues = new HashSet<>(get(key)); addedValues.removeAll(previousContents.get(key)); helper.fireValueChangedEvent( new SetMultimapListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>(key, removedValues, addedValues))); return true; } return false; } @Override public boolean putAll(Multimap<? extends K, ? extends V> multimap) { SetMultimap<K, V> previousContents = delegateCopy(); if (super.putAll(multimap)) { List<ElementarySubChange<K, V>> elementaryChanges = new ArrayList<>(); for (K key : multimap.keySet()) { // this causes multiple change notifications, as an elementary // change is related to a single key only Set<V> removedValues = new HashSet<>(previousContents.get(key)); removedValues.removeAll(get(key)); Set<V> addedValues = new HashSet<>(get(key)); addedValues.removeAll(previousContents.get(key)); elementaryChanges.add(new ElementarySubChange<>(key, removedValues, addedValues)); } helper.fireValueChangedEvent( new SetMultimapListenerHelper.AtomicChange<>(this, previousContents, elementaryChanges)); return true; } return false; } @SuppressWarnings("unchecked") @Override public boolean remove(Object key, Object value) { SetMultimap<K, V> previousContents = delegateCopy(); if (super.remove(key, value)) { // XXX: If the key or value are not of matching type, the super call // should not have an effect; as such, the cast should be safe here. helper.fireValueChangedEvent( new SetMultimapListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>((K) key, Collections.singleton((V) value), Collections.<V> emptySet()))); return true; } return false; } @SuppressWarnings("unchecked") @Override public Set<V> removeAll(Object key) { SetMultimap<K, V> previousContents = delegateCopy(); Set<V> oldValues = super.removeAll(key); if (!oldValues.isEmpty()) { // XXX: If values could be removed, the key should have the // appropriate type. As such the cast here should be safe. helper.fireValueChangedEvent( new SetMultimapListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>((K) key, oldValues, Collections.<V> emptySet()))); } return oldValues; } @Override public void removeListener(InvalidationListener listener) { helper.removeListener(listener); } @Override public void removeListener( SetMultimapChangeListener<? super K, ? super V> listener) { helper.removeListener(listener); } @Override public boolean replaceAll( SetMultimap<? extends K, ? extends V> setMultimap) { SetMultimap<K, V> previousContents = delegateCopy(); super.clear(); super.putAll(setMultimap); if (!previousContents.equals(setMultimap)) { List<ElementarySubChange<K, V>> elementaryChanges = new ArrayList<>(); for (K key : previousContents.keySet()) { // removed key if (!setMultimap.containsKey(key)) { elementaryChanges.add(new ElementarySubChange<>(key, new HashSet<>(previousContents.get(key)), Collections.<V> emptySet())); } else { // changed entry? Set<? extends V> addedValues = new HashSet<>(get(key)); addedValues.removeAll(previousContents.get(key)); Set<? extends V> removedValues = new HashSet<>( previousContents.get(key)); removedValues.removeAll(get(key)); if (!addedValues.isEmpty() || !removedValues.isEmpty()) { elementaryChanges.add(new ElementarySubChange<>(key, removedValues, addedValues)); } } } for (K key : keySet()) { // added key if (!previousContents.containsKey(key)) { elementaryChanges.add(new ElementarySubChange<>(key, Collections.<V> emptySet(), new HashSet<>(get(key)))); } } helper.fireValueChangedEvent( new SetMultimapListenerHelper.AtomicChange<>(this, previousContents, elementaryChanges)); return true; } return false; } @Override public Set<V> replaceValues(K key, Iterable<? extends V> values) { SetMultimap<K, V> previousContents = delegateCopy(); Set<V> replacedValues = super.replaceValues(key, values); if (!replacedValues.isEmpty()) { helper.fireValueChangedEvent( new SetMultimapListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>(key, replacedValues, Sets.newHashSet(values)))); } return replacedValues; } }