/******************************************************************************* * Copyright (c) 2016 vogella GmbH 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: * Simon Scholz <simon.scholz@vogella.com> - initial API and implementation ******************************************************************************/ package org.eclipse.core.databinding.observable.sideeffect; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import org.eclipse.core.databinding.observable.Realm; import org.eclipse.core.runtime.Assert; /** * Represents an {@link ISideEffect} that is composed of a bunch of component * {@link ISideEffect}s. It has the following properties: * * <ul> * <li>Disposing the composite will dispose all of the children.</li> * <li>If the composite is paused, all of the children will be paused as well. * </li> * </ul> * * Note that resuming a composite does not guarantee that all children will * resume. Children may also be paused externally, in which case the child must * be resumed both by the composite and by the external source(s) before it will * execute. * <p> * Children may belong to multiple composites. When this occurs, all of its * parent composites must be resumed in order for the child to execute and the * child will be disposed the first time any of its parents are disposed. * <p> * Children may be removed from a composite. When this occurs, the child may be * resumed immediately if the composite was paused and disposing the composite * will no longer have any effect on the removed child. * <p> * Disposing a child will automatically remove it from its parent composite(s). * <p> * The main use of this class is to manage a group of side-effects that share * the same life-cycle. For example, all side-effects used to populate widgets * within a workbench part would likely be paused and resumed when the part is * made visible or invisible, and would all be disposed together when the part * is closed. * * @since 1.6 */ public final class CompositeSideEffect implements ISideEffect { private final List<ISideEffect> sideEffects; private int pauseDepth; private boolean isDisposed; private final Realm realm; /** * List of dispose listeners. Null if empty. */ private List<Consumer<ISideEffect>> disposeListeners; private Consumer<ISideEffect> removalConsumer = this::remove; /** * Default constructor of an CompositeSideEffect. */ public CompositeSideEffect() { realm = Realm.getDefault(); sideEffects = new ArrayList<>(); } private void checkRealm() { Assert.isTrue(realm.isCurrent(), "This operation must be run within the observable's realm"); //$NON-NLS-1$ } @Override public void dispose() { checkRealm(); if (isDisposed) { return; } sideEffects.forEach(s -> s.removeDisposeListener(removalConsumer)); sideEffects.forEach(s -> s.dispose()); sideEffects.clear(); isDisposed = true; if (disposeListeners != null) { List<Consumer<ISideEffect>> listeners = disposeListeners; disposeListeners = null; listeners.forEach(dc -> dc.accept(CompositeSideEffect.this)); } } @Override public boolean isDisposed() { checkRealm(); return this.isDisposed; } @Override public void addDisposeListener(Consumer<ISideEffect> disposalConsumer) { checkRealm(); if (isDisposed()) { return; } if (this.disposeListeners == null) { this.disposeListeners = new ArrayList<>(); } this.disposeListeners.add(disposalConsumer); } @Override public void removeDisposeListener(Consumer<ISideEffect> disposalConsumer) { checkRealm(); if (this.disposeListeners == null) { return; } this.disposeListeners.remove(disposalConsumer); } @Override public void pause() { checkRealm(); pauseDepth++; if (pauseDepth == 1) { sideEffects.forEach(s -> s.pause()); } } @Override public void resume() { checkRealm(); pauseDepth--; if (pauseDepth < 0) { throw new IllegalStateException( "The resume() method was called more times than pause()."); //$NON-NLS-1$ } else if (pauseDepth == 0) { sideEffects.forEach(s -> s.resume()); } } @Override public void resumeAndRunIfDirty() { checkRealm(); pauseDepth--; if (pauseDepth == 0) { sideEffects.forEach(s -> s.resumeAndRunIfDirty()); } } @Override public void runIfDirty() { checkRealm(); if (pauseDepth <= 0) { sideEffects.forEach(s -> s.runIfDirty()); } } /** * Adds the given {@link ISideEffect} instance from the composite. * * @param sideEffect * {@link ISideEffect} */ public void add(ISideEffect sideEffect) { checkRealm(); if (!sideEffect.isDisposed()) { sideEffects.add(sideEffect); if (pauseDepth > 0) { sideEffect.pause(); } sideEffect.addDisposeListener(removalConsumer); } } /** * Removes the given {@link ISideEffect} instance from the composite. This * has no effect if the given side-effect is not part of the composite. * * @param sideEffect * {@link ISideEffect} */ public void remove(ISideEffect sideEffect) { checkRealm(); sideEffects.remove(sideEffect); if (!sideEffect.isDisposed()) { if (pauseDepth > 0) { sideEffect.resume(); } sideEffect.removeDisposeListener(removalConsumer); } } }