/*******************************************************************************
* 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.eclipse.ui.convert;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.text.edits.ReplaceEdit;
import com.windowtester.eclipse.ui.convert.rule.WTReplaceIUIContextMethodCallWithEnsureThatRule;
import com.windowtester.eclipse.ui.convert.rule.WTReplacePauseCallsRule;
import com.windowtester.eclipse.ui.convert.rule.WTReplaceSWTWidgetLocatorRule;
import com.windowtester.eclipse.ui.convert.rule.WTReplaceTypeRule;
import com.windowtester.runtime.locator.IWidgetReference;
import com.windowtester.runtime.swing.UITestCaseSwing;
import com.windowtester.runtime.swt.UITestCaseSWT;
import com.windowtester.runtime.swt.UnableToFindActiveShellException;
import com.windowtester.runtime.swt.locator.ShellLocator;
import com.windowtester.runtime.swt.locator.eclipse.ActiveEditorLocator;
import com.windowtester.runtime.swt.util.DebugHelper;
/**
* Refactoring for converting/migrating all java code in the selection
* project/package/class to use the new WindowTester API.
*/
public class WTConvertAPIRefactoring extends Refactoring
{
private final List<IJavaElement> selection;
private WTConvertAPIRule[] rules;
private Collection<IJavaElement> visited = new HashSet<IJavaElement>();
private CompositeChange compositeChange;
private RefactoringStatus status;
public WTConvertAPIRefactoring(List<IJavaElement> selected) {
this.selection = selected;
}
public String getName() {
return "WindowTester API Conversion";
}
/**
* Set the rules used to convert WindowTester API. This is used for testing purposes,
* and need not be called during normal operation.
*
* @param rules an array of conversion rules (not <code>null</code>, contains no
* <code>null</code>s)
*/
public void setRules(WTConvertAPIRule[] rules) {
this.rules = rules;
}
/**
* Answer the rules used to convert WindowTester API. This is used for testing
* purposes, and need not be called during normal operation.
*
* @re rules an array of conversion rules (not <code>null</code>, contains no
* <code>null</code>s)
*/
public WTConvertAPIRule[] getRules() {
if (rules == null) {
rules = new WTConvertAPIRule[]{
// TODO Include all old API WT classes that need to be replaced one-for-one with new API WT classes
new WTReplaceTypeRule("com.windowtester.runtime.swt.experimental.locator.ActiveEditorLocator",
ActiveEditorLocator.class),
new WTReplaceTypeRule("com.windowtester.swt.util.DebugHelper", DebugHelper.class),
new WTReplaceTypeRule("com.windowtester.runtime.locator.WidgetReference", IWidgetReference.class),
new WTReplaceTypeRule("junit.extensions.UITestCase", UITestCaseSWT.class),
new WTReplaceTypeRule("junit.extensions.UITestCaseSWT", UITestCaseSWT.class),
new WTReplaceTypeRule("junit.extensions.UITestCaseSwing", UITestCaseSwing.class),
new WTReplaceTypeRule("com.windowtester.runtime.swt.finder.UnableToFindActiveShellException",
UnableToFindActiveShellException.class),
// ??? Should we be converting references to internal classes ???
new WTReplaceTypeRule("com.windowtester.swt.util.ExceptionHandlingHelper",
"com.windowtester.runtime.swt.internal.ExceptionHandlingHelper"),
new WTReplaceTypeRule("com.windowtester.event.swt.text.InsertTextEntryStrategy",
"com.windowtester.runtime.swt.internal.text.InsertTextEntryStrategy"),
new WTReplaceTypeRule("com.windowtester.event.swt.text.ITextEntryStrategy",
"com.windowtester.runtime.swt.internal.text.ITextEntryStrategy"),
new WTReplaceTypeRule("com.windowtester.event.swt.text.TextEntryStrategy",
"com.windowtester.runtime.swt.internal.text.TextEntryStrategy"),
new WTReplaceTypeRule("com.windowtester.swt.util.PathStringTokenizerUtil",
"com.windowtester.runtime.swt.internal.util.PathStringTokenizerUtil"),
new WTReplaceTypeRule("com.windowtester.swt.util.TextUtils",
"com.windowtester.runtime.swt.internal.util.TextUtils"),
new WTReplaceTypeRule("com.windowtester.swt.WidgetLocatorService",
"com.windowtester.runtime.swt.internal.finder.WidgetLocatorService"),
new WTReplaceTypeRule("com.windowtester.finder.swt.ShellFinder",
"com.windowtester.runtime.swt.internal.finder.ShellFinder"),
// TODO Include all constructor replacement rules
new WTReplaceSWTWidgetLocatorRule(Shell.class, ShellLocator.class),
// TODO Include all method call replacement rules
new WTReplaceIUIContextMethodCallWithEnsureThatRule("setFocus", "hasFocus"),
new WTReplaceIUIContextMethodCallWithEnsureThatRule("close", "isClosed"), new WTReplacePauseCallsRule()
};
}
return rules;
}
/**
* Checks some initial conditions based on the element to be refactored. The method is
* typically called by the UI to perform an initial checks after an action has been
* executed.
* <p>
* The refactoring has to be considered as not being executable if the returned status
* has the severity of <code>RefactoringStatus#FATAL</code>.
*/
public RefactoringStatus checkInitialConditions(IProgressMonitor monitor) throws CoreException,
OperationCanceledException
{
if (monitor != null) {
monitor.beginTask("", 1); //$NON-NLS-1$
monitor.worked(1);
monitor.done();
}
return new RefactoringStatus();
}
/**
* After <code>checkInitialConditions</code> has been performed and the user has
* provided all input necessary to perform the refactoring this method is called to
* check the remaining preconditions.
* <p>
* The refactoring has to be considered as not being executable if the returned status
* has the severity of <code>RefactoringStatus#FATAL</code>.
*/
public RefactoringStatus checkFinalConditions(IProgressMonitor monitor) throws CoreException,
OperationCanceledException
{
if (monitor == null)
monitor = new NullProgressMonitor();
status = new RefactoringStatus();
compositeChange = new CompositeChange("Convert to new WindowTester API");
monitor.beginTask("Converting to new WindowTester API", selection.size());
for (Iterator<IJavaElement> iter = selection.iterator(); iter.hasNext();)
convertElement(iter.next(), new SubProgressMonitor(monitor, 1));
monitor.done();
// if (monitor != null) {
// monitor.beginTask("", 1); //$NON-NLS-1$
// monitor.worked(1);
// monitor.done();
// }
//
//
// monitor.beginTask("Checking preconditions", selection.size());
// for (IJavaElement elem : selection) {
// checkForProblems(elem, status, monitor);
// }
// monitor.done();
return status;
}
// private void checkForProblems(IJavaElement elem, RefactoringStatus status, IProgressMonitor monitor)
// throws JavaModelException
// {
// switch (elem.getElementType()) {
// case IJavaElement.JAVA_PROJECT :
// IJavaProject project = (IJavaProject) elem;
// IPackageFragmentRoot[] roots = project.getPackageFragmentRoots();
// monitor.beginTask("Checking " + project.getPath(), roots.length);
// for (int i = 0; i < roots.length; i++)
// checkForProblems(roots[i], status, new SubProgressMonitor(monitor, 1));
// monitor.done();
// break;
// case IJavaElement.PACKAGE_FRAGMENT_ROOT :
// IPackageFragmentRoot root = (IPackageFragmentRoot) elem;
// IJavaElement[] children = root.getChildren();
// monitor.beginTask("Checking " + root.getPath(), children.length);
// for (int i = 0; i < children.length; i++)
// checkForProblems(children[i], status, new SubProgressMonitor(monitor, 1));
// monitor.done();
// break;
// case IJavaElement.PACKAGE_FRAGMENT :
// IPackageFragment pkg = (IPackageFragment) elem;
// monitor.beginTask("Converting " + pkg.getPath(), pkg.getChildren().length);
// for (IJavaElement child : pkg.getChildren())
// checkForProblems(child, status, new SubProgressMonitor(monitor, 1));
// monitor.done();
// break;
// case IJavaElement.COMPILATION_UNIT :
// ICompilationUnit cu = (ICompilationUnit) elem;
// if (hasProblem(cu)) {
// //JDT status contexts are all internal so this is the best we can do
// status.addWarning(cu.getElementName() + " has syntax errors and will be skipped");
// }
// break;
// default :
// break;
// }
// }
// private boolean hasProblem(ICompilationUnit cu) throws JavaModelException {
// String source = cu.getSource();
// ASTParser parser = ASTParser.newParser(AST.JLS3);
// parser.setSource(source.toCharArray());
// CompilationUnit compUnit = (CompilationUnit) parser.createAST(new NullProgressMonitor());
// IProblem[] problems = compUnit.getProblems();
// for (IProblem problem : problems) {
// if (problem.isError())
// return true;
// }
// return false;
// }
/**
* Recursively iterate over the selected java elements and their children to convert
* each compilation to use the new WindowTester API. Creates a {@link Change} object
* that performs the actual workspace transformation.
*/
public Change createChange(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
return compositeChange;
}
/**
* Recursively iterate over the specified java element and their children to convert
* each compilation to use the new WindowTester API.
*
* @param elem the java element (not <code>null</code>)
* @param monitor the progress monitor (not <code>null</code>)
*/
private void convertElement(IJavaElement elem, IProgressMonitor monitor) throws JavaModelException {
switch (elem.getElementType()) {
case IJavaElement.JAVA_PROJECT :
convertProject((IJavaProject) elem, monitor);
break;
case IJavaElement.PACKAGE_FRAGMENT_ROOT :
convertPackageRoot((IPackageFragmentRoot) elem, monitor);
break;
case IJavaElement.PACKAGE_FRAGMENT :
convertPackage((IPackageFragment) elem, monitor);
break;
case IJavaElement.COMPILATION_UNIT :
convertCompilationUnit((ICompilationUnit) elem, monitor);
break;
default :
break;
}
}
/**
* Recursively iterate over the elements in the specified java element to convert each
* compilation to use the new WindowTester API.
*
* @param proj the java project (not <code>null</code>)
* @param monitor the progress monitor (not <code>null</code>)
*/
private void convertProject(IJavaProject proj, IProgressMonitor monitor) throws JavaModelException {
if (visited.contains(proj))
return;
visited.add(proj);
IPackageFragmentRoot[] roots = proj.getPackageFragmentRoots();
monitor.beginTask("Converting " + proj.getPath(), roots.length);
for (int i = 0; i < roots.length; i++)
convertPackageRoot(roots[i], new SubProgressMonitor(monitor, 1));
monitor.done();
}
/**
* Recursively iterate over the elements in the specified java element to convert each
* compilation to use the new WindowTester API.
*
* @param root the package fragment root (not <code>null</code>)
* @param monitor the progress monitor (not <code>null</code>)
*/
private void convertPackageRoot(IPackageFragmentRoot root, IProgressMonitor monitor) throws JavaModelException {
if (root.getKind() != IPackageFragmentRoot.K_SOURCE || visited.contains(root))
return;
visited.add(root);
IJavaElement[] children = root.getChildren();
monitor.beginTask("Converting " + root.getPath(), children.length);
for (int i = 0; i < children.length; i++)
convertElement(children[i], new SubProgressMonitor(monitor, 1));
monitor.done();
}
/**
* Recursively iterate over the elements in the specified java element to convert each
* compilation to use the new WindowTester API.
*
* @param pkg the package fragment (not <code>null</code>)
* @param monitor the progress monitor (not <code>null</code>)
*/
private void convertPackage(IPackageFragment pkg, IProgressMonitor monitor) throws JavaModelException {
if (visited.contains(pkg))
return;
visited.add(pkg);
IJavaElement[] children = pkg.getChildren();
monitor.beginTask("Converting " + pkg.getPath(), children.length);
for (int i = 0; i < children.length; i++)
convertElement(children[i], new SubProgressMonitor(monitor, 1));
monitor.done();
}
/**
* Convert the specified compilation unit to use the new WindowTester API.
*
* @param compUnit the compilation unit (not <code>null</code>)
* @param monitor the progress monitor (not <code>null</code>)
*/
private void convertCompilationUnit(ICompilationUnit compUnit, IProgressMonitor monitor) throws JavaModelException {
if (visited.contains(compUnit))
return;
visited.add(compUnit);
monitor.beginTask("Converting " + compUnit.getPath(), 1);
String oldSource = compUnit.getSource();
WTConvertAPIContext context;
try {
context = new WTConvertAPIContextBuilder().buildContext(compUnit);
convertCompilationUnitSource(context);
}
catch (WTConvertAPIParseException e) {
IProblem problem = e.getProblem();
status.addWarning(compUnit.getElementName() + " has a syntax error on line "
+ problem.getSourceLineNumber() + " and will be skipped: " + problem);
return;
}
if (context.isSourceModified()) {
String newSource = context.getSource();
TextFileChange change = new TextFileChange(compUnit.getElementName(), (IFile) compUnit.getResource());
change.setEdit(new ReplaceEdit(0, oldSource.length(), newSource));
compositeChange.add(change);
}
monitor.done();
}
/**
* Convert the specified compilation unit's source. This is used for testing purposes
* and typically not called outside this class during the normal course of events
*
* @param source the compilation unit source
* @return the context containing the converted source (not <code>null</code>)
*/
public WTConvertAPIContext convertCompilationUnitSource(String source) {
WTConvertAPIContext context = new WTConvertAPIContextBuilder().buildContext(source);
convertCompilationUnitSource(context);
return context;
}
private void convertCompilationUnitSource(WTConvertAPIContext context) {
if (context.hasWTReferences())
for (WTConvertAPIRule rule : getRules())
rule.convert(context);
}
}