package xapi.dev.gwtc.impl;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import xapi.annotation.compile.Dependency;
import xapi.annotation.compile.Dependency.DependencyType;
import xapi.annotation.compile.DependencyBuilder;
import xapi.annotation.compile.Resource;
import xapi.annotation.compile.ResourceBuilder;
import xapi.collect.X_Collect;
import xapi.collect.api.StringTo;
import xapi.dev.gwtc.api.GwtcService;
import xapi.dev.source.ClassBuffer;
import xapi.dev.source.MethodBuffer;
import xapi.dev.source.SourceBuilder;
import xapi.dev.source.SourceBuilder.JavaType;
import xapi.dev.source.XmlBuffer;
import xapi.file.X_File;
import xapi.fu.Out1;
import xapi.gwtc.api.GwtManifest;
import xapi.gwtc.api.GwtcXmlBuilder;
import xapi.log.X_Log;
import xapi.log.api.LogLevel;
import xapi.reflect.X_Reflect;
import xapi.util.X_Debug;
import xapi.util.X_Namespace;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.junit.tools.GWTTestSuite;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public abstract class GwtcServiceAbstract implements GwtcService {
protected class Replacement {
protected String newValue;
protected int start, end;
public Replacement(int start, int end, String path) {
this.start = start;
this.end = end;
newValue = path;
}
}
protected final GwtcContext context;
protected final String genName;
protected final File tempDir;
protected SourceBuilder<GwtcService> entryPoint;
protected final Set<String> finished;
protected GwtcEntryPointBuilder out;
private boolean needsReportError = true;
protected String manifestName;
protected StringTo<Out1<String>> files;
public GwtcServiceAbstract(ClassLoader resourceLoader) {
finished = new HashSet<>();
files = X_Collect.newStringMap(Out1.class);
context = new GwtcContext(this, resourceLoader);
genName = context.getGenName();
tempDir = X_File.createTempDir(genName);
String qualifiedName = GwtManifest.GEN_PREFIX+"."+genName;
entryPoint = new SourceBuilder<GwtcService>("public class "+ genName).setPackage(GwtManifest.GEN_PREFIX);
out = new GwtcEntryPointBuilder(entryPoint);
context.setEntryPoint(qualifiedName);
context.addGwtXmlSource(GwtManifest.GEN_PREFIX);
}
@Override
public void addClass(Class<?> clazz) {
if (!finished.add(clazz.getName())) {
return;
}
addGwtModules(clazz);
if (EntryPoint.class.isAssignableFrom(clazz)) {
try {
addMethod(clazz.getMethod("onModuleLoad"));
} catch (Exception e) {
X_Log.error(getClass(), "Could not extract onModuleLoad method from ", clazz, e);
}
} else if (GWTTestCase.class.isAssignableFrom(clazz)) {
addGwtTestCase(clazz.asSubclass(GWTTestCase.class));
} else if (GWTTestSuite.class.isAssignableFrom(clazz)) {
addGwtTestSuite(clazz.asSubclass(GWTTestSuite.class));
} else if (RunAsyncCallback.class.isAssignableFrom(clazz)) {
addAsyncBlock(clazz.asSubclass(RunAsyncCallback.class));
} else {
// Check if this class has methods annotated w/ junit @Test
for (Method m : clazz.getMethods()) {
if (m.getAnnotation(Test.class) != null) {
finished.remove(clazz.getName());
addJUnitClass(clazz);
return;
}
}
try {
addMethod(clazz.getMethod("main", String[].class));
} catch (Exception ignored){
X_Log.warn(getClass(), "Class",clazz," was added to Gwtc, "
+ "but that class was not a subclass of EntryPoint, RunAsync,"
+ " GWTTestCase, GWTTestSuite, nor did it have a main method, or"
+ " any JUnit 4 annotated @Test method.");
}
}
}
@Override
public void addGwtModules(Class<?> clazz) {
context.addClass(clazz);
}
@Override
public void addClasspath(Class<?> cls) {
String loc = X_Reflect.getFileLoc(cls);
final DependencyBuilder builder = DependencyBuilder
.buildDependency(loc)
.setDependencyType(DependencyType.ABSOLUTE);
context.addDependency(builder.build());
// if loc is a src/main/java or src/test/java, also include resources module:
String unixed = loc.replace('\\', '/');
int index = unixed.indexOf("src/main/java");
if (index != -1) {
context.addDependency(builder
.setValue(unixed.replace("src/main/java", "src/main/resources"))
.build());
} else if (unixed.indexOf("src/test/java") != -1) {
context.addDependency(builder
.setValue(unixed.replace("src/test/java", "src/test/resources"))
.build());
context.addDependency(builder
.setValue(unixed.replace("src/test/java", "src/main/java"))
.build());
context.addDependency(builder
.setValue(unixed.replace("src/test/java", "src/main/resources"))
.build());
}
}
public String getEntryPoint() {
return entryPoint.toString();
}
public String getGwtXml(GwtManifest manifest) {
return context.getGwtXml(manifest).toString();
}
@Override
public String getModuleName() {
return genName;
}
protected void generateWar(GwtManifest manifest) {
String warDir = manifest.getWarDir();
String moduleName = manifest.getModuleName();
final XmlBuffer
buffer = new XmlBuffer("html"),
head = buffer.makeTag("head"),
body= buffer.makeTag("body")
;
buffer.printBefore(getHostPageDocType());
context.generateAll(new File(manifest.getGenDir()), moduleName, head, body);
head.makeTag("script")
.setAttribute("type", "text/javascript")
.setAttribute("src", getScriptLocation(moduleName));
String hostPage = buffer.toString();
X_File.saveFile(warDir +"/public", "index.html", hostPage);
info("Generated host page:\n"+hostPage);
X_Log.info("Generate war into ", warDir);
}
protected String getHostPageDocType() {
return "<!doctype html>\n";
}
private String getScriptLocation(String genName) {
return //genName+"/"+
genName+".nocache.js";
}
protected void doLog(LogLevel level, String msg) {
X_Log.log(level, msg);
}
@Override
public File getTempDir() {
return tempDir;
}
public void error(String log) {
doLog(LogLevel.ERROR, log);
}
public void warn(String log) {
doLog(LogLevel.WARN, log);
}
public void info(String log) {
doLog(LogLevel.INFO, log);
}
public void trace(String log) {
doLog(LogLevel.TRACE, log);
}
public void debug(String log) {
doLog(LogLevel.DEBUG, log);
}
public void addDependency(Dependency dep) {
context.addDependency(dep);
}
@Override
public boolean addJUnitClass(Class<?> clazz) {
inheritGwtXml(clazz, ResourceBuilder.buildResource("org.junit.JUnit4").build());
X_Log.info(getClass(), "Adding class", clazz," to junit 4 module");
addDependency(
DependencyBuilder.buildDependency("gwt-reflect")
.setGroupId("net.wetheinter")
.setVersion(X_Namespace.GWT_VERSION)
.setClassifier("tests")
.setDependencyType(DependencyType.MAVEN)
.build()
);
List<Method>
beforeClass = new ArrayList<>(),
before = new ArrayList<>(),
test = new ArrayList<>(),
after = new ArrayList<>(),
afterClass = new ArrayList<>()
;
List<Class<?>> hierarchy = new LinkedList<>();
{
Class<?> c = clazz;
while (c != Object.class) {
hierarchy.add(0, c);
c = c.getSuperclass();
}
}
for (Class<?> c : hierarchy) {
for (Method method : c.getMethods()) {
if (method.getAnnotation(Test.class) != null) {
test.add(method);
} else if (method.getAnnotation(Before.class) != null) {
before.add(method);
} else if (method.getAnnotation(BeforeClass.class) != null) {
beforeClass.add(method);
} else if (method.getAnnotation(After.class) != null) {
after.add(method);
} else if (method.getAnnotation(AfterClass.class) != null) {
afterClass.add(method);
}
}
}
out.println(generateTestRunner(clazz, beforeClass, before, test, after, afterClass));
return true;
}
protected void inheritGwtXml(Class<?> clazz, Resource build) {
context.addGwtXmlInherit(build.value());
}
protected ClassBuffer classBuffer() {
return entryPoint.getClassBuffer();
}
protected String extractGwtVersion(String gwtHome) {
int lastInd = gwtHome.lastIndexOf("gwt-dev");
gwtHome = gwtHome.substring(lastInd+7).replace(".jar", "");
return gwtHome.startsWith("-") ? gwtHome.substring(1) : gwtHome;
}
protected void generateReportError(ClassBuffer classBuffer) {
classBuffer.createMethod("private static void reportError"
+ "(Class<?> clazz, String method, Throwable e)")
.println("String error = method+\" failed test\";")
.println("System.err.println(error);")
.println("e.printStackTrace();");
}
protected String generateSetupMethod(String field, String type, Class<?> clazz, List<Method> befores) {
if (befores.isEmpty()) {
return "";
}
String methodName = type+"TestRun_"+field;
String simpleName = entryPoint.getImports().addImport(clazz);
MethodBuffer runner = entryPoint.getClassBuffer().createMethod(
"private final void "+methodName+"("+simpleName+" field)");
for (Method before : befores) {
runner.println("field."+before.getName()+"();");
}
return methodName;
}
protected String generateTestRunner(Class<?> clazz, List<Method> beforeClasses,
List<Method> befores, List<Method> tests, List<Method> afters,
List<Method> afterClasses) {
String field = out.formatInstanceProvider(clazz);
String methodName = "testRun_"+field;
MethodBuffer runner = classBuffer().createMethod("private final void "+methodName);
// Setup
for (Method beforeClass : beforeClasses) {
String shortName = runner.addImport(beforeClass.getDeclaringClass());
runner.println(shortName+"."+beforeClass.getName()+"();");
}
String shortName = runner.addImport(clazz);
String beforeMethod = generateSetupMethod(field, "before", clazz, befores);
String afterMethod = generateSetupMethod(field, "after", clazz, afters);
for (Method test : tests) {
String testName = methodName+"_"+test.getName();
MethodBuffer testRunner = entryPoint.getClassBuffer().createMethod
("private final void "+testName);
if (!beforeMethod.isEmpty()) {
testRunner.println(beforeMethod+"("+field+");");
}
Test testAnno = test.getAnnotation(Test.class);
testRunner.startTry();
testRunner.println(field+"."+test.getName()+"();");
if (testAnno.expected() != Test.None.class) {
String assertClass = testRunner.addImport(Assert.class);
String exceptionType = testRunner.addImport(testAnno.expected());
testRunner.println(assertClass+".fail(\""
+ "expected exception "+exceptionType+ "\");");
testRunner.startCatch(exceptionType);
}
String errorName = testRunner.startCatch("Throwable");
ensureReportError();
testRunner.println("reportError("+shortName+".class,"
+ "\""+test.toGenericString()+"\", "+errorName+ ");");
if (!afterMethod.isEmpty()) {
testRunner.startFinally();
testRunner.println(afterMethod+"("+field+");");
}
testRunner.endTry();
runner.println(testName+"();");
}
for (Method afterClass : afterClasses) {
shortName = runner.addImport(afterClass.getDeclaringClass());
runner.println(shortName+"."+afterClass.getName()+"();");
}
return methodName+"();";
}
protected void ensureReportError() {
if (needsReportError) {
needsReportError = false;
generateReportError(entryPoint.getClassBuffer());
}
}
public void copyModuleTo(String module, GwtManifest manifest) {
String from = manifestName == null ? genName : manifestName;
File f = new File(tempDir, from+".gwt.xml");
if (f.exists()) {
int ind = module.lastIndexOf('.');
String path;
if (ind == -1) {
path = "";
} else {
path = module.substring(0, ind).replace('.', '/');
}
f = new File(tempDir, path);
f.mkdirs();
saveGwtXmlFile(context.getGwtXml(manifest), module.substring(ind+1), manifest);
}
}
protected void saveGwtXmlFile(XmlBuffer xml, String moduleName, GwtManifest manifest) {
saveTempFile(GwtcXmlBuilder.HEADER+xml, new File(inGeneratedDirectory(manifest, moduleName.replace('.', '/')+".gwt.xml")));
}
protected void saveTempFile(String value, File dest) {
X_Log.trace(getClass(), "saving generated file to",dest);
final boolean success = dest.getParentFile().mkdirs();
assert success || dest.getParentFile().exists() : "Unable to create parent directories for " + dest.getAbsolutePath();
try (FileWriter out = new FileWriter(dest)) {
out.append(value);
} catch (IOException e) {
X_Log.warn(getClass(), "Error saving generated file ",dest,"\n"+value);
throw X_Debug.rethrow(e);
}
}
@Override
public SourceBuilder createJavaFile(String pkg, String filename, JavaType type) {
final SourceBuilder builder = new SourceBuilder();
type.initialize(builder, pkg, filename);
createFile(pkg.replace('.', '/'), filename+".java", builder::toSource);
return builder;
}
@Override
public void createFile(String pkg, String filename, Out1<String> sourceProvider) {
String fqcn = pkg + File.separatorChar + filename;
assert !files.containsKey(fqcn) : "Already created file with the qualified name " + fqcn;
files.put(fqcn, sourceProvider);
}
@Override
public String modifyPackage(String pkgToUse) {
// exists in case subclasses want to repackage things...
return pkgToUse;
}
@Override
public MethodBuffer addMethodToEntryPoint(String methodDef) {
return entryPoint.getClassBuffer().createMethod(methodDef);
}
@Override
public void addGwtInherit(String inherit) {
context.addGwtXmlInherit(inherit);
}
@Override
public MethodBuffer getOnModuleLoad() {
return out.out;
}
}