/*******************************************************************************
* 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.codegen;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.windowtester.codegen.assembly.unit.MethodUnit;
import com.windowtester.codegen.eventstream.IEventStream;
import com.windowtester.codegen.generator.CodegenSettings;
import com.windowtester.codegen.generator.SetupBlockBuilder;
import com.windowtester.internal.debug.TraceHandler;
import com.windowtester.internal.debug.Tracer;
import com.windowtester.internal.runtime.IWidgetIdentifier;
import com.windowtester.recorder.event.ISemanticEvent;
import com.windowtester.recorder.event.IUISemanticEvent;
import com.windowtester.recorder.event.meta.RecorderAssertionHookAddedEvent;
import com.windowtester.recorder.event.user.SemanticComboSelectionEvent;
import com.windowtester.recorder.event.user.SemanticDragEvent;
import com.windowtester.recorder.event.user.SemanticDropEvent;
import com.windowtester.recorder.event.user.SemanticFocusEvent;
import com.windowtester.recorder.event.user.SemanticKeyDownEvent;
import com.windowtester.recorder.event.user.SemanticListSelectionEvent;
import com.windowtester.recorder.event.user.SemanticMenuSelectionEvent;
import com.windowtester.recorder.event.user.SemanticMoveEvent;
import com.windowtester.recorder.event.user.SemanticResizeEvent;
import com.windowtester.recorder.event.user.SemanticShellClosingEvent;
import com.windowtester.recorder.event.user.SemanticShellDisposedEvent;
import com.windowtester.recorder.event.user.SemanticShellShowingEvent;
import com.windowtester.recorder.event.user.SemanticTableSelectionEvent;
import com.windowtester.recorder.event.user.SemanticTextEntryEvent;
import com.windowtester.recorder.event.user.SemanticTreeItemSelectionEvent;
import com.windowtester.recorder.event.user.SemanticWidgetClosedEvent;
import com.windowtester.recorder.event.user.SemanticWidgetInspectionEvent;
import com.windowtester.recorder.event.user.SemanticWidgetSelectionEvent;
import com.windowtester.runtime.swt.internal.debug.LogHandler;
import com.windowtester.runtime.swt.internal.preferences.ICodeGenConstants;
/**
* An abstract base class that iterates over a stream of events and dispatches
* handling to abstract "handle*(..)" hook methods.
*/
public abstract class TestCaseGenerator implements ICodeGenerator {
/** The builder instance */
protected ITestCaseBuilder builder;
protected final SetupBlockBuilder setupBuilder;
/** Cached current widget info for use in ensuring proper focus in text entry handling */
protected IWidgetIdentifier currentWidgetInfo;
public static final String NEW_LINE = ICodeGenConstants.NEW_LINE;
/**
* Create an instance.
* @param builder - the test case builder
*/
public TestCaseGenerator(ITestCaseBuilder builder) {
this(builder, CodegenSettings.forPreferences());
}
public TestCaseGenerator(ITestCaseBuilder builder, CodegenSettings codegenSettings) {
this.builder = builder;
this.setupBuilder = new SetupBlockBuilder(builder, codegenSettings);
}
public ITestCaseBuilder getTestBuilder() {
return builder;
}
/* (non-Javadoc)
* @see com.windowtester.codegen.ICodeGenerator#generate(com.windowtester.codegen.eventstream.IEventStream)
*/
public String generate(IEventStream stream) {
getTestBuilder().prime();
handleSetup(stream);
while(stream.hasNext()) {
ISemanticEvent event = stream.nextEvent();
if (isHandledInSetup(event))
continue;
if (event instanceof SemanticWidgetInspectionEvent) {
handleInspection((SemanticWidgetInspectionEvent)event);
}
if (event instanceof IUISemanticEvent) {
IUISemanticEvent uiEvent = (IUISemanticEvent)event;
//cache the generating widget for focus check in text entry handling
currentWidgetInfo = uiEvent.getHierarchyInfo();
}
//menu (menu)* menuItem <-- TODO: update this grammar (menu*)
if (event instanceof SemanticMenuSelectionEvent) {
handleMenuCase(stream, event);
//keyDown (keyDown)* [ | in text area]
//for now only text area related key downs are being captured...
} else if (event instanceof SemanticKeyDownEvent) {
List events = new ArrayList();
events.add(event);
while(stream.hasNext() && ((event = stream.peek()) instanceof SemanticKeyDownEvent)) {
events.add(stream.nextEvent()); //advance
}
handleTextEntry(events);
//text entry
} else if (event instanceof SemanticTextEntryEvent) {
SemanticKeyDownEvent[] keys = ((SemanticTextEntryEvent)event).getKeys();
handleTextEntry(Arrays.asList(keys));
//tree-item
} else if (event instanceof SemanticTreeItemSelectionEvent) {
SemanticTreeItemSelectionEvent e = (SemanticTreeItemSelectionEvent)event;
handleTreeItemSelection(e);
//swing table item
} else if (event instanceof SemanticTableSelectionEvent) {
SemanticTableSelectionEvent e = (SemanticTableSelectionEvent)event;
handleTableItemSelection(e);
} else if (event instanceof SemanticShellShowingEvent) {
SemanticShellShowingEvent show = (SemanticShellShowingEvent)event;
/*
* A work-around for the case were a close event causes a
* progress dialog to pop up. Waiting for this Shell is NOT
* the right thing to do.
*
* The case to ignore is where the _last_ event is a waitForShellShowing
*/
if (!stream.hasNext())
Tracer.trace(ICodeGenPluginTraceOptions.CODEGEN, "Trailing wait for shell event discarded");
else {
/*
* Another special case. Progress monitors that pop-up and go away should not be waited
* for. To handle these cases we discard any pairs of showing/disposed events that
* are on the same shell and have no events in between.
*/
ISemanticEvent next = stream.peek();
if (next != null && next instanceof SemanticShellDisposedEvent && sameShell((SemanticShellDisposedEvent)next, show)) {
//just advance the stream
stream.nextEvent();
TraceHandler.trace(ICodeGenPluginTraceOptions.CODEGEN, "show/dispose event pair (" + show.getName() +") ignored");
} else {
handleShellShowing(show);
}
}
} else if (event instanceof SemanticShellClosingEvent) {
handleShellClosing((SemanticShellClosingEvent)event);
} else if (event instanceof SemanticShellDisposedEvent) {
handleShellDisposed((SemanticShellDisposedEvent)event);
//buttons, etc.
} else if (event instanceof SemanticWidgetSelectionEvent) {
SemanticWidgetSelectionEvent widgetSelection = (SemanticWidgetSelectionEvent)event;
if (isButtonSelection(widgetSelection))
handleButtonClick(widgetSelection);
else if (widgetSelection.isContext()) {
handleMenuCase(stream, event);
} else {
/*
* Special case: dispose of superfluous selection before close
* TODO: push this out to be used by the recorder console
*/
if (!handledSelectionBeforeCloseSpecialCase(stream, widgetSelection))
handleGenericWidgetSelection(widgetSelection); //handle fall-through cases
}
} else if (event instanceof SemanticWidgetClosedEvent) {
handleWidgetClosed((SemanticWidgetClosedEvent)event);
} else if (event instanceof SemanticListSelectionEvent) {
SemanticListSelectionEvent listSelection = (SemanticListSelectionEvent)event;
handleListSelection(listSelection);
} else if (event instanceof SemanticComboSelectionEvent) {
SemanticComboSelectionEvent comboSelection = (SemanticComboSelectionEvent)event;
handleComboSelection(comboSelection);
} else if (event instanceof SemanticResizeEvent) {
SemanticResizeEvent resizeEvent = (SemanticResizeEvent)event;
handleResize(resizeEvent);
} else if (event instanceof SemanticMoveEvent) {
SemanticMoveEvent moveEvent = (SemanticMoveEvent)event;
handleMove(moveEvent);
} else if (event instanceof RecorderAssertionHookAddedEvent) {
RecorderAssertionHookAddedEvent assertEvent = (RecorderAssertionHookAddedEvent)event;
handleAssertion(assertEvent);
} else if (event instanceof SemanticFocusEvent) {
List events = new ArrayList();
events.add(event);
while(stream.hasNext() && ((event = stream.peek()) instanceof SemanticFocusEvent)) {
events.add(stream.nextEvent()); //advance
}
handleFocus(events);
} else if (event instanceof SemanticDropEvent) {
handleDrop((SemanticDropEvent)event);
} else if (event instanceof SemanticDragEvent) {
ISemanticEvent next = null;
do {
next = stream.peek();
if (next instanceof SemanticDropEvent) {
handleDragDrop((SemanticDragEvent)event, (SemanticDropEvent)stream.nextEvent());
//signal that we're done
next = null;
// shell lifecycle events are ignored
} else if (isShellLifeCycleEvent(next)) {
//advance token and trace
TraceHandler.trace(ICodeGenPluginTraceOptions.CODEGEN, "shell lifecyle event " + stream.nextEvent() + " ignored in drag event generation");
} else {
LogHandler.log("drag event(" + event +") not followed by a drop - got " + stream.peek() + " instead - discarded");
//we're done
next = null;
}
} while (next != null); //until we error or drop
}
}
return getTestBuilder().build();
}
private boolean isHandledInSetup(ISemanticEvent event) {
return setupBuilder.handled(event);
}
private void handleSetup(IEventStream stream) {
MethodUnit setup = setupBuilder.buildSetup(stream);
if (setup == null)
return;
getTestBuilder().addMethod(setup);
}
private boolean handledSelectionBeforeCloseSpecialCase(
IEventStream stream, SemanticWidgetSelectionEvent widgetSelection) {
if (!stream.hasNext())
return false;
ISemanticEvent next = stream.peek();
if (!(next instanceof SemanticWidgetClosedEvent))
return false;
IWidgetIdentifier closedWidget = ((SemanticWidgetClosedEvent) next).getHierarchyInfo();
IWidgetIdentifier selectedWidget = widgetSelection.getHierarchyInfo();
if (closedWidget == null || selectedWidget == null)
return false;
//spot check to see if targets are the same -- for now just class will suffice
if (closedWidget.getClass().equals(selectedWidget.getClass()))
return true;
return false;
}
/**
* Check to see if the events are triggered by the same shell.
*/
private boolean sameShell(SemanticShellDisposedEvent dispose, SemanticShellShowingEvent show) {
String n1 = dispose.getName();
String n2 = show.getName();
return n1 == null ? n1 == null : n1.equals(n2);
}
/**
* Check for shell life-cycle event.
* @param event the event in question.
* @return <code>true</code> if it is a shell lifecycle event
*/
private boolean isShellLifeCycleEvent(ISemanticEvent event) {
return event instanceof SemanticShellClosingEvent ||
event instanceof SemanticShellDisposedEvent ||
event instanceof SemanticShellShowingEvent;
}
/**
* Handle menu selections
*/
private void handleMenuCase(IEventStream stream, ISemanticEvent event) {
List events = new ArrayList();
events.add(event);
ISemanticEvent top = event;
while(stream.hasNext() && ((event = stream.peek()) instanceof SemanticMenuSelectionEvent) && isInSameMenu(event, top)) {
events.add(stream.nextEvent()); //advance
}
SemanticMenuSelectionEvent last = (SemanticMenuSelectionEvent)events.remove(events.size()-1);
handleMenuInvocation(events, last);
}
////////////////////////////////////////////////////////////////////////////
//
// Hooks for implementing custom event handling.
//
////////////////////////////////////////////////////////////////////////////
protected abstract void handleDragDrop(SemanticDragEvent drag, SemanticDropEvent drop);
protected abstract void handleShellClosing(SemanticShellClosingEvent event);
protected abstract void handleShellDisposed(SemanticShellDisposedEvent event);
protected abstract void handleWidgetClosed(SemanticWidgetClosedEvent event);
protected abstract void handleShellShowing(SemanticShellShowingEvent event);
protected abstract void handleListSelection(SemanticListSelectionEvent listSelection);
protected abstract void handleComboSelection(SemanticComboSelectionEvent comboSelection);
protected abstract void handleMove(SemanticMoveEvent moveEvent);
protected abstract void handleResize(SemanticResizeEvent resizeEvent);
protected abstract void handleDrop(SemanticDropEvent event);
/**
* Handle assert hooks.
*/
protected abstract void handleAssertion(RecorderAssertionHookAddedEvent assertEvent);
/**
* Handle an inspection (and possible flagged assertion).
*/
protected abstract void handleInspection(SemanticWidgetInspectionEvent event);
/**
* A fall-through method to handle widget selections that do not have defined handling logic.
* Note that this method is only called if no other handler is called for the given event.
* @param event - the event
*/
protected abstract void handleGenericWidgetSelection(SemanticWidgetSelectionEvent event);
/**
* Handle a tree-item select UI event.
* @param event - the event
*/
protected abstract void handleTreeItemSelection(SemanticTreeItemSelectionEvent event);
/**
* Handle a table-item select UI event.
* @param event - the event
*/
protected abstract void handleTableItemSelection(SemanticTableSelectionEvent event);
/**
* Handle a button-click UI event.
* @param event - the event
*/
protected abstract void handleButtonClick(SemanticWidgetSelectionEvent event);
/**
* Handle a text entry event.
* @param events - the list of key events
*/
protected abstract void handleTextEntry(List events);
/**
* Handle a focus event.
* @param events - the list of focus events
*/
protected abstract void handleFocus(List events);
/**
* Handle a menu invocation event.
* @param events - the list of menu selection events
* @param last - the item selection event
*/
protected abstract void handleMenuInvocation(List events, SemanticMenuSelectionEvent last);
////////////////////////////////////////////////////////////////////////////
//
// Helper predicates
//
////////////////////////////////////////////////////////////////////////////
/**
* Check whether this is a button selection event.
* @param event - the event in question
* @return true if it is a button-selection
*/
private boolean isButtonSelection(SemanticWidgetSelectionEvent event) {
return event.getItemClass().equals("org.eclipse.swt.widgets.Button");
}
/**
* Check to see if these two events are in the same menu.
*/
private boolean isInSameMenu(ISemanticEvent event, ISemanticEvent top) {
//these should both be ISWTSemanticEvents
if (!(event instanceof IUISemanticEvent) || !(top instanceof IUISemanticEvent))
return false;
IUISemanticEvent one = (IUISemanticEvent)event;
IUISemanticEvent two = (IUISemanticEvent)top;
if (one.getHierarchyInfo() == null || two == null)
return false;
return one.getHierarchyInfo().equals(two.getClass());
}
}