blob: 87c1ea69c83dadfb89e115cd5bcb4b3833cb85d9 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.junit.rebind;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class generates a stub class for classes that derive from GWTTestCase.
* This stub class provides the necessary bridge between our Hosted or Hybrid
* mode classes and the JUnit system.
*/
public class JUnitTestCaseStubGenerator extends Generator {
/**
* An interface for filtering out methods.
*/
protected interface MethodFilter {
boolean accept(JMethod method);
}
/**
* Like JClassType.getMethod(String name) except:
*
* <li>it accepts a filter</li>
* <li>it searches the inheritance hierarchy (includes superclasses)</li>
*
* For methods which are overridden, only the most derived implementations are
* included.
*
* @param type the type to search (non-null)
* @return the set of matching methods (non-null)
*/
protected static Map<String, List<JMethod>> getAllMethods(JClassType type,
MethodFilter filter) {
Map<String, List<JMethod>> methods = new HashMap<String, List<JMethod>>();
JClassType cls = type;
while (cls != null) {
JMethod[] clsDeclMethods = cls.getMethods();
// For every method, include it iff our filter accepts it
// and we don't already have a matching method
for (int i = 0, n = clsDeclMethods.length; i < n; ++i) {
JMethod declMethod = clsDeclMethods[i];
if (!filter.accept(declMethod)) {
continue;
}
List<JMethod> list = methods.get(declMethod.getName());
if (list == null) {
list = new ArrayList<JMethod>();
methods.put(declMethod.getName(), list);
list.add(declMethod);
continue;
}
JParameter[] declParams = declMethod.getParameters();
for (int j = 0; j < list.size(); ++j) {
JMethod method = list.get(j);
JParameter[] parameters = method.getParameters();
if (!equals(declParams, parameters)) {
list.add(declMethod);
}
}
}
cls = cls.getSuperclass();
}
return methods;
}
/**
* Returns true if the method is considered to be a valid JUnit test method.
* The criteria are that the method's name begin with "test" and have public
* access. The method may be static. You must choose to include or exclude
* methods which have arguments.
*/
protected static boolean isJUnitTestMethod(JMethod method, boolean acceptArgs) {
if (!method.getName().startsWith("test")) {
return false;
}
if (!method.isPublic()) {
return false;
}
return acceptArgs || method.getParameters().length == 0 && !acceptArgs;
}
/**
* Returns true IFF the two sets of parameters are of the same lengths and
* types.
*
* @param params1 must not be null
* @param params2 must not be null
*/
private static boolean equals(JParameter[] params1, JParameter[] params2) {
if (params1.length != params2.length) {
return false;
}
for (int i = 0; i < params1.length; ++i) {
if (params1[i].getType() != params2[i].getType()) {
return false;
}
}
return true;
}
/**
* Returns the method names for the set of methods that are strictly JUnit
* test methods (have no arguments).
*
* @param requestedClass
*/
private static String[] getTestMethodNames(JClassType requestedClass) {
return getAllMethods(requestedClass, new MethodFilter() {
public boolean accept(JMethod method) {
return isJUnitTestMethod(method, false);
}
}).keySet().toArray(new String[] {});
}
protected TreeLogger logger;
private String packageName;
private String qualifiedStubClassName;
private JClassType requestedClass;
private String simpleStubClassName;
private SourceWriter sourceWriter;
private TypeOracle typeOracle;
/**
* Create a new type that satisfies the rebind request.
*/
@Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
if (!init(logger, context, typeName)) {
return qualifiedStubClassName;
}
writeSource();
sourceWriter.commit(logger);
return qualifiedStubClassName;
}
protected JClassType getRequestedClass() {
return requestedClass;
}
protected SourceWriter getSourceWriter() {
return sourceWriter;
}
protected TypeOracle getTypeOracle() {
return typeOracle;
}
@SuppressWarnings("unused")
protected void writeSource() throws UnableToCompleteException {
String[] testMethods = getTestMethodNames(requestedClass);
writeDoRunTestMethod(testMethods, sourceWriter);
}
/**
* Gets the name of the native stub class.
*/
private String getSimpleStubClassName(JClassType baseClass) {
return "__" + baseClass.getSimpleSourceName() + "_unitTestImpl";
}
private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx,
String packageName, String className, String superclassName) {
PrintWriter printWriter = ctx.tryCreate(logger, packageName, className);
if (printWriter == null) {
return null;
}
ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
packageName, className);
composerFactory.setSuperclass(superclassName);
return composerFactory.createSourceWriter(ctx, printWriter);
}
private boolean init(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
this.logger = logger;
typeOracle = context.getTypeOracle();
assert typeOracle != null;
try {
requestedClass = typeOracle.getType(typeName);
} catch (NotFoundException e) {
logger.log(
TreeLogger.ERROR,
"Could not find type '"
+ typeName
+ "'; please see the log, as this usually indicates a previous error ",
e);
throw new UnableToCompleteException();
}
// Get the stub class name, and see if its source file exists.
//
simpleStubClassName = getSimpleStubClassName(requestedClass);
packageName = requestedClass.getPackage().getName();
qualifiedStubClassName = packageName + "." + simpleStubClassName;
sourceWriter = getSourceWriter(logger, context, packageName,
simpleStubClassName, requestedClass.getQualifiedSourceName());
return sourceWriter != null;
}
private void writeDoRunTestMethod(String[] testMethodNames, SourceWriter sw) {
sw.println();
sw.println("protected final void doRunTest(String name) throws Throwable {");
sw.indent();
for (int i = 0, n = testMethodNames.length; i < n; ++i) {
String methodName = testMethodNames[i];
if (i > 0) {
sw.print("else ");
}
sw.println("if (name.equals(\"" + methodName + "\")) {");
sw.indentln(methodName + "();");
sw.println("}");
}
sw.outdent();
sw.println("}"); // finish doRunTest();
}
}