/******************************************************************************* * Copyright (c) 2015 Pivotal, Inc. * 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: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.dash.livexp; import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression; import org.springsource.ide.eclipse.commons.livexp.core.ValueListener; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import org.springsource.ide.eclipse.commons.livexp.core.ObservableSet; /** * An observable set that contains the results of applying a mapping function onto * the values of an ObservableSet of LiveExps. */ public abstract class MappedValuesSet<T, R> extends ObservableSet<R> { private final ObservableSet<LiveExpression<T>> target; private ImmutableSet<LiveExpression<T>> listenersAttached = ImmutableSet.of(); private ValueListener<T> valueListener = new ValueListener<T>() { @Override public void gotValue(LiveExpression<T> exp, T value) { MappedValuesSet.this.refresh(); } }; private ValueListener<ImmutableSet<LiveExpression<T>>> setListener = new ValueListener<ImmutableSet<LiveExpression<T>>>() { public void gotValue(LiveExpression<ImmutableSet<LiveExpression<T>>> exp, ImmutableSet<LiveExpression<T>> value) { refreshValueListeners(); } }; public MappedValuesSet(ObservableSet<LiveExpression<T>> target) { this.target = target; this.target.addListener(setListener); } /** * Override to define the function to apply to each value before adding to the result set. */ protected abstract R applyFun(T arg); /** * Called when the target set changes. This should add and remove valuelistener to * the LiveExps in the target to ensure we listen each expression. */ private synchronized void refreshValueListeners() { ImmutableSet<LiveExpression<T>> current = target.getValues(); SetView<LiveExpression<T>> removed = Sets.difference(listenersAttached, current); SetView<LiveExpression<T>> added = Sets.difference(current, listenersAttached); for (LiveExpression<T> exp : removed) { exp.removeListener(valueListener); } for (LiveExpression<T> exp : added) { exp.addListener(valueListener); } listenersAttached = current; } @Override protected ImmutableSet<R> compute() { ImmutableSet.Builder<R> builder = immutableSetBuilder(); for (LiveExpression<T> exp : target.getValues()) { R val = applyFun(exp.getValue()); if (val!=null) { builder.add(val); } } return builder.build(); } /** * By overriding this method, subclass can determine the kind of ImmutableSet that will * be constructed to hold the mapped values. For example, a subclass may want to use ImmutableSortedSet instead * of a plain ImmutableSet which iterates elements in unpredictable order. */ protected ImmutableSet.Builder<R> immutableSetBuilder() { //TODO: pull up to superclass and use consistently anywhere a ObservableSet needs to // construct a immutable set? return ImmutableSet.builder(); } @Override public void dispose() { synchronized (this) { if (listenersAttached!=null) { for (LiveExpression<T> exp : listenersAttached) { exp.removeListener(valueListener); } listenersAttached = null; } if (setListener!=null) { target.removeListener(setListener); setListener = null; } } super.dispose(); } }