blob: 6be8e2c6c30682417fe7ab5b8f14c4c0fb9be859 [file] [log] [blame]
/*
* Copyright 2007 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.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
/**
* 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 {
interface MethodFilter {
public boolean accept( JMethod method );
}
private static final String GWT_TESTCASE_CLASS_NAME = "com.google.gwt.junit.client.GWTTestCase";
/**
* Returns the method names for the set of methods that are strictly JUnit
* test methods (have no arguments).
*
* @param requestedClass
*/
public static String[] getTestMethodNames(JClassType requestedClass) {
return (String[]) getAllMethods( requestedClass, new MethodFilter() {
public boolean accept(JMethod method) {
return isJUnitTestMethod(method,false);
}
} ).keySet().toArray( new String[] {} );
}
/**
* Like JClassType.getMethod( String name ), except:
*
* <li>it accepts a filter</li>
* <li>it searches the inheritance hierarchy (includes subclasses)</li>
*
* For methods which are overriden, only the most derived implementations are included.
*
* @param type The type to search. Must not be null
* @return Map<String.List<JMethod>> The set of matching methods. Will not be null.
*/
static Map getAllMethods( JClassType type, MethodFilter filter ) {
Map 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 list = (List)methods.get(declMethod.getName());
if (list == null) {
list = new ArrayList();
methods.put(declMethod.getName(),list);
list.add(declMethod);
continue;
}
JParameter[] declParams = declMethod.getParameters();
for (int j = 0; j < list.size(); ++j) {
JMethod method = (JMethod)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.
*
*/
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;
}
String qualifiedStubClassName;
String simpleStubClassName;
String typeName;
TreeLogger logger;
String packageName;
private JClassType requestedClass;
private SourceWriter sourceWriter;
private TypeOracle typeOracle;
/**
* Create a new type that statisfies the rebind request.
*/
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
if ( ! init( logger, context, typeName ) ) {
return qualifiedStubClassName;
}
writeSource();
sourceWriter.commit( logger );
return qualifiedStubClassName;
}
public JClassType getRequestedClass() {
return requestedClass;
}
public SourceWriter getSourceWriter() {
return sourceWriter;
}
public TypeOracle getTypeOracle() {
return typeOracle;
}
boolean init(TreeLogger logger, GeneratorContext context,String typeName) throws
UnableToCompleteException {
this.typeName = typeName;
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;
}
void writeSource() throws UnableToCompleteException {
String[] testMethods = getTestMethodNames(requestedClass);
writeGetNewTestCase(simpleStubClassName, sourceWriter);
writeDoRunTestMethod(testMethods, sourceWriter);
writeGetTestName(typeName, 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 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();
}
/**
* Create the appMain method that is the main entry point for the GWT
* application.
*/
private void writeGetNewTestCase(String stubClassName, SourceWriter sw) {
sw.println();
sw.println("public final " + GWT_TESTCASE_CLASS_NAME
+ " getNewTestCase() {");
sw.indent();
sw.println("return new " + stubClassName + "();");
sw.outdent();
sw.println("}"); // finish getNewTestCase();
}
/**
* Create the appMain method that is the main entry point for the GWT
* application.
*/
private void writeGetTestName(String testClassName, SourceWriter sw) {
sw.println();
sw.println("public final String getTestName() {");
sw.indent();
sw.println("return \"" + testClassName + "\";");
sw.outdent();
sw.println("}"); // finish getNewTestCase();
}
}