package org.tessell.processor; import java.util.ArrayList; import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.tools.Diagnostic.Kind; import joist.sourcegen.Argument; import joist.sourcegen.GClass; import joist.sourcegen.GMethod; import joist.util.Join; import org.exigencecorp.aptutil.Util; import org.tessell.GenPlace; /** * Generates Place classes that wrap the boilerplate {@code GWT.runAsync} and call presenter static methods when requested. * * For example: * * <pre> * public class FooPresenter { * @GenPlace("foo") * public static void handleRequest(AppWideState state) { * // called when FooPlace is fired * state.doStuffToShowMe(); * } * } * </pre> * * Generates a {@code FooPlace} class that takes an {@code AppWideState} state * as its constructor, e.g.: * * <pre> * PlaceManager m = ...; * m.registerPlace(new FooPlace(appWideState)); * </pre> */ public class PlaceGenerator { private final ProcessingEnvironment env; private final ExecutableElement element; private final GenPlace place; private final GClass placeClass; private final GClass placeRequestClass; public PlaceGenerator(ProcessingEnvironment env, ExecutableElement element, GenPlace place) throws InvalidTypeElementException { if (!element.getModifiers().contains(Modifier.STATIC)) { env.getMessager().printMessage(Kind.ERROR, "GenPlace methods must be static", element); throw new InvalidTypeElementException(); } this.env = env; this.element = element; this.place = place; placeClass = new GClass(getPlaceQualifiedClassName()).baseClassName("org.tessell.place.Place"); placeRequestClass = new GClass(getPlaceRequestQualifiedClassName()).baseClassName("org.tessell.place.PlaceRequest"); } public void generate() { // PlaceClass GMethod cstr = placeClass.getConstructor(); addStaticPlaceName(); addStaticNewRequest(); addCstrSuperCall(cstr); addCstrStaticMethodArguments(cstr); addCstrFailureCallbackIfNeeded(cstr); if (place.async()) { addAsyncHandleRequest(); } else { addSyncHandleRequest(); } Util.saveCode(env, placeClass, element); // PlaceRequestClass addPlaceRequestCstrOne(); addPlaceRequestCstrTwo(); addPlaceRequestCstrThree(); addPlaceRequestParameters(); Util.saveCode(env, placeRequestClass, element); } private void addStaticPlaceName() { placeClass.getField("NAME").type("String").setPublic().setStatic().setFinal().initialValue("\"{}\"", place.name()); } private void addStaticNewRequest() { GMethod m = placeClass.getMethod("newRequest").setStatic().returnType(placeRequestClass.getSimpleName()); m.body.line("return new {}();", placeRequestClass.getSimpleName()); } private void addCstrSuperCall(GMethod cstr) { cstr.body.line("super(NAME);"); } private void addCstrStaticMethodArguments(GMethod cstr) { // any of the static method arguments become constructor arguments for (VariableElement param : element.getParameters()) { String paramName = param.getSimpleName().toString(); String paramType = param.asType().toString(); // this isn't a static argument if (paramType.equals("org.tessell.place.PlaceRequest")) { continue; } placeClass.getField(paramName).type(paramType).setFinal(); cstr.argument(paramType, paramName); cstr.body.line("this.{} = {};", paramName, paramName); } } private void addCstrFailureCallbackIfNeeded(GMethod cstr) { if (place.async()) { placeClass.getField("failureCallback").type("org.tessell.util.FailureCallback").setFinal(); cstr.argument("org.tessell.util.FailureCallback", "failureCallback"); cstr.body.line("this.{} = {};", "failureCallback", "failureCallback"); } } private void addAsyncHandleRequest() { GMethod m = placeClass.getMethod("handleRequest").argument("final org.tessell.place.PlaceRequest", "request"); m.body.line("GWT.runAsync(new RunAsyncCallback() {"); m.body.line(" public void onSuccess() {"); m.body.line(" if (request == null) {"); m.body.line(" return; // prefetching"); m.body.line(" }"); m.body.line(" {}.{}({});", getPresenterClassName(), getMethodName(), Join.commaSpace(getMethodParamNames())); m.body.line(" }"); m.body.line(""); m.body.line(" public void onFailure(Throwable caught) {"); m.body.line(" failureCallback.onFailure(caught);"); m.body.line(" }"); m.body.line("});"); placeClass.addImports("com.google.gwt.core.client.GWT", "com.google.gwt.core.client.RunAsyncCallback"); } private void addSyncHandleRequest() { GMethod m = placeClass.getMethod("handleRequest").argument("final org.tessell.place.PlaceRequest", "request"); m.body.line("if (request == null) {"); m.body.line(" return; // prefetching (not needed, just for consistency)"); m.body.line("}"); m.body.line("{}.{}({});", getPresenterClassName(), getMethodName(), Join.commaSpace(getMethodParamNames())); } private List<String> getMethodParamNames() { List<String> paramNames = new ArrayList<String>(); for (VariableElement param : element.getParameters()) { paramNames.add(param.getSimpleName().toString()); } return paramNames; } private String getMethodName() { return element.getSimpleName().toString(); } private String getPresenterClassName() { return ((TypeElement) element.getEnclosingElement()).getSimpleName().toString(); } private String getPlaceQualifiedClassName() { return ((TypeElement) element.getEnclosingElement()).getQualifiedName().toString().replace("Presenter", "Place"); } private String getPlaceRequestQualifiedClassName() { return ((TypeElement) element.getEnclosingElement()).getQualifiedName().toString().replace("Presenter", "PlaceRequest"); } private void addPlaceRequestCstrOne() { GMethod cstr = placeRequestClass.getConstructor(Argument.arg("PlaceRequest", "request")); cstr.body.line("super(request);"); cstr.body.line("if (!{}.NAME.equals(request.getName())) {", placeClass.getSimpleName()); cstr.body.line(" throw new IllegalArgumentException(\"Wrong place for class: \" + request.getName());"); cstr.body.line("}"); } private void addPlaceRequestCstrTwo() { GMethod cstr = placeRequestClass.getConstructor(); cstr.body.line("super({}.NAME);", placeClass.getSimpleName()); } private void addPlaceRequestCstrThree() { if (place.params() == null || place.params().length == 0) { return; } GMethod cstr = placeRequestClass.getConstructor(// Argument.arg(placeRequestClass.getSimpleName(), "request"), Argument.arg("String", "param"), Argument.arg("String", "value")).setPrivate(); cstr.body.line("super(request, param, value);"); } private void addPlaceRequestParameters() { if (place.params() == null) { return; } for (String param : place.params()) { GMethod getter = placeRequestClass.getMethod(param).returnType("String"); getter.body.line("return getParameter(\"{}\", null);", param); GMethod getterWithDefault = placeRequestClass.getMethod(param + "Or", Argument.arg("String", "def")).returnType("String"); getterWithDefault.body.line("return getParameter(\"{}\", def);", param); GMethod setter = placeRequestClass.getMethod(param, Argument.arg("Object", "value")).returnType(placeRequestClass.getSimpleName()); setter.body.line("return new {}(this, \"{}\", ObjectUtils.toStr(value, \"\"));", placeRequestClass.getSimpleName(), param); placeRequestClass.addImports("org.tessell.util.ObjectUtils"); } } }