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