package org.tessell.processor;
import static joist.sourcegen.Argument.arg;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import joist.sourcegen.Argument;
import joist.sourcegen.GClass;
import joist.sourcegen.GMethod;
import joist.util.Copy;
import joist.util.Function1;
import joist.util.Join;
import org.exigencecorp.aptutil.GenericSuffix;
import org.exigencecorp.aptutil.Prop;
import org.exigencecorp.aptutil.PropUtil;
import org.exigencecorp.aptutil.Util;
import org.tessell.GenDispatch;
import org.tessell.In;
import org.tessell.Out;
public class DispatchGenerator {
private final ProcessingEnvironment env;
private final TypeElement element;
private final GClass actionClass;
private final GClass resultClass;
private final GenericSuffix generics;
private final Map<Integer, VariableElement> inParams = new TreeMap<Integer, VariableElement>();
private final Map<Integer, VariableElement> outParams = new TreeMap<Integer, VariableElement>();
private final String simpleName;
private final String dispatchPackageName;
public DispatchGenerator(ProcessingEnvironment env, TypeElement element) throws InvalidTypeElementException {
if (!element.toString().endsWith("Spec")) {
env.getMessager().printMessage(Kind.ERROR, "GenDispatch targets must end with a Spec suffix", element);
throw new InvalidTypeElementException();
}
this.env = env;
this.element = element;
this.generics = new GenericSuffix(element);
simpleName = element.toString().replaceAll("Spec$", "");
dispatchPackageName = detectDispatchBasePackage(env);
this.actionClass = new GClass(simpleName + "Action" + generics.varsWithBounds);
this.resultClass = new GClass(simpleName + "Result" + generics.varsWithBounds);
}
public void generate() {
setResultBaseClassOrInterface();
setActionBaseClassOrInterface();
addAnnotatedInAndOutParams();
generateDto(actionClass, MpvUtil.toProperties(inParams.values()));
generateDto(resultClass, MpvUtil.toProperties(outParams.values()));
makeUiCommandIfOnClasspath();
}
private void makeUiCommandIfOnClasspath() {
if (env.getElementUtils().getTypeElement("org.tessell.model.commands.DispatchUiCommand") == null) {
return;
}
if (!dispatchPackageName.contains("org.tessell")) {
return;
}
GClass command = new GClass(simpleName + "Command" + generics.varsWithBounds);
command.setAbstract().baseClassName(
"org.tessell.model.commands.DispatchUiCommand<{}Action{}, {}Result{}>",
simpleName,
generics.vars,
simpleName,
generics.vars);
command.getConstructor(arg("org.tessell.dispatch.client.util.OutstandingDispatchAsync", "async")).body.line("super(async);");
String actionWithoutBounds = simpleName + "Action" + generics.vars;
// if no arguments to the action, just implement createAction for the user
if (inParams.size() == 0) {
GMethod createAction = command.getMethod("createAction").setProtected().returnType(actionWithoutBounds);
createAction.addAnnotation("@Override");
createAction.body.line("return new {}();", actionWithoutBounds);
} else {
// add createAction helper method with all the incoming params (to avoid a long new Xxx(...) call)
GMethod createActionHelper = command.getMethod("createAction", asArguments(inParams.values())).setProtected().returnType(actionWithoutBounds);
createActionHelper.body.line("return new {}({});", actionWithoutBounds, Join.commaSpace(asNames(inParams.values())));
// add an execute overload that will make an action
GMethod executeOverload = command.getMethod("execute", asArguments(inParams.values()));
executeOverload.body.line("doExecute(createAction({}));", Join.commaSpace(asNames(inParams.values())));
}
PropUtil.addGenerated(command, DispatchGenerator.class);
Util.saveCode(env, command, element);
}
private void addAnnotatedInAndOutParams() {
for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) {
In in = field.getAnnotation(In.class);
Out out = field.getAnnotation(Out.class);
if (in != null) {
addInParam(field, in);
} else if (out != null) {
addOutParam(field, out);
} else {
env.getMessager().printMessage(Kind.ERROR, field.getSimpleName().toString() + " must be annotated with @In or @Out", field);
}
}
}
private void addInParam(VariableElement field, In in) {
if (inParams.containsKey(in.value())) {
env.getMessager().printMessage(Kind.ERROR, field.getSimpleName().toString() + " reuses an order value", field);
} else {
inParams.put(in.value(), field);
}
}
private void addOutParam(VariableElement field, Out out) {
if (outParams.containsKey(out.value())) {
env.getMessager().printMessage(Kind.ERROR, field.getSimpleName().toString() + " reuses an order value", field);
} else {
outParams.put(out.value(), field);
}
}
private void setActionBaseClassOrInterface() {
GenDispatch genDispatch = element.getAnnotation(GenDispatch.class);
if (genDispatch.baseAction() != null && genDispatch.baseAction().length() > 0) {
this.actionClass.baseClassName("{}<{}>", genDispatch.baseAction(), simpleName + "Result" + generics.vars);
} else {
this.actionClass.implementsInterface("{}.Action<{}>", dispatchPackageName, simpleName + "Result" + generics.vars);
}
}
private void setResultBaseClassOrInterface() {
GenDispatch genDispatch = element.getAnnotation(GenDispatch.class);
if (genDispatch.baseResult() != null && genDispatch.baseResult().length() > 0) {
this.resultClass.baseClassName(genDispatch.baseResult());
} else {
this.resultClass.implementsInterface("{}.Result", dispatchPackageName);
}
}
private void generateDto(GClass gclass, List<Prop> properties) {
PropUtil.addGenerated(gclass, DispatchGenerator.class);
// move to GClass as a utility method
GMethod cstr = gclass.getConstructor();
for (Prop p : properties) {
addFieldAndGetterAndConstructorArg(gclass, cstr, p.name, p.type);
}
if (properties.size() > 0) {
// re-add the default constructor for serialization
gclass.getConstructor().setProtected();
}
PropUtil.addHashCode(gclass, properties);
PropUtil.addEquals(gclass, generics, properties);
PropUtil.addToString(gclass, properties);
Util.saveCode(env, gclass);
}
private String detectDispatchBasePackage(ProcessingEnvironment env) {
String dispatchBasePackage = env.getOptions().get("dispatchBasePackage");
if (dispatchBasePackage != null) {
return dispatchBasePackage;
}
for (String option : new String[] {
"org.tessell.dispatch.shared.Action",
"net.customware.gwt.dispatch.shared.Action",
"com.gwtplatform.dispatch.shared.Action" }) {
TypeElement t = env.getElementUtils().getTypeElement(option);
if (t != null) {
return ((PackageElement) t.getEnclosingElement()).getQualifiedName().toString();
}
}
return "org.tessell.dispatch.shared";
}
private void addFieldAndGetterAndConstructorArg(GClass gclass, GMethod cstr, String name, String type) {
gclass.getField(name).type(type);
gclass.getMethod("get" + Util.upper(name)).returnType(type).body.append("return this.{};", name);
cstr.argument(type, name);
cstr.body.line("this.{} = {};", name, name);
}
private static List<Argument> asArguments(Collection<VariableElement> elements) {
return Copy.list(elements).map(new Function1<Argument, VariableElement>() {
public Argument apply(VariableElement p1) {
return new Argument(p1.asType().toString(), p1.getSimpleName().toString());
}
});
}
private static List<String> asNames(Collection<VariableElement> elements) {
return Copy.list(elements).map(new Function1<String, VariableElement>() {
public String apply(VariableElement p1) {
return p1.getSimpleName().toString();
}
});
}
}