/*******************************************************************************
* 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.mvc.fx.handlers;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.gef.common.adapt.IAdaptable;
import org.eclipse.gef.mvc.fx.domain.IDomain;
import org.eclipse.gef.mvc.fx.gestures.AbstractGesture;
import org.eclipse.gef.mvc.fx.operations.ITransactionalOperation;
import org.eclipse.gef.mvc.fx.parts.IVisualPart;
import org.eclipse.gef.mvc.fx.parts.PartUtils;
import org.eclipse.gef.mvc.fx.policies.IPolicy;
import org.eclipse.gef.mvc.fx.viewer.IViewer;
import javafx.event.EventTarget;
import javafx.scene.Node;
/**
* Abstract base implementation of {@link IHandler} that handles an interaction.
*
* @author anyssen
*
*/
public abstract class AbstractHandler extends
IAdaptable.Bound.Impl<IVisualPart<? extends Node>> implements IHandler {
private static Map<IPolicy, StackTraceElement[]> started = new HashMap<>();
// SNIP debug utilities
private static Map<IPolicy, StackTraceElement[]> finished = new HashMap<>();
private static boolean isDebug = false; // true;
private static boolean canFinish(IPolicy policy) {
if (!started.containsKey(policy)) {
System.out.println(
"[ERROR] Trying to finish not-yet-started transaction policy "
+ policy + " from:");
printStackTrace(getRelevantStackTrace());
if (finished.containsKey(policy)) {
System.out.println(
"[INFO] The policy was previously finished at:");
printStackTrace(finished.get(policy));
}
return false;
} else {
started.remove(policy);
finished.put(policy, getRelevantStackTrace());
return true;
}
}
private static boolean canStart(IPolicy policy) {
if (started.containsKey(policy)) {
System.out.println(
"[ERROR] Trying to start already-started transaction policy "
+ policy + " from:");
printStackTrace(getRelevantStackTrace());
System.out.println("[INFO] The policy was previously started at:");
printStackTrace(started.get(policy));
return false;
} else {
started.put(policy, getRelevantStackTrace());
return true;
}
}
private static StackTraceElement[] getRelevantStackTrace() {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
// keep method calls until we find a Tool
int i = 2; // start at 2 to dismiss local methods
for (; i < trace.length; i++) {
if (AbstractGesture.class.isAssignableFrom(trace[i].getClass())) {
break;
}
}
return Arrays.copyOfRange(trace, 2, i);
}
private static void printStackTrace(StackTraceElement[] trace) {
for (int i = 0; i < trace.length; i++) {
System.out.println("*) " + trace[i]);
}
}
private final Map<IVisualPart<? extends Node>, Boolean> initialRefreshVisual = new HashMap<>();
// SNAP debug utilities
// If using e.g. a hover handle, it may be that this policy looses its
// viewer link between init() and commit(). In order to being able to safely
// commit(), we need to keep track of the domains.
private Map<IPolicy, IDomain> domains = new HashMap<>();
// TODO: add lifecycle of start, end, and abort interaction -> disable
// visuals, etc.
/**
* If the given {@link IPolicy} is not <code>null</code>, executes its
* commit operation the {@link IDomain}.
*
* @param policy
* The {@link IPolicy} to commit.
*/
protected void commit(IPolicy policy) {
if (policy != null) {
IDomain domain = domains.remove(policy);
if (isDebug) {
if (!canFinish(policy)) {
return;
}
}
ITransactionalOperation o = policy.commit();
if (o != null && !o.isNoOp()) {
try {
domain.execute(o, new NullProgressMonitor());
} catch (ExecutionException e) {
throw new RuntimeException(
"An exception occured when committing policy "
+ policy + ".",
e);
}
}
}
}
/**
* If the given {@link ITransactionalOperation} is not <code>null</code>,
* executes it on the {@link IDomain} of the {@link #getHost() host's}
* {@link IViewer}.
*
* @param operation
* The {@link ITransactionalOperation} to execute on the domain.
*/
protected void execute(ITransactionalOperation operation) {
if (operation != null && !operation.isNoOp()) {
try {
getHost().getViewer().getDomain().execute(operation,
new NullProgressMonitor());
} catch (ExecutionException e) {
throw new RuntimeException(
"An exception occured when executing operation "
+ operation + ".",
e);
}
}
}
/**
* Returns the {@link #getAdaptable() adaptable} of this
* {@link AbstractHandler}.
*
* @return The {@link #getAdaptable() adaptable} of this
* {@link AbstractHandler}.
*/
@Override
public IVisualPart<? extends Node> getHost() {
return getAdaptable();
}
/**
* If the given {@link IPolicy} is not <code>null</code>, initializes it.
*
* @param policy
* The {@link IPolicy} to commit.
*/
protected void init(IPolicy policy) {
if (policy != null) {
if (isDebug) {
if (!canStart(policy)) {
return;
}
}
domains.put(policy, getHost().getRoot().getViewer().getDomain());
policy.init();
}
}
/**
* Returns <code>true</code> if the given {@link EventTarget} is registered
* in the visual-part-map. Otherwise returns <code>false</code>.
*
* @param eventTarget
* The {@link EventTarget} that is tested.
* @return <code>true</code> if the given {@link EventTarget} is registered
* in the visual-part-map, otherwise <code>false</code>.
*/
protected boolean isRegistered(EventTarget eventTarget) {
IVisualPart<? extends Node> host = getHost();
if (host.getRoot() == null || host.getRoot().getViewer() == null) {
// host is not in visual-part-hierarchy or not in viewer
return false;
}
IViewer viewer = host.getRoot().getViewer();
if (eventTarget instanceof Node) {
return PartUtils.retrieveVisualPart(viewer,
(Node) eventTarget) != viewer.getRootPart();
}
// eventTarget is a Scene
return false;
}
/**
* Returns <code>true</code> if the given {@link EventTarget} is registered
* in the visual-part-map for the {@link #getHost() host} of this
* {@link AbstractHandler}. Otherwise returns <code>false</code>.
*
* @param eventTarget
* The {@link EventTarget} that is tested.
* @return <code>true</code> if the given {@link EventTarget} is registered
* in the visual-part-map for the host of this policy, otherwise
* <code>false</code>.
*/
protected boolean isRegisteredForHost(EventTarget eventTarget) {
IVisualPart<? extends Node> host = getHost();
if (host.getRoot() == null || host.getRoot().getViewer() == null) {
// host is not in visual-part-hierarchy or not in viewer
return false;
}
IViewer viewer = host.getRoot().getViewer();
if (eventTarget instanceof Node) {
return PartUtils.retrieveVisualPart(viewer,
(Node) eventTarget) == host;
}
// eventTarget is a Scene
return false;
}
/**
* Restores that the given {@link IVisualPart} refreshes its visual if this
* was the case prior to disabling the refresh of visuals.
*
* @param part
* The {@link IVisualPart} for which refreshing of visuals is
* restored.
*/
protected void restoreRefreshVisuals(IVisualPart<? extends Node> part) {
part.setRefreshVisual(initialRefreshVisual.remove(part));
}
/**
* If the given {@link IPolicy} is not <code>null</code>, rolls it back.
*
* @param policy
* The {@link IPolicy} to commit.
*/
protected void rollback(IPolicy policy) {
if (policy != null) {
domains.remove(policy);
if (isDebug) {
if (!canFinish(policy)) {
return;
}
}
policy.rollback();
}
}
/**
* Disable that the given {@link IVisualPart} refreshes its visual, if this
* was not already the case (see
* {@link IVisualPart#setRefreshVisual(boolean)}). Stores the state (whether
* the part was still refreshing its visual or not) so it can be restored
* later (see {@link #restoreRefreshVisuals(IVisualPart)}).
*
* @param part
* The {@link IVisualPart} whose visual refresh is to be
* disabled.
*/
protected void storeAndDisableRefreshVisuals(
IVisualPart<? extends Node> part) {
initialRefreshVisual.put(part, part.isRefreshVisual());
part.setRefreshVisual(false);
}
}