/****************************************************************************** * 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.Collection; import java.util.HashSet; import java.util.List; import org.eclipse.gef.common.collections.MultisetListenerHelper.ElementarySubChange; import com.google.common.collect.ForwardingMultiset; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import javafx.beans.InvalidationListener; /** * An {@link ObservableMultisetWrapper} is an {@link ObservableMultiset} that * wraps an underlying {@link Multiset}. * * @param <E> * The element type of the {@link Multiset}. * * @author anyssen */ class ObservableMultisetWrapper<E> extends ForwardingMultiset<E> implements ObservableMultiset<E> { private MultisetListenerHelper<E> helper = new MultisetListenerHelper<>( this); private Multiset<E> backingMultiset; /** * Creates a new {@link ObservableMultiset} wrapping the given * {@link Multiset}. * * @param setMultimap * The {@link Multiset} to wrap into the newly created * {@link ObservableMultisetWrapper}. */ public ObservableMultisetWrapper(Multiset<E> setMultimap) { this.backingMultiset = setMultimap; } @Override public boolean add(E element) { Multiset<E> previousContents = delegateCopy(); boolean changed = super.add(element); if (changed) { helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>(element, 0, 1))); } return changed; } @Override public int add(E element, int occurrences) { Multiset<E> previousContents = delegateCopy(); int countBefore = super.add(element, occurrences); if (count(element) > countBefore) { // only fire change if occurrences have really been added. helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>(element, 0, count(element) - countBefore))); } return countBefore; } @Override public boolean addAll(Collection<? extends E> collection) { Multiset<E> previousContents = delegateCopy(); boolean changed = super.addAll(collection); if (changed) { List<ElementarySubChange<E>> elementaryChanges = new ArrayList<>(); // collection may contain element multiple times; as we only want to // notify once per element, we have to iterate over the set of // unique elements for (E e : new HashSet<>(collection)) { if (previousContents.contains(e)) { // already contained if (count(e) > previousContents.count(e)) { elementaryChanges.add(new ElementarySubChange<>(e, 0, count(e) - previousContents.count(e))); } } else { // newly added elementaryChanges .add(new ElementarySubChange<>(e, 0, count(e))); } } helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, elementaryChanges)); } return changed; } @Override public void addListener(InvalidationListener listener) { helper.addListener(listener); } @Override public void addListener(MultisetChangeListener<? super E> listener) { helper.addListener(listener); } @Override public void clear() { Multiset<E> previousContents = delegateCopy(); super.clear(); if (!previousContents.isEmpty()) { List<ElementarySubChange<E>> elementaryChanges = new ArrayList<>(); for (E e : previousContents.elementSet()) { elementaryChanges.add(new ElementarySubChange<>(e, previousContents.count(e), 0)); } helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, elementaryChanges)); } } @Override protected Multiset<E> delegate() { return backingMultiset; } /** * Returns a copy of the delegate {@link Multiset}, which is used for change * notifications. * * @return A copy of the backing {@link Multiset}. */ protected Multiset<E> delegateCopy() { return HashMultiset.create(backingMultiset); } @SuppressWarnings("unchecked") @Override public boolean remove(Object object) { Multiset<E> previousContents = delegateCopy(); boolean changed = super.remove(object); if (changed) { // if remove was successful, the cast to E should be safe. helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>((E) object, 1, 0))); } return changed; } @SuppressWarnings("unchecked") @Override public int remove(Object element, int occurrences) { Multiset<E> previousContents = delegateCopy(); int countBefore = super.remove(element, occurrences); if (countBefore > count(element)) { // if the element has been removed, the cast to E should be safe // here; we may actually remove fewer then the specified // occurrences, thus we have to compute how many have actually be // removed. helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>((E) element, countBefore - count(element), 0))); } return countBefore; } @SuppressWarnings("unchecked") @Override public boolean removeAll(Collection<?> collection) { Multiset<E> previousContents = delegateCopy(); boolean changed = super.removeAll(collection); if (changed) { List<ElementarySubChange<E>> elementaryChanges = new ArrayList<>(); // collection may contain element multiple times; as we only want to // notify once per element, we have to iterate over the set of // unique elements for (Object e : new HashSet<>(collection)) { if (previousContents.contains(e)) { // if the element was contained, its safe to cast to E in // the following if (previousContents.count(e) > count(e)) { elementaryChanges.add(new ElementarySubChange<>((E) e, previousContents.count(e), 0)); } } } helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, elementaryChanges)); } return changed; } @Override public void removeListener(InvalidationListener listener) { helper.removeListener(listener); } @Override public void removeListener(MultisetChangeListener<? super E> listener) { helper.removeListener(listener); } @Override public boolean replaceAll(Multiset<? extends E> multiset) { Multiset<E> previousContents = delegateCopy(); super.clear(); super.addAll(multiset); Multiset<E> removedElements = Multisets.difference(previousContents, multiset); Multiset<? extends E> addedElements = Multisets.difference(multiset, previousContents); if (!addedElements.isEmpty() || !removedElements.isEmpty()) { List<ElementarySubChange<E>> elementaryChanges = new ArrayList<>(); // removed / decreased elements for (E e : removedElements.elementSet()) { elementaryChanges.add(new ElementarySubChange<>(e, removedElements.count(e), 0)); } // added / increased entries for (E e : addedElements.elementSet()) { elementaryChanges.add(new ElementarySubChange<>(e, 0, addedElements.count(e))); } helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, elementaryChanges)); return true; } return false; } @Override public boolean retainAll(Collection<?> collection) { Multiset<E> previousContents = delegateCopy(); boolean changed = super.retainAll(collection); if (changed) { List<ElementarySubChange<E>> elementaryChanges = new ArrayList<>(); // collection may contain element multiple times; as we only want to // notify once per element, we have to iterate over the set of // unique elements for (E e : previousContents.elementSet()) { if (!collection.contains(e)) { elementaryChanges.add(new ElementarySubChange<>(e, previousContents.count(e), 0)); } } helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, elementaryChanges)); } return changed; } @Override public int setCount(E element, int count) { Multiset<E> previousContents = delegateCopy(); int countBefore = super.setCount(element, count); if (count(element) > countBefore) { helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>(element, 0, count(element) - countBefore))); } else if (count(element) < countBefore) { helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>(element, countBefore - count(element), 0))); } return countBefore; } @Override public boolean setCount(E element, int oldCount, int newCount) { Multiset<E> previousContents = delegateCopy(); boolean changed = super.setCount(element, oldCount, newCount); // if changed it means that the oldCound was matched and that now we // have the new count if (changed) { if (newCount > oldCount) { helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>( element, 0, newCount - oldCount))); } else if (oldCount > newCount) { helper.fireValueChangedEvent( new MultisetListenerHelper.AtomicChange<>(this, previousContents, new ElementarySubChange<>( element, oldCount - newCount, 0))); } } return changed; } }