/*******************************************************************************
* 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.generator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.windowtester.codegen.ICodeGenPluginTraceOptions;
import com.windowtester.codegen.ITestCaseBuilder;
import com.windowtester.codegen.TestCaseGenerator;
import com.windowtester.codegen.assembly.block.CodeBlock;
import com.windowtester.codegen.assembly.unit.ImportUnit;
import com.windowtester.codegen.assembly.unit.MethodUnit;
import com.windowtester.codegen.assembly.unit.Modifier;
import com.windowtester.codegen.generator.ICodegenAdvisor.Advice;
import com.windowtester.internal.debug.Tracer;
import com.windowtester.internal.runtime.IWidgetIdentifier;
import com.windowtester.internal.runtime.PropertySet.PropertyMapping;
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.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.recorder.event.user.UISemanticEvent;
import com.windowtester.runtime.swt.internal.debug.LogHandler;
import com.windowtester.runtime.swt.internal.preferences.ICodeGenConstants;
/**
* A code generator that generates test code using the WindowTester test API.
*/
public class PluggableCodeGenerator extends TestCaseGenerator {
private ShellSet _openShells = new ShellSet();
private ICodeBlockBuilder _snippetBuilder;
/**
* Create an instance.
* @param builder - the builder strategy
*/
public PluggableCodeGenerator(ITestCaseBuilder builder, ICodeBlockBuilder blockBuilder) {
super(builder);
_snippetBuilder = blockBuilder;
}
public PluggableCodeGenerator(ITestCaseBuilder builder, ICodeBlockBuilder blockBuilder, CodegenSettings codegenSettings) {
super(builder, codegenSettings);
_snippetBuilder = blockBuilder;
}
public ICodeBlockBuilder getBlockBuilder() {
return _snippetBuilder;
}
////////////////////////////////////////////////////////////////////////////
//
// Event Handlers
//
////////////////////////////////////////////////////////////////////////////
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleShellShowing(com.windowtester.recorder.event.user.SemanticShellShowingEvent)
*/
protected void handleShellShowing(SemanticShellShowingEvent event) {
/**
* If the shell was not already showing we need to generate a wait for condition
* TODO: this is abbot's scheme (to wait for Shell by name) but it is not robust (suppose we have 2 shells with the same name?)
*/
boolean alreadyOpen = !_openShells.add(event);
if (!alreadyOpen)
addWaitForShell(event);
}
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleShellDisposed(com.windowtester.recorder.event.user.SemanticShellDisposedEvent)
*/
protected void handleShellDisposed(SemanticShellDisposedEvent event) {
//remove the shell from the open list
_openShells.remove(event);
addWaitForShellDisposed(event);
}
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleShellClosing(com.windowtester.recorder.event.user.SemanticShellClosingEvent)
*/
protected void handleShellClosing(SemanticShellClosingEvent event) {
//remove the shell from the open list
_openShells.remove(event);
//gen closing code:
addCodeBlock(getBlockBuilder().buildShellClosing(event));
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleWidgetClosed(com.windowtester.recorder.event.user.SemanticWidgetClosedEvent)
*/
protected void handleWidgetClosed(SemanticWidgetClosedEvent event) {
addCodeBlock(getBlockBuilder().buildWidgetClosing(event));
}
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleMove(com.windowtester.recorder.event.user.SemanticMoveEvent)
*/
protected void handleMove(SemanticMoveEvent event) {
addCodeBlock(getBlockBuilder().buildMove(event));
}
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleResize(com.windowtester.recorder.event.user.SemanticResizeEvent)
*/
protected void handleResize(SemanticResizeEvent event) {
addCodeBlock(getBlockBuilder().buildResize(event));
}
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleFocus(com.windowtester.recorder.event.user.SemanticFocusEvent)
*/
protected void handleFocus(List events) {
SemanticFocusEvent curr;
SemanticFocusEvent last = null;
for (Iterator iter = events.iterator(); iter.hasNext();) {
curr = (SemanticFocusEvent)iter.next();
if (last == null || last != null && last.getHierarchyInfo().equals(curr.getHierarchyInfo())) {
addCodeBlock(getBlockBuilder().buildFocus(curr));
last = curr;
} // else, ignore duplicates
}
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleInspection(com.windowtester.recorder.event.user.SemanticWidgetInspectionEvent)
*/
protected void handleInspection(SemanticWidgetInspectionEvent event) {
PropertyMapping[] props = event.getProperties().toArray();
for (int i = 0; i < props.length; i++) {
if (props[i].isFlagged())
addCodeBlock(getBlockBuilder().buildAssertion(event.getLocator(), props[i]));
}
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleButtonClick(com.windowtester.swt.event.model.SemanticWidgetSelectionEvent)
*/
protected void handleButtonClick(SemanticWidgetSelectionEvent event) {
addCodeBlock(getBlockBuilder().buildButtonSelect(event));
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleMenuInvocation(java.util.List, com.windowtester.swt.event.model.SemanticWidgetSelectionEvent)
*/
protected void handleMenuInvocation(List events, SemanticMenuSelectionEvent event) {
addCodeBlock(getBlockBuilder().buildMenuSelect(event));
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleTreeItemSelection(com.windowtester.swt.event.model.SemanticTreeItemSelectionEvent)
*/
protected void handleTreeItemSelection(SemanticTreeItemSelectionEvent event) {
//TODO: move this to a generic import handler
//add import to resolve SWT.CHECK
//TODO: handle this in a system-independent way
// if (event.getChecked())
// addImport(new ImportUnit("org.eclipse.swt.SWT"));
addCodeBlock(getBlockBuilder().buildTreeSelect(event));
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleGenericWidgetSelection(com.windowtester.swt.event.model.SemanticWidgetSelectionEvent)
*/
protected void handleGenericWidgetSelection(SemanticWidgetSelectionEvent event) {
Advice advice = getAdvice(event);
if (!advice.isOverriden())
doHandleSelection(event);
}
protected Advice getAdvice(SemanticWidgetSelectionEvent event) {
ICodegenAdvisor[] contribs = CodegenContributionManager.getInstance().getContributedGenerators();
Advice advice = new Advice();
for (int i = 0; i < contribs.length; i++) {
ICodegenAdvisor advisor = contribs[i];
advisor.handleSelection(event, this, advice);
}
return advice;
}
/**
* Handle this selection event. DO NOT delegate to any advisors.
*/
public void doHandleSelection(SemanticWidgetSelectionEvent event) {
addCodeBlock(getSelectBlock(event));
}
private CodeBlock getSelectBlock(SemanticWidgetSelectionEvent event) {
return getBlockBuilder().buildSelect(event);
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleTextEntry(java.util.List)
*/
protected void handleTextEntry(List events) {
/*
* Identification errors are silently ignored here... a warning message should be produced
*
* TODO: this has been quickly hacked for the STPCon demo, it needs to be repaired to be SWT pluggable
*
*/
SemanticKeyDownEvent kde = (SemanticKeyDownEvent)events.get(0);
String key = null;
StringBuffer sb = new StringBuffer();
String controlKey = null; //used for control character handling
IWidgetIdentifier textEntryTarget = null;
for (Iterator iter = events.iterator(); iter.hasNext();) {
kde = (SemanticKeyDownEvent)iter.next();
key = kde.getKey();
controlKey = null; //reset the controlKey
//check for focus change
textEntryTarget = kde.getHierarchyInfo();
if (textEntryTarget != null && !textEntryTarget.equals(currentWidgetInfo)) {
//update cache...
currentWidgetInfo = textEntryTarget;
//flush the previous keys
if (sb.length() > 0) {
addCodeBlock(getBlockBuilder().buildTextEntry(sb.toString()));
sb = new StringBuffer();
}
}
//handle "control" keys specially (TABs, LFs)
if (isControl(key)) {
if (isBackSpace(key)) {
/**
* If the backspace "mid-stream", it is considered a typo and is corrected for,
* otherwise it is emmtited.
*/
if (sb.length() > 0) //in midstream
handleBackspace(sb);
}
controlKey = parseControlKey(kde);
//if it's a control char, handle it specially:
if (controlKey != null) {
//flush the previous keys
if (sb.length() > 0) {
addCodeBlock(getBlockBuilder().buildTextEntry(sb.toString()));
sb = new StringBuffer();
}
//add import to resolve control key
addImport(getBlockBuilder().getKeyEventImport());
//create a control entry snippet
addCodeBlock(getBlockBuilder().buildKeyEntry(controlKey));
}
//control sequences (ctrl-A) are also special...
} else if (kde.isControlSequence()) {
//add import to resolve control key
addImport(getBlockBuilder().getKeyEventImport());
//create a control entry snippet
addCodeBlock(getBlockBuilder().buildKeyEntry(getTestBuilder().getControlKey(), key));
} else { //non-control keys are just appended
sb.append(key);
}
}
//flush cached keys (if any)
if (sb.length() > 0) {
addCodeBlock(getBlockBuilder().buildTextEntry(sb.toString()));
}
}
private String parseControlKey(SemanticKeyDownEvent kde) {
return getTestBuilder().parseControlKey(kde);
}
/**
* Set the ui focus to a new target.
* @param newTarget - the new focus target
*/
protected void handleFocusChange(IWidgetIdentifier newTarget) {
addCodeBlock(getBlockBuilder().buildFocusChange(newTarget));
}
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleListSelection(com.windowtester.recorder.event.user.SemanticListSelectionEvent)
*/
protected void handleListSelection(SemanticListSelectionEvent listSelection) {
//TODO: handle this in a system-independent way
// String mask = listSelection.getMask();
// if (mask != null) {
// //add SWT import to identify mask
// addImport(new ImportUnit("org.eclipse.swt.SWT"));
// }
addCodeBlock(getBlockBuilder().build(listSelection));
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleTableItemSelection(com.windowtester.recorder.event.user.SemanticTableSelectionEvent)
*/
protected void handleTableItemSelection(SemanticTableSelectionEvent tableSelection) {
//TODO[!pq]: shouldn't table item selections have masks?
// String mask = tableSelection.getMask();
// if (mask != null) {
// //add SWT import to identify mask
// addImport(new ImportUnit("org.eclipse.swt.SWT"));
// }
addCodeBlock(getBlockBuilder().buildTableSelect(tableSelection));
}
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleComboSelection(com.windowtester.recorder.event.user.SemanticComboSelectionEvent)
*/
protected void handleComboSelection(SemanticComboSelectionEvent comboSelection) {
addCodeBlock(getBlockBuilder().build(comboSelection));
}
/* (non-Javadoc)
* @see com.windowtester.codegen.TestCaseGenerator#handleDrop(com.windowtester.recorder.event.user.SemanticDropEvent)
*/
protected void handleDrop(SemanticDropEvent drop) {
handleMissingSelectionIfNecessary(drop);
IUISemanticEvent dest = drop.getDropTargetEvent();
handleDragTo(dest);
}
private void handleMissingSelectionIfNecessary(SemanticDropEvent drop) {
CodeBlock missingSelect = getMissingDragTargetSelection(drop);
if (missingSelect != null)
addCodeBlock(missingSelect);
}
/**
* @see com.windowtester.codegen.TestCaseGenerator#handleDragDrop(com.windowtester.recorder.event.user.SemanticDragEvent, com.windowtester.recorder.event.user.SemanticDropEvent)
*/
//TODO: this is likely never called...
protected void handleDragDrop(SemanticDragEvent drag, SemanticDropEvent drop) {
/*
* The current implementation is not as clean as it might be.
* In particular, there is a selection event that may not be
* necessary (an extra tree selection for instance). To improve
* this, we might keep a back point to the last handled event
* or current focus and check to see if we really need to update...
*/
/*
* 1) handle hover
*/
IUISemanticEvent src = drag.getDragSourceEvent();
//first check for path-based selections:
if (src instanceof SemanticTreeItemSelectionEvent) {
handleTreeItemSelection((SemanticTreeItemSelectionEvent)src);
//NOTE: there might be more here: e.g., tables but tables are
//treated as widget selections for now
} else {
handleMouseMoveTo(src);
}
/*
* 2) handle drag and drop
*/
handleDrop(drop);
}
protected void handleDragTo(IUISemanticEvent event) {
addCodeBlock(getBlockBuilder().buildDragTo(event));
}
private CodeBlock getMissingDragTargetSelection(SemanticDropEvent drop) {
UISemanticEvent dragSource = drop.getDragSource();
if (!(dragSource instanceof SemanticWidgetSelectionEvent))
return null;
CodeBlock select = getBlockBuilder().buildSelect((SemanticWidgetSelectionEvent)dragSource);
if (select == null)
return null;
String selectString = select.toString();
if (selectString == null)
return null;
CodeBlock currentBlock = getTestBuilder().getCurrentBlock();
if (currentBlock == null)
return select;
if (currentBlock.toString().trim().equals(selectString.trim()))
return null;
return select;
}
private void handleMouseMoveTo(IUISemanticEvent event) {
addCodeBlock(getBlockBuilder().buildMoveTo(event));
}
protected void handleAssertion(RecorderAssertionHookAddedEvent assertEvent) {
String method = getTestBuilder().getFreshMethod(assertEvent.getHookName());
MethodUnit assertMethod = new MethodUnit(method,"// TODO Auto-generated method stub" + NEW_LINE);
assertMethod.addModifier(Modifier.PROTECTED);
assertMethod.addThrows("Exception");
addCodeBlock(getBlockBuilder().buildMethodInvocation(method));
getTestBuilder().addMethod(assertMethod);
}
////////////////////////////////////////////////////////////////////////////
//
// Code block helpers
//
////////////////////////////////////////////////////////////////////////////
protected void addCodeBlock(CodeBlock block) {
getTestBuilder().add(block);
}
protected void addImport(ImportUnit imprt) {
getTestBuilder().addImport(imprt);
}
/**
* Handle when a shell change event occurs by injecting the appropriate wait condition
* @param event - the shell title
*/
private void addWaitForShell(SemanticShellShowingEvent event) {
addCodeBlock(getBlockBuilder().buildWaitForShellShowing(event));
}
/**
* Handle when a shell dispose event occurs by injecting the appropriate wait condition
* @param event - the shell title
*/
private void addWaitForShellDisposed(SemanticShellDisposedEvent event) {
if (event == null || event.getName().equals("") || event.getName().length() ==0) {
//empty shell titles are ignored for now (these are usually component frames)
Tracer.trace(ICodeGenPluginTraceOptions.CODEGEN, "wait for empty shell title ignored");
return;
}
addCodeBlock(getBlockBuilder().buildWaitForShellDisposed(event));
}
////////////////////////////////////////////////////////////////////////////
//
// Helpers
//
////////////////////////////////////////////////////////////////////////////
protected StringBuffer createIdErrorMessage(String eventType, String comment) {
StringBuffer sb = new StringBuffer();
sb.append("//error identifying target of ").append(eventType).append(" event;").append(comment).append(NEW_LINE);
return sb;
}
protected StringBuffer createIdErrorMessage(String eventType) {
return createIdErrorMessage(eventType, "event ignored"); //a default comment
}
/**
* Check for control characters
* @param key - the character to check
* @return true if the key is a control character
*/
public static boolean isControl(String key) {
return key != null && Character.isISOControl(key.charAt(0));
}
/**
* Check for a backspace.
* @param key - the character to check
* @return true if the key is a backspace
*/
public static boolean isBackSpace(String key) {
return key.charAt(0) == '\b';
}
/**
* Check for a tab.
* @param key - the character to check
* @return true if the key is a tab
*/
protected boolean isTab(String key) {
return key.charAt(0) == '\t';
}
/**
* Check for an enter key.
* @param key - the character to check
* @return true if the key is an 'enter'
*/
protected boolean isEnter(String key) {
return key.charAt(0) == ICodeGenConstants.NEW_LINE.charAt(0) || key.charAt(0) == '\r';
}
/**
* Handle a backspace by deleting a char from the end of the buffer
* (if there is a char to delete).
* @param sb - the string buffer
*/
public void handleBackspace(StringBuffer sb) {
//System.out.print("<backspace>");
int last = sb.length() - 1;
if (last >= 0)
sb.deleteCharAt(last);
}
protected static final class ShellSet {
Set _shellTitles = new HashSet();
boolean add(String title) {
return _shellTitles.add(title);
}
boolean remove(String title) {
return _shellTitles.remove(title);
}
boolean add(SemanticShellShowingEvent event) {
String title = event.getName();
if (title == null) {
LogHandler.log("Shell title null --- ignoring in add");
return false;
}
return _shellTitles.add(title);
}
boolean remove(SemanticShellClosingEvent event) {
String title = event.getName();
if (title == null) {
LogHandler.log("Shell title null --- ignoring in remove");
return false;
}
return _shellTitles.remove(title);
}
boolean remove(SemanticShellDisposedEvent event) {
String title = event.getName();
if (title == null) {
LogHandler.log("Shell title null --- ignoring in remove");
return false;
}
return _shellTitles.remove(title);
}
boolean contains(String title) {
return _shellTitles.contains(title);
}
}
}