/******************************************************************************* * Copyright (c) 2012 Google, 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: * Google, Inc. - initial API and implementation *******************************************************************************/ package com.windowtester.runtime.gef.internal.commandstack; import java.util.ArrayList; import java.util.List; import org.eclipse.gef.commands.CommandStack; import org.eclipse.gef.commands.CommandStackEvent; import org.eclipse.gef.commands.CommandStackEventListener; import com.windowtester.runtime.IUIContext; import com.windowtester.runtime.WidgetSearchException; /** * Tracks events pushed on and popped off the command stack. */ public class CommandStackTransaction { /** * Timeout (in milliseconds) for waits on transaction completion. * <p> * Note: this is only public for testing purposes. */ public static final long TRANSACTION_TIMEOUT = 5000; //TODO: should this be longer? /** * Listener on the command stack. Since this object will be accessed from * two threads, it is appropriately synchronized. Accesses are as follows: * <ul> * <li> App thread: stack change notifications (modifying command list).</li> * <li> Test thread: command list querying (for size).</li> * </ul> * * */ private class StackListener implements CommandStackEventListener { //this could probably be a stack... List<Object> commands = new ArrayList<Object>(); public synchronized void stackChanged(CommandStackEvent event) { if (CommandStackEventType.forEvent(event).isPre()) addEvent(event); else { //TODO: consider testing/asserting that the remove succeeded (but there may be some leftover from before we start) removeEvent(event); } } private boolean removeEvent(CommandStackEvent event) { //System.out.println("removing command: " + event); return commands.remove(event.getSource()); } private boolean addEvent(CommandStackEvent event) { //System.out.println("adding command: " + event); return commands.add(event.getSource()); } public synchronized boolean containsUnfinishedCommmands() { return commands.size() > 0; } } /** * A special transaction for the non-existent edit domain case. * * NOTE: public for testing. * */ public final static CommandStackTransaction UNCHECKED_TRANSACTION = new CommandStackTransaction() { public boolean isComplete() { return true; } public CommandStackTransaction start() { //no-op return this; } /* (non-Javadoc) * @see com.windowtester.runtime.gef.internal.commandstack.CommandStackTransaction#stop() */ public void stop() { //no-op } public Object runInUI(UIRunnable runnable, IUIContext arg1) throws WidgetSearchException { //just run without waiting for transaction completion return runnable.runWithResult(); } }; private final StackListener stackListener = new StackListener(); private CommandStack stack; private boolean started; /** * Create a transaction for the stack associated with the current active editor. * Note: the stack may be null. */ public static CommandStackTransaction forActiveEditor() { return new CommandStackTransaction().forStack(CommandStackFinder.findStackForActiveEditor()); } /** * Create a transaction for the given stack. Note: the stack may be null. */ public CommandStackTransaction forStack(CommandStack stack) { if (stack == null) return UNCHECKED_TRANSACTION; this.stack = stack; return this; } public CommandStackTransaction start() { started = true; stack.addCommandStackEventListener(stackListener); return this; } public void stop() { stack.removeCommandStackEventListener(stackListener); } public boolean isComplete() { boolean result = !stackListener.containsUnfinishedCommmands(); // if (!result) // System.out.println("commandstack is not empty"); return result; } public boolean isStarted() { return started; } /** * Run this runnable as a transaction. */ public Object runInUI(UIRunnable runnable, IUIContext ui) throws WidgetSearchException { start(); try { Object result = runnable.runWithResult(); //TODO: do we need an extra waitForIdle here to make sure the commands get on the stack? ui.wait(TransactionCompleteCondition.forTransaction(this), TRANSACTION_TIMEOUT); return result; } finally { stop(); } } }