diff -r c50c541368f8 -r 1adce93fea0f javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Wed Jan 16 12:27:53 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Wed Jan 23 12:53:23 2013 +0100 @@ -22,9 +22,13 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Completion; @@ -39,12 +43,16 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.MirroredTypeException; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; import org.apidesign.bck2brwsr.htmlpage.api.On; import org.apidesign.bck2brwsr.htmlpage.api.Page; +import org.apidesign.bck2brwsr.htmlpage.api.Property; import org.openide.util.lookup.ServiceProvider; /** Annotation processor to process an XHTML page and generate appropriate @@ -89,19 +97,44 @@ try { w.append("package " + pkg + ";\n"); w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n"); - w.append("class ").append(className).append(" {\n"); + w.append("final class ").append(className).append(" {\n"); + w.append(" private boolean locked;\n"); + if (!initializeOnClick(className, (TypeElement) e, w, pp)) { + return false; + } for (String id : pp.ids()) { String tag = pp.tagNameForId(id); String type = type(tag); - w.append(" ").append("public static final "). + w.append(" ").append("public final "). append(type).append(' ').append(cnstnt(id)).append(" = new "). append(type).append("(\"").append(id).append("\");\n"); } - w.append(" static {\n"); - if (!initializeOnClick(pe, w, pp)) { - return false; + List propsGetSet = new ArrayList(); + Map> propsDeps = new HashMap>(); + generateComputedProperties(w, e.getEnclosedElements(), propsGetSet, propsDeps); + generateProperties(w, p.properties(), propsGetSet, propsDeps); + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n"); + if (!propsGetSet.isEmpty()) { + w.write("public " + className + " applyBindings() {\n"); + w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings("); + w.write(className + ".class, this, "); + w.write("new String[] {\n"); + String sep = ""; + for (String n : propsGetSet) { + w.write(sep); + if (n == null) { + w.write(" null"); + } else { + w.write(" \"" + n + "\""); + } + sep = ",\n"; + } + w.write("\n });\n return this;\n}\n"); + + w.write("public void triggerEvent(Element e, OnEvent ev) {\n"); + w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n"); + w.write("}\n"); } - w.append(" }\n"); w.append("}\n"); } finally { w.close(); @@ -134,21 +167,31 @@ if (tag.equals("input")) { return "Input"; } + if (tag.equals("canvas")) { + return "Canvas"; + } + if (tag.equals("img")) { + return "Image"; + } return "Element"; } private static String cnstnt(String id) { - return id.toUpperCase(Locale.ENGLISH).replace('.', '_'); + return id.toUpperCase(Locale.ENGLISH).replace('.', '_').replace('-', '_'); } - private boolean initializeOnClick(PackageElement pe, Writer w, ProcessPage pp) throws IOException { + private boolean initializeOnClick( + String className, TypeElement type, Writer w, ProcessPage pp + ) throws IOException { TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); - for (Element clazz : pe.getEnclosedElements()) { - if (clazz.getKind() != ElementKind.CLASS) { - continue; - } - TypeElement type = (TypeElement)clazz; - for (Element method : clazz.getEnclosedElements()) { + { //for (Element clazz : pe.getEnclosedElements()) { + // if (clazz.getKind() != ElementKind.CLASS) { + // continue; + // } + w.append(" public ").append(className).append("() {\n"); + StringBuilder dispatch = new StringBuilder(); + int dispatchCnt = 0; + for (Element method : type.getEnclosedElements()) { On oc = method.getAnnotation(On.class); if (oc != null) { for (String id : oc.id()) { @@ -157,15 +200,33 @@ return false; } ExecutableElement ee = (ExecutableElement)method; - boolean hasParam; - if (ee.getParameters().isEmpty()) { - hasParam = false; - } else { - if (ee.getParameters().size() != 1 || ee.getParameters().get(0).asType() != stringType) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method should either have no arguments or one String argument", ee); + StringBuilder params = new StringBuilder(); + { + boolean first = true; + for (VariableElement ve : ee.getParameters()) { + if (!first) { + params.append(", "); + } + first = false; + if (ve.asType() == stringType) { + params.append('"').append(id).append('"'); + continue; + } + String rn = ve.asType().toString(); + int last = rn.lastIndexOf('.'); + if (last >= 0) { + rn = rn.substring(last + 1); + } + if (rn.equals(className)) { + params.append(className).append(".this"); + continue; + } + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@On method can only accept String or " + className + " arguments", + ee + ); return false; } - hasParam = true; } if (!ee.getModifiers().contains(Modifier.STATIC)) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee); @@ -176,17 +237,33 @@ return false; } w.append(" OnEvent." + oc.event()).append(".of(").append(cnstnt(id)). - append(").perform(new Runnable() { public void run() {\n"); - w.append(" ").append(type.getSimpleName().toString()). - append('.').append(ee.getSimpleName()).append("("); - if (hasParam) { - w.append("\"").append(id).append("\""); - } - w.append(");\n"); - w.append(" }});\n"); - } + append(").perform(new OnDispatch(" + dispatchCnt + "));\n"); + + dispatch. + append(" case ").append(dispatchCnt).append(": "). + append(type.getSimpleName().toString()). + append('.').append(ee.getSimpleName()).append("("). + append(params). + append("); break;\n"); + + dispatchCnt++; + } } } + w.append(" }\n"); + if (dispatchCnt > 0) { + w.append("class OnDispatch implements Runnable {\n"); + w.append(" private final int dispatch;\n"); + w.append(" OnDispatch(int d) { dispatch = d; }\n"); + w.append(" public void run() {\n"); + w.append(" switch (dispatch) {\n"); + w.append(dispatch); + w.append(" }\n"); + w.append(" }\n"); + w.append("}\n"); + } + + } return true; } @@ -233,4 +310,126 @@ } return e.getEnclosingElement(); } + + private static void generateProperties( + Writer w, Property[] properties, Collection props, + Map> deps + ) throws IOException { + for (Property p : properties) { + final String tn = typeName(p); + String[] gs = toGetSet(p.name(), tn); + + w.write("private " + tn + " prop_" + p.name() + ";\n"); + w.write("public " + tn + " " + gs[0] + "() {\n"); + w.write(" if (locked) throw new IllegalStateException();\n"); + w.write(" return prop_" + p.name() + ";\n"); + w.write("}\n"); + w.write("public void " + gs[1] + "(" + tn + " v) {\n"); + w.write(" if (locked) throw new IllegalStateException();\n"); + w.write(" prop_" + p.name() + " = v;\n"); + w.write(" if (ko != null) {\n"); + w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n"); + final Collection dependants = deps.get(p.name()); + if (dependants != null) { + for (String depProp : dependants) { + w.write(" ko.valueHasMutated(\"" + depProp + "\");\n"); + } + } + w.write(" }\n"); + w.write("}\n"); + + props.add(p.name()); + props.add(gs[2]); + props.add(gs[3]); + props.add(gs[0]); + } + } + + private boolean generateComputedProperties( + Writer w, Collection arr, Collection props, + Map> deps + ) throws IOException { + for (Element e : arr) { + if (e.getKind() != ElementKind.METHOD) { + continue; + } + if (e.getAnnotation(ComputedProperty.class) == null) { + continue; + } + ExecutableElement ee = (ExecutableElement)e; + final String tn = ee.getReturnType().toString(); + final String sn = ee.getSimpleName().toString(); + String[] gs = toGetSet(sn, tn); + + w.write("public " + tn + " " + gs[0] + "() {\n"); + w.write(" if (locked) throw new IllegalStateException();\n"); + int arg = 0; + for (VariableElement pe : ee.getParameters()) { + final String dn = pe.getSimpleName().toString(); + final String dt = pe.asType().toString(); + String[] call = toGetSet(dn, dt); + w.write(" " + dt + " arg" + (++arg) + " = "); + w.write(call[0] + "();\n"); + + Collection depends = deps.get(dn); + if (depends == null) { + depends = new LinkedHashSet(); + deps.put(dn, depends); + } + depends.add(sn); + } + w.write(" try {\n"); + w.write(" locked = true;\n"); + w.write(" return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "("); + String sep = ""; + for (int i = 1; i <= arg; i++) { + w.write(sep); + w.write("arg" + i); + sep = ", "; + } + w.write(");\n"); + w.write(" } finally {\n"); + w.write(" locked = false;\n"); + w.write(" }\n"); + w.write("}\n"); + + props.add(e.getSimpleName().toString()); + props.add(gs[2]); + props.add(null); + props.add(gs[0]); + } + + return true; + } + + private static String[] toGetSet(String name, String type) { + String n = Character.toUpperCase(name.charAt(0)) + name.substring(1); + String bck2brwsrType = "L" + type.replace('.', '_') + "_2"; + if ("int".equals(type)) { + bck2brwsrType = "I"; + } + if ("double".equals(type)) { + bck2brwsrType = "D"; + } + String pref = "get"; + if ("boolean".equals(type)) { + pref = "is"; + bck2brwsrType = "Z"; + } + final String nu = n.replace('.', '_'); + return new String[]{ + pref + n, + "set" + n, + pref + nu + "__" + bck2brwsrType, + "set" + nu + "__V" + bck2brwsrType + }; + } + + private static String typeName(Property p) { + try { + return p.type().getName(); + } catch (MirroredTypeException ex) { + return ex.getTypeMirror().toString(); + } + } }