package org.tessell.processor; import static joist.sourcegen.Argument.arg; import java.util.ArrayList; 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.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic.Kind; import joist.sourcegen.GClass; import joist.sourcegen.GMethod; import joist.util.Copy; 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.GenEvent; import org.tessell.Param; public class EventGenerator { private final ProcessingEnvironment env; private final TypeElement element; private final GClass eventClass; private final GClass handlerClass; private final GenEvent eventSpec; private final String handlerName; private final GenericSuffix generics; private final List<Prop> properties; public EventGenerator(ProcessingEnvironment env, TypeElement element, GenEvent eventSpec) throws InvalidTypeElementException { if (!element.toString().endsWith("EventSpec")) { env.getMessager().printMessage(Kind.ERROR, "GenEvent target must end with a suffix EventSpec", element); throw new InvalidTypeElementException(); } this.env = env; this.element = element; this.generics = new GenericSuffix(element); this.eventClass = new GClass(element.toString().replaceAll("Spec$", "") + generics.varsWithBounds); this.eventSpec = eventSpec; this.handlerName = element.toString().replaceAll("EventSpec$", "Handler"); this.handlerClass = new GClass(handlerName + generics.varsWithBounds); if (eventSpec.gwtEvent()) { this.eventClass.baseClassName("com.google.gwt.event.shared.GwtEvent<{}>", handlerName + generics.vars); } else { this.eventClass.baseClassName("com.google.web.bindery.event.shared.Event<{}>", handlerName + generics.vars); } this.eventClass.addAnnotation("@SuppressWarnings(\"all\")"); this.properties = MpvUtil.toProperties(findParamsInOrder()); } public void generate() { generateHandlerClass(); generateType(); generateDispatch(); generateFields(); generateFire(); generateToDebugStringIfGwtEvent(); PropUtil.addEquals(eventClass, generics, properties); PropUtil.addHashCode(eventClass, properties); PropUtil.addToString(eventClass, properties); PropUtil.addGenerated(eventClass, DispatchGenerator.class); Util.saveCode(env, eventClass, element); Util.saveCode(env, handlerClass, element); } private void generateHandlerClass() { handlerClass.setInterface(); if (eventSpec.gwtEvent()) { handlerClass.baseClassName("com.google.gwt.event.shared.EventHandler"); } handlerClass.getMethod(getMethodName()).argument(eventClass.getFullName() + generics.vars, "event"); } private void generateType() { eventClass .getField("TYPE") .setStatic() .setPublic() .setFinal() .type("Type<{}>", handlerName + generics.varsAsStatic) .initialValue("new Type<{}>()", handlerName + generics.varsAsStatic); eventClass.getMethod("getType").setStatic().returnType("Type<{}>", handlerName + generics.varsAsStatic).body.append("return TYPE;"); GMethod associatedType = eventClass.getMethod("getAssociatedType"); associatedType.returnType("Type<{}>", handlerName + generics.vars).addAnnotation("@Override"); if (generics.vars.length() > 0) { associatedType.addAnnotation("@SuppressWarnings(\"all\")"); associatedType.body.line("return (Type) TYPE;"); } else { associatedType.body.line("return TYPE;"); } } private void generateDispatch() { eventClass.getMethod("dispatch").setProtected().addAnnotation("@Override").argument(handlerName + generics.vars, "handler").body.line( "handler.{}(this);", getMethodName()); } private void generateFields() { GMethod cstr = eventClass.getConstructor(); for (Prop p : properties) { eventClass.getField(p.name).type(p.type).setFinal(); eventClass.getMethod("get" + Util.upper(p.name)).returnType(p.type).body.append("return {};", p.name); cstr.argument(p.type, p.name); cstr.body.line("this.{} = {};", p.name, p.name); } } private void generateFire() { // add one fire method for each available event bus for (String bus : detectEventBuses(env)) { GMethod fire = eventClass.getMethod("fire", arg(bus, "eventBus")).setStatic(); if (generics.varsWithBounds.length() > 0) { fire.typeParameters(generics.varsWithBounds.substring(1, generics.varsWithBounds.length() - 1)); // ugly } List<String> args = new ArrayList<String>(); for (Prop p : properties) { fire.argument(p.type, p.name); args.add(p.name); } fire.body.line("eventBus.fireEvent(new {}({}));", eventClass.getSimpleName() + generics.vars, Join.commaSpace(args)); } } private void generateToDebugStringIfGwtEvent() { if (eventSpec.gwtEvent()) { // We already generate a nice toString, so just use that. GMethod toDebugString = eventClass.getMethod("toDebugString").returnType(String.class); toDebugString.body.line("return toString();"); } } private String getMethodName() { if (eventSpec.methodName().length() > 0) { return eventSpec.methodName(); } else { return "on" + element.getSimpleName().toString().replaceAll("EventSpec$", ""); } } private List<String> detectEventBuses(ProcessingEnvironment env) { List<String> available = new ArrayList<String>(); List<String> options = eventSpec.gwtEvent() ? Copy.list( "com.google.gwt.event.shared.HandlerManager", "com.google.gwt.event.shared.EventBus", "net.customware.gwt.presenter.client.EventBus", "com.gwtplatform.mvp.client.EventBus") : // Copy.list("com.google.web.bindery.event.shared.EventBus"); for (String option : options) { TypeElement t = env.getElementUtils().getTypeElement(option); if (t != null) { available.add(option); } } return available; } private Collection<VariableElement> findParamsInOrder() { Map<Integer, VariableElement> params = new TreeMap<Integer, VariableElement>(); for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) { Param param = field.getAnnotation(Param.class); if (param != null) { if (params.containsKey(param.value())) { env.getMessager().printMessage(Kind.ERROR, field.getSimpleName().toString() + " reuses an order value", field); } else { params.put(param.value(), field); } continue; } env.getMessager().printMessage(Kind.ERROR, field.getSimpleName().toString() + " must be annotated with @Param", field); } return params.values(); } }