javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Sun, 31 Mar 2013 06:46:25 +0200
branchmodel
changeset 908 3e023bea2da4
parent 907 5dc21ce7269d
child 909 e51a474fcf79
permissions -rw-r--r--
Exposing properties as functions
     1 /**
     2  * Back 2 Browser Bytecode Translator
     3  * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, version 2 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. Look for COPYING file in the top folder.
    16  * If not, see http://opensource.org/licenses/GPL-2.0.
    17  */
    18 package org.apidesign.bck2brwsr.htmlpage;
    19 
    20 import java.io.IOException;
    21 import java.io.InputStream;
    22 import java.io.OutputStreamWriter;
    23 import java.io.StringWriter;
    24 import java.io.Writer;
    25 import java.util.ArrayList;
    26 import java.util.Collection;
    27 import java.util.Collections;
    28 import java.util.HashMap;
    29 import java.util.LinkedHashSet;
    30 import java.util.List;
    31 import java.util.Map;
    32 import java.util.Set;
    33 import java.util.WeakHashMap;
    34 import javax.annotation.processing.AbstractProcessor;
    35 import javax.annotation.processing.Completion;
    36 import javax.annotation.processing.Completions;
    37 import javax.annotation.processing.Processor;
    38 import javax.annotation.processing.RoundEnvironment;
    39 import javax.annotation.processing.SupportedAnnotationTypes;
    40 import javax.lang.model.element.AnnotationMirror;
    41 import javax.lang.model.element.Element;
    42 import javax.lang.model.element.ElementKind;
    43 import javax.lang.model.element.ExecutableElement;
    44 import javax.lang.model.element.Modifier;
    45 import javax.lang.model.element.PackageElement;
    46 import javax.lang.model.element.TypeElement;
    47 import javax.lang.model.element.VariableElement;
    48 import javax.lang.model.type.MirroredTypeException;
    49 import javax.lang.model.type.TypeKind;
    50 import javax.lang.model.type.TypeMirror;
    51 import javax.lang.model.util.Types;
    52 import javax.tools.Diagnostic;
    53 import javax.tools.FileObject;
    54 import javax.tools.StandardLocation;
    55 import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
    56 import org.apidesign.bck2brwsr.htmlpage.api.Model;
    57 import org.apidesign.bck2brwsr.htmlpage.api.On;
    58 import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
    59 import org.apidesign.bck2brwsr.htmlpage.api.Page;
    60 import org.apidesign.bck2brwsr.htmlpage.api.Property;
    61 import org.openide.util.lookup.ServiceProvider;
    62 
    63 /** Annotation processor to process an XHTML page and generate appropriate 
    64  * "id" file.
    65  *
    66  * @author Jaroslav Tulach <jtulach@netbeans.org>
    67  */
    68 @ServiceProvider(service=Processor.class)
    69 @SupportedAnnotationTypes({
    70     "org.apidesign.bck2brwsr.htmlpage.api.Model",
    71     "org.apidesign.bck2brwsr.htmlpage.api.Page",
    72     "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
    73     "org.apidesign.bck2brwsr.htmlpage.api.On"
    74 })
    75 public final class PageProcessor extends AbstractProcessor {
    76     private final Map<Element,String> models = new WeakHashMap<>();
    77     @Override
    78     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    79         boolean ok = true;
    80         for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
    81             if (!processModel(e)) {
    82                 ok = false;
    83             }
    84         }
    85         for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
    86             if (!processPage(e)) {
    87                 ok = false;
    88             }
    89         }
    90         if (roundEnv.processingOver()) {
    91             models.clear();
    92         }
    93         return ok;
    94     }
    95 
    96     private InputStream openStream(String pkg, String name) throws IOException {
    97         try {
    98             FileObject fo = processingEnv.getFiler().getResource(
    99                 StandardLocation.SOURCE_PATH, pkg, name);
   100             return fo.openInputStream();
   101         } catch (IOException ex) {
   102             return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
   103         }
   104     }
   105     
   106     private boolean processModel(Element e) {
   107         boolean ok = true;
   108         Model m = e.getAnnotation(Model.class);
   109         if (m == null) {
   110             return true;
   111         }
   112         String pkg = findPkgName(e);
   113         Writer w;
   114         String className = m.className();
   115         try {
   116             StringWriter body = new StringWriter();
   117             List<String> propsGetSet = new ArrayList<>();
   118             Map<String, Collection<String>> propsDeps = new HashMap<>();
   119             if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   120                 ok = false;
   121             }
   122             if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps)) {
   123                 ok = false;
   124             }
   125             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   126             w = new OutputStreamWriter(java.openOutputStream());
   127             try {
   128                 w.append("package " + pkg + ";\n");
   129                 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   130                 w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   131                 w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
   132                 w.append("final class ").append(className).append(" {\n");
   133                 w.append("  private Object json;\n");
   134                 w.append("  private boolean locked;\n");
   135                 w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   136                 w.append(body.toString());
   137                 w.append("  private static Class<" + e.getSimpleName() + "> modelFor() { return null; }\n");
   138                 for (int i = 0; i < propsGetSet.size(); i += 4) {
   139                     w.append("  @JavaScriptOnly(name=\"" + propsGetSet.get(i) + "\",\n");
   140                     w.append("    value=\"function() { ");
   141                     final String setter = propsGetSet.get(i + 2);
   142                     if (setter != null) {
   143                         w.append("if (arguments.length == 1) this." + setter + "(arguments[0]); ");
   144                     }
   145                     w.append("return this." + propsGetSet.get(i + 1) + "();}\")\n");
   146                     w.append("  private static native void __accessor" + i + "();");
   147                 }
   148                 w.append("}\n");
   149             } finally {
   150                 w.close();
   151             }
   152         } catch (IOException ex) {
   153             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   154             return false;
   155         }
   156         return ok;
   157     }
   158     
   159     private boolean processPage(Element e) {
   160         boolean ok = true;
   161         Page p = e.getAnnotation(Page.class);
   162         if (p == null) {
   163             return true;
   164         }
   165         String pkg = findPkgName(e);
   166 
   167         ProcessPage pp;
   168         try (InputStream is = openStream(pkg, p.xhtml())) {
   169             pp = ProcessPage.readPage(is);
   170             is.close();
   171         } catch (IOException iOException) {
   172             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
   173             ok = false;
   174             pp = null;
   175         }
   176         Writer w;
   177         String className = p.className();
   178         if (className.isEmpty()) {
   179             int indx = p.xhtml().indexOf('.');
   180             className = p.xhtml().substring(0, indx);
   181         }
   182         try {
   183             StringWriter body = new StringWriter();
   184             List<String> propsGetSet = new ArrayList<>();
   185             List<String> functions = new ArrayList<>();
   186             Map<String, Collection<String>> propsDeps = new HashMap<>();
   187             if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   188                 ok = false;
   189             }
   190             if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps)) {
   191                 ok = false;
   192             }
   193             if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   194                 ok = false;
   195             }
   196             
   197             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   198             w = new OutputStreamWriter(java.openOutputStream());
   199             try {
   200                 w.append("package " + pkg + ";\n");
   201                 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   202                 w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   203                 w.append("final class ").append(className).append(" {\n");
   204                 w.append("  private boolean locked;\n");
   205                 if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
   206                     ok = false;
   207                 } else {
   208                     for (String id : pp.ids()) {
   209                         String tag = pp.tagNameForId(id);
   210                         String type = type(tag);
   211                         w.append("  ").append("public final ").
   212                             append(type).append(' ').append(cnstnt(id)).append(" = new ").
   213                             append(type).append("(\"").append(id).append("\");\n");
   214                     }
   215                 }
   216                 w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   217                 w.append(body.toString());
   218                 if (!propsGetSet.isEmpty()) {
   219                     w.write("public " + className + " applyBindings() {\n");
   220                     w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
   221                     w.write(className + ".class, this, ");
   222                     w.write("new String[] {\n");
   223                     String sep = "";
   224                     for (String n : propsGetSet) {
   225                         w.write(sep);
   226                         if (n == null) {
   227                             w.write("    null");
   228                         } else {
   229                             w.write("    \"" + n + "\"");
   230                         }
   231                         sep = ",\n";
   232                     }
   233                     w.write("\n  }, new String[] {\n");
   234                     sep = "";
   235                     for (String n : functions) {
   236                         w.write(sep);
   237                         w.write(n);
   238                         sep = ",\n";
   239                     }
   240                     w.write("\n  });\n  return this;\n}\n");
   241 
   242                     w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
   243                     w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
   244                     w.write("}\n");
   245                 }
   246                 w.append("}\n");
   247             } finally {
   248                 w.close();
   249             }
   250         } catch (IOException ex) {
   251             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   252             return false;
   253         }
   254         return ok;
   255     }
   256 
   257     private static String type(String tag) {
   258         if (tag.equals("title")) {
   259             return "Title";
   260         }
   261         if (tag.equals("button")) {
   262             return "Button";
   263         }
   264         if (tag.equals("input")) {
   265             return "Input";
   266         }
   267         if (tag.equals("canvas")) {
   268             return "Canvas";
   269         }
   270         if (tag.equals("img")) {
   271             return "Image";
   272         }
   273         return "Element";
   274     }
   275 
   276     private static String cnstnt(String id) {
   277         return id.replace('.', '_').replace('-', '_');
   278     }
   279 
   280     private boolean initializeOnClick(
   281         String className, TypeElement type, Writer w, ProcessPage pp
   282     ) throws IOException {
   283         boolean ok = true;
   284         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   285         { //for (Element clazz : pe.getEnclosedElements()) {
   286           //  if (clazz.getKind() != ElementKind.CLASS) {
   287             //    continue;
   288            // }
   289             w.append("  public ").append(className).append("() {\n");
   290             StringBuilder dispatch = new StringBuilder();
   291             int dispatchCnt = 0;
   292             for (Element method : type.getEnclosedElements()) {
   293                 On oc = method.getAnnotation(On.class);
   294                 if (oc != null) {
   295                     for (String id : oc.id()) {
   296                         if (pp == null) {
   297                             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   298                             ok = false;
   299                             continue;
   300                         }
   301                         if (pp.tagNameForId(id) == null) {
   302                             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   303                             ok = false;
   304                             continue;
   305                         }
   306                         ExecutableElement ee = (ExecutableElement)method;
   307                         CharSequence params = wrapParams(ee, id, className, "ev", null);
   308                         if (!ee.getModifiers().contains(Modifier.STATIC)) {
   309                             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   310                             ok = false;
   311                             continue;
   312                         }
   313                         if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   314                             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   315                             ok = false;
   316                             continue;
   317                         }
   318                         w.append("  OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
   319                             append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
   320 
   321                         dispatch.
   322                             append("      case ").append(dispatchCnt).append(": ").
   323                             append(type.getSimpleName().toString()).
   324                             append('.').append(ee.getSimpleName()).append("(").
   325                             append(params).
   326                             append("); break;\n");
   327                         
   328                         dispatchCnt++;
   329                     }
   330                 }
   331             }
   332             w.append("  }\n");
   333             if (dispatchCnt > 0) {
   334                 w.append("class OnDispatch implements OnHandler {\n");
   335                 w.append("  private final int dispatch;\n");
   336                 w.append("  OnDispatch(int d) { dispatch = d; }\n");
   337                 w.append("  public void onEvent(Object ev) {\n");
   338                 w.append("    switch (dispatch) {\n");
   339                 w.append(dispatch);
   340                 w.append("    }\n");
   341                 w.append("  }\n");
   342                 w.append("}\n");
   343             }
   344             
   345 
   346         }
   347         return ok;
   348     }
   349 
   350     @Override
   351     public Iterable<? extends Completion> getCompletions(
   352         Element element, AnnotationMirror annotation, 
   353         ExecutableElement member, String userText
   354     ) {
   355         if (!userText.startsWith("\"")) {
   356             return Collections.emptyList();
   357         }
   358         
   359         Element cls = findClass(element);
   360         Page p = cls.getAnnotation(Page.class);
   361         String pkg = findPkgName(cls);
   362         ProcessPage pp;
   363         try {
   364             InputStream is = openStream(pkg, p.xhtml());
   365             pp = ProcessPage.readPage(is);
   366             is.close();
   367         } catch (IOException iOException) {
   368             return Collections.emptyList();
   369         }
   370         
   371         List<Completion> cc = new ArrayList<>();
   372         userText = userText.substring(1);
   373         for (String id : pp.ids()) {
   374             if (id.startsWith(userText)) {
   375                 cc.add(Completions.of("\"" + id + "\"", id));
   376             }
   377         }
   378         return cc;
   379     }
   380     
   381     private static Element findClass(Element e) {
   382         if (e == null) {
   383             return null;
   384         }
   385         Page p = e.getAnnotation(Page.class);
   386         if (p != null) {
   387             return e;
   388         }
   389         return e.getEnclosingElement();
   390     }
   391 
   392     private boolean generateProperties(
   393         Element where,
   394         Writer w, Property[] properties,
   395         Collection<String> props, Map<String,Collection<String>> deps
   396     ) throws IOException {
   397         boolean ok = true;
   398         for (Property p : properties) {
   399             final String tn;
   400             tn = typeName(where, p);
   401             String[] gs = toGetSet(p.name(), tn, p.array());
   402 
   403             if (p.array()) {
   404                 w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
   405                     + p.name() + "\"");
   406                 final Collection<String> dependants = deps.get(p.name());
   407                 if (dependants != null) {
   408                     for (String depProp : dependants) {
   409                         w.write(", ");
   410                         w.write('\"');
   411                         w.write(depProp);
   412                         w.write('\"');
   413                     }
   414                 }
   415                 w.write(");\n");
   416                 w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   417                 w.write("  if (locked) throw new IllegalStateException();\n");
   418                 w.write("  prop_" + p.name() + ".assign(ko);\n");
   419                 w.write("  return prop_" + p.name() + ";\n");
   420                 w.write("}\n");
   421             } else {
   422                 w.write("private " + tn + " prop_" + p.name() + ";\n");
   423                 w.write("public " + tn + " " + gs[0] + "() {\n");
   424                 w.write("  if (locked) throw new IllegalStateException();\n");
   425                 w.write("  return prop_" + p.name() + ";\n");
   426                 w.write("}\n");
   427                 w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   428                 w.write("  if (locked) throw new IllegalStateException();\n");
   429                 w.write("  prop_" + p.name() + " = v;\n");
   430                 w.write("  if (ko != null) {\n");
   431                 w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   432                 final Collection<String> dependants = deps.get(p.name());
   433                 if (dependants != null) {
   434                     for (String depProp : dependants) {
   435                         w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   436                     }
   437                 }
   438                 w.write("  }\n");
   439                 w.write("}\n");
   440             }
   441             
   442             props.add(p.name());
   443             props.add(gs[2]);
   444             props.add(gs[3]);
   445             props.add(gs[0]);
   446         }
   447         return ok;
   448     }
   449 
   450     private boolean generateComputedProperties(
   451         Writer w, Property[] fixedProps,
   452         Collection<? extends Element> arr, Collection<String> props,
   453         Map<String,Collection<String>> deps
   454     ) throws IOException {
   455         boolean ok = true;
   456         for (Element e : arr) {
   457             if (e.getKind() != ElementKind.METHOD) {
   458                 continue;
   459             }
   460             if (e.getAnnotation(ComputedProperty.class) == null) {
   461                 continue;
   462             }
   463             ExecutableElement ee = (ExecutableElement)e;
   464             final TypeMirror rt = ee.getReturnType();
   465             final Types tu = processingEnv.getTypeUtils();
   466             TypeMirror ert = tu.erasure(rt);
   467             String tn = ert.toString();
   468             boolean array = false;
   469             if (tn.equals("java.util.List")) {
   470                 array = true;
   471             }
   472             
   473             final String sn = ee.getSimpleName().toString();
   474             String[] gs = toGetSet(sn, tn, array);
   475             
   476             w.write("public " + tn + " " + gs[0] + "() {\n");
   477             w.write("  if (locked) throw new IllegalStateException();\n");
   478             int arg = 0;
   479             for (VariableElement pe : ee.getParameters()) {
   480                 final String dn = pe.getSimpleName().toString();
   481                 
   482                 if (!verifyPropName(pe, dn, fixedProps)) {
   483                     ok = false;
   484                 }
   485                 
   486                 final String dt = pe.asType().toString();
   487                 String[] call = toGetSet(dn, dt, false);
   488                 w.write("  " + dt + " arg" + (++arg) + " = ");
   489                 w.write(call[0] + "();\n");
   490                 
   491                 Collection<String> depends = deps.get(dn);
   492                 if (depends == null) {
   493                     depends = new LinkedHashSet<>();
   494                     deps.put(dn, depends);
   495                 }
   496                 depends.add(sn);
   497             }
   498             w.write("  try {\n");
   499             w.write("    locked = true;\n");
   500             w.write("    return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "(");
   501             String sep = "";
   502             for (int i = 1; i <= arg; i++) {
   503                 w.write(sep);
   504                 w.write("arg" + i);
   505                 sep = ", ";
   506             }
   507             w.write(");\n");
   508             w.write("  } finally {\n");
   509             w.write("    locked = false;\n");
   510             w.write("  }\n");
   511             w.write("}\n");
   512 
   513             props.add(e.getSimpleName().toString());
   514             props.add(gs[2]);
   515             props.add(null);
   516             props.add(gs[0]);
   517         }
   518         
   519         return ok;
   520     }
   521 
   522     private static String[] toGetSet(String name, String type, boolean array) {
   523         String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
   524         String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
   525         if ("int".equals(type)) {
   526             bck2brwsrType = "I";
   527         }
   528         if ("double".equals(type)) {
   529             bck2brwsrType = "D";
   530         }
   531         String pref = "get";
   532         if ("boolean".equals(type)) {
   533             pref = "is";
   534             bck2brwsrType = "Z";
   535         }
   536         final String nu = n.replace('.', '_');
   537         if (array) {
   538             return new String[] { 
   539                 "get" + n,
   540                 null,
   541                 "get" + nu + "__Ljava_util_List_2",
   542                 null
   543             };
   544         }
   545         return new String[]{
   546             pref + n, 
   547             "set" + n, 
   548             pref + nu + "__" + bck2brwsrType,
   549             "set" + nu + "__V" + bck2brwsrType
   550         };
   551     }
   552 
   553     private String typeName(Element where, Property p) {
   554         String ret;
   555         boolean isModel = false;
   556         try {
   557             ret = p.type().getName();
   558         } catch (MirroredTypeException ex) {
   559             TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror());
   560             final Element e = processingEnv.getTypeUtils().asElement(tm);
   561             final Model m = e == null ? null : e.getAnnotation(Model.class);
   562             if (m != null) {
   563                 ret = findPkgName(e) + '.' + m.className();
   564                 isModel = true;
   565                 models.put(e, m.className());
   566             } else {
   567                 ret = tm.toString();
   568             }
   569         }
   570         if (p.array()) {
   571             String bt = findBoxedType(ret);
   572             if (bt != null) {
   573                 return bt;
   574             }
   575         }
   576         if (!isModel && !"java.lang.String".equals(ret)) {
   577             String bt = findBoxedType(ret);
   578             if (bt == null) {
   579                 processingEnv.getMessager().printMessage(
   580                     Diagnostic.Kind.ERROR, 
   581                     "Only primitive types supported in the mapping. Not " + ret,
   582                     where
   583                 );
   584             }
   585         }
   586         return ret;
   587     }
   588     
   589     private static String findBoxedType(String ret) {
   590         if (ret.equals("boolean")) {
   591             return Boolean.class.getName();
   592         }
   593         if (ret.equals("byte")) {
   594             return Byte.class.getName();
   595         }
   596         if (ret.equals("short")) {
   597             return Short.class.getName();
   598         }
   599         if (ret.equals("char")) {
   600             return Character.class.getName();
   601         }
   602         if (ret.equals("int")) {
   603             return Integer.class.getName();
   604         }
   605         if (ret.equals("long")) {
   606             return Long.class.getName();
   607         }
   608         if (ret.equals("float")) {
   609             return Float.class.getName();
   610         }
   611         if (ret.equals("double")) {
   612             return Double.class.getName();
   613         }
   614         return null;
   615     }
   616 
   617     private boolean verifyPropName(Element e, String propName, Property[] existingProps) {
   618         StringBuilder sb = new StringBuilder();
   619         String sep = "";
   620         for (Property property : existingProps) {
   621             if (property.name().equals(propName)) {
   622                 return true;
   623             }
   624             sb.append(sep);
   625             sb.append('"');
   626             sb.append(property.name());
   627             sb.append('"');
   628             sep = ", ";
   629         }
   630         processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
   631             propName + " is not one of known properties: " + sb
   632             , e
   633         );
   634         return false;
   635     }
   636 
   637     private static String findPkgName(Element e) {
   638         for (;;) {
   639             if (e.getKind() == ElementKind.PACKAGE) {
   640                 return ((PackageElement)e).getQualifiedName().toString();
   641             }
   642             e = e.getEnclosingElement();
   643         }
   644     }
   645 
   646     private boolean generateFunctions(
   647         Element clazz, StringWriter body, String className, 
   648         List<? extends Element> enclosedElements, List<String> functions
   649     ) {
   650         for (Element m : enclosedElements) {
   651             if (m.getKind() != ElementKind.METHOD) {
   652                 continue;
   653             }
   654             ExecutableElement e = (ExecutableElement)m;
   655             OnFunction onF = e.getAnnotation(OnFunction.class);
   656             if (onF == null) {
   657                 continue;
   658             }
   659             if (!e.getModifiers().contains(Modifier.STATIC)) {
   660                 processingEnv.getMessager().printMessage(
   661                     Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e
   662                 );
   663                 return false;
   664             }
   665             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   666                 processingEnv.getMessager().printMessage(
   667                     Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e
   668                 );
   669                 return false;
   670             }
   671             if (e.getReturnType().getKind() != TypeKind.VOID) {
   672                 processingEnv.getMessager().printMessage(
   673                     Diagnostic.Kind.ERROR, "@OnFunction method should return void", e
   674                 );
   675                 return false;
   676             }
   677             String n = e.getSimpleName().toString();
   678             body.append("void ").append(n).append("(Object data, Object ev) {\n");
   679             body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   680             body.append(wrapParams(e, null, className, "ev", "data"));
   681             body.append(");\n");
   682             body.append("}\n");
   683             
   684             functions.add('\"' + n + '\"');
   685             functions.add('\"' + n + "__VLjava_lang_Object_2Ljava_lang_Object_2" + '\"');
   686         }
   687         return true;
   688     }
   689 
   690     private CharSequence wrapParams(
   691         ExecutableElement ee, String id, String className, String evName, String dataName
   692     ) {
   693         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   694         StringBuilder params = new StringBuilder();
   695         boolean first = true;
   696         for (VariableElement ve : ee.getParameters()) {
   697             if (!first) {
   698                 params.append(", ");
   699             }
   700             first = false;
   701             String toCall = null;
   702             if (ve.asType() == stringType) {
   703                 if (ve.getSimpleName().contentEquals("id")) {
   704                     params.append('"').append(id).append('"');
   705                     continue;
   706                 }
   707                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
   708             }
   709             if (ve.asType().getKind() == TypeKind.DOUBLE) {
   710                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
   711             }
   712             if (ve.asType().getKind() == TypeKind.INT) {
   713                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
   714             }
   715             if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
   716                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
   717             }
   718 
   719             if (toCall != null) {
   720                 params.append(toCall);
   721                 if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
   722                     params.append(dataName);
   723                     params.append(", null");
   724                 } else {
   725                     params.append(evName);
   726                     params.append(", \"");
   727                     params.append(ve.getSimpleName().toString());
   728                     params.append("\"");
   729                 }
   730                 params.append(")");
   731                 continue;
   732             }
   733             String rn = ve.asType().toString();
   734             int last = rn.lastIndexOf('.');
   735             if (last >= 0) {
   736                 rn = rn.substring(last + 1);
   737             }
   738             if (rn.equals(className)) {
   739                 params.append(className).append(".this");
   740                 continue;
   741             }
   742             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   743                 "@On method can only accept String named 'id' or " + className + " arguments",
   744                 ee
   745             );
   746         }
   747         return params;
   748     }
   749     
   750     private boolean isModel(TypeMirror tm) {
   751         final Element e = processingEnv.getTypeUtils().asElement(tm);
   752         if (e == null) {
   753             return false;
   754         }
   755         for (Element ch : e.getEnclosedElements()) {
   756             if (ch.getKind() == ElementKind.METHOD) {
   757                 ExecutableElement ee = (ExecutableElement)ch;
   758                 if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
   759                     return true;
   760                 }
   761             }
   762         }
   763         return models.values().contains(e.getSimpleName().toString());
   764     }
   765 }