javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Sun, 07 Apr 2013 21:41:57 +0200
branchmodel
changeset 944 1e2b0dcc8326
parent 943 28aae214c202
child 947 26f7eeb81aec
permissions -rw-r--r--
Can fetch JSON numbers in an array
     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.Messager;
    38 import javax.annotation.processing.Processor;
    39 import javax.annotation.processing.RoundEnvironment;
    40 import javax.annotation.processing.SupportedAnnotationTypes;
    41 import javax.lang.model.element.AnnotationMirror;
    42 import javax.lang.model.element.Element;
    43 import javax.lang.model.element.ElementKind;
    44 import javax.lang.model.element.ExecutableElement;
    45 import javax.lang.model.element.Modifier;
    46 import javax.lang.model.element.PackageElement;
    47 import javax.lang.model.element.TypeElement;
    48 import javax.lang.model.element.VariableElement;
    49 import javax.lang.model.type.ArrayType;
    50 import javax.lang.model.type.DeclaredType;
    51 import javax.lang.model.type.MirroredTypeException;
    52 import javax.lang.model.type.TypeKind;
    53 import javax.lang.model.type.TypeMirror;
    54 import javax.lang.model.util.Elements;
    55 import javax.lang.model.util.Types;
    56 import javax.tools.Diagnostic;
    57 import javax.tools.FileObject;
    58 import javax.tools.StandardLocation;
    59 import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
    60 import org.apidesign.bck2brwsr.htmlpage.api.Model;
    61 import org.apidesign.bck2brwsr.htmlpage.api.On;
    62 import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
    63 import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
    64 import org.apidesign.bck2brwsr.htmlpage.api.Page;
    65 import org.apidesign.bck2brwsr.htmlpage.api.Property;
    66 import org.openide.util.lookup.ServiceProvider;
    67 
    68 /** Annotation processor to process an XHTML page and generate appropriate 
    69  * "id" file.
    70  *
    71  * @author Jaroslav Tulach <jtulach@netbeans.org>
    72  */
    73 @ServiceProvider(service=Processor.class)
    74 @SupportedAnnotationTypes({
    75     "org.apidesign.bck2brwsr.htmlpage.api.Model",
    76     "org.apidesign.bck2brwsr.htmlpage.api.Page",
    77     "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
    78     "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
    79     "org.apidesign.bck2brwsr.htmlpage.api.On"
    80 })
    81 public final class PageProcessor extends AbstractProcessor {
    82     private final Map<Element,String> models = new WeakHashMap<>();
    83     @Override
    84     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    85         boolean ok = true;
    86         for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
    87             if (!processModel(e)) {
    88                 ok = false;
    89             }
    90         }
    91         for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
    92             if (!processPage(e)) {
    93                 ok = false;
    94             }
    95         }
    96         if (roundEnv.processingOver()) {
    97             models.clear();
    98         }
    99         return ok;
   100     }
   101 
   102     private InputStream openStream(String pkg, String name) throws IOException {
   103         try {
   104             FileObject fo = processingEnv.getFiler().getResource(
   105                 StandardLocation.SOURCE_PATH, pkg, name);
   106             return fo.openInputStream();
   107         } catch (IOException ex) {
   108             return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
   109         }
   110     }
   111 
   112     private  Messager err() {
   113         return processingEnv.getMessager();
   114     }
   115     
   116     private boolean processModel(Element e) {
   117         boolean ok = true;
   118         Model m = e.getAnnotation(Model.class);
   119         if (m == null) {
   120             return true;
   121         }
   122         String pkg = findPkgName(e);
   123         Writer w;
   124         String className = m.className();
   125         models.put(e, className);
   126         try {
   127             StringWriter body = new StringWriter();
   128             List<String> propsGetSet = new ArrayList<>();
   129             List<String> functions = new ArrayList<>();
   130             Map<String, Collection<String>> propsDeps = new HashMap<>();
   131             if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   132                 ok = false;
   133             }
   134             if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps)) {
   135                 ok = false;
   136             }
   137             if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   138                 ok = false;
   139             }
   140             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   141             w = new OutputStreamWriter(java.openOutputStream());
   142             try {
   143                 w.append("package " + pkg + ";\n");
   144                 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   145                 w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   146                 w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
   147                 w.append("final class ").append(className).append(" implements Cloneable {\n");
   148                 w.append("  private boolean locked;\n");
   149                 w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   150                 w.append(body.toString());
   151                 w.append("  private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
   152                 w.append("  public ").append(className).append("() {\n");
   153                 w.append("    intKnckt();\n");
   154                 w.append("  };\n");
   155                 w.append("  private void intKnckt() {\n");
   156                 w.append("    ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
   157                 writeStringArray(propsGetSet, w);
   158                 w.append(", ");
   159                 writeStringArray(functions, w);
   160                 w.append("    );\n");
   161                 w.append("  };\n");
   162                 w.append("  ").append(className).append("(Object json) {\n");
   163                 int values = 0;
   164                 for (int i = 0; i < propsGetSet.size(); i += 4) {
   165                     Property p = findProperty(m.properties(), propsGetSet.get(i));
   166                     if (p == null) {
   167                         continue;
   168                     }
   169                     values++;
   170                 }
   171                 w.append("    Object[] ret = new Object[" + values + "];\n");
   172                 w.append("    org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
   173                 for (int i = 0; i < propsGetSet.size(); i += 4) {
   174                     Property p = findProperty(m.properties(), propsGetSet.get(i));
   175                     if (p == null) {
   176                         continue;
   177                     }
   178                     w.append("      \"").append(propsGetSet.get(i)).append("\",\n");
   179                 }
   180                 w.append("    }, ret);\n");
   181                 for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
   182                     final String pn = propsGetSet.get(i);
   183                     Property p = findProperty(m.properties(), pn);
   184                     if (p == null) {
   185                         continue;
   186                     }
   187                     boolean[] isModel = { false };
   188                     boolean[] isEnum = { false };
   189                     String type = checkType(m.properties()[prop++], isModel, isEnum);
   190                     if (isEnum[0]) {
   191 //                        w.append(type).append(".valueOf((String)");
   192 //                        close = true;
   193                         w.append("    this.prop_").append(pn);
   194                         w.append(" = null;\n");
   195                     } else if (p.array()) {
   196                         w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
   197                         w.append("  for (Object e : ((Object[])ret[" + cnt + "])) {\n");
   198                         if (isModel[0]) {
   199                             w.append("    this.prop_").append(pn).append(".add(new ");
   200                             w.append(type).append("(e));\n");
   201                         } else {
   202                             if (isPrimitive(type)) {
   203                                 w.append("    this.prop_").append(pn).append(".add(((Number)e).");
   204                                 w.append(type).append("Value());\n");
   205                             } else {
   206                                 w.append("    this.prop_").append(pn).append(".add((");
   207                                 w.append(type).append(")e);\n");
   208                             }
   209                         }
   210                         w.append("  }\n");
   211                         w.append("}\n");
   212                     } else {
   213                         w.append("    this.prop_").append(pn);
   214                         w.append(" = (").append(type).append(')');
   215                         w.append("ret[" + cnt + "];\n");
   216                     }
   217                     cnt++;
   218                 }
   219                 w.append("    intKnckt();\n");
   220                 w.append("  };\n");
   221                 writeToString(m.properties(), w);
   222                 writeClone(className, m.properties(), w);
   223                 w.append("}\n");
   224             } finally {
   225                 w.close();
   226             }
   227         } catch (IOException ex) {
   228             err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   229             return false;
   230         }
   231         return ok;
   232     }
   233     
   234     private boolean processPage(Element e) {
   235         boolean ok = true;
   236         Page p = e.getAnnotation(Page.class);
   237         if (p == null) {
   238             return true;
   239         }
   240         String pkg = findPkgName(e);
   241 
   242         ProcessPage pp;
   243         try (InputStream is = openStream(pkg, p.xhtml())) {
   244             pp = ProcessPage.readPage(is);
   245             is.close();
   246         } catch (IOException iOException) {
   247             err().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
   248             ok = false;
   249             pp = null;
   250         }
   251         Writer w;
   252         String className = p.className();
   253         if (className.isEmpty()) {
   254             int indx = p.xhtml().indexOf('.');
   255             className = p.xhtml().substring(0, indx);
   256         }
   257         try {
   258             StringWriter body = new StringWriter();
   259             List<String> propsGetSet = new ArrayList<>();
   260             List<String> functions = new ArrayList<>();
   261             Map<String, Collection<String>> propsDeps = new HashMap<>();
   262             if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   263                 ok = false;
   264             }
   265             if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps)) {
   266                 ok = false;
   267             }
   268             if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   269                 ok = false;
   270             }
   271             if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) {
   272                 ok = false;
   273             }
   274             
   275             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   276             w = new OutputStreamWriter(java.openOutputStream());
   277             try {
   278                 w.append("package " + pkg + ";\n");
   279                 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   280                 w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   281                 w.append("final class ").append(className).append(" {\n");
   282                 w.append("  private boolean locked;\n");
   283                 if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
   284                     ok = false;
   285                 } else {
   286                     if (pp != null) for (String id : pp.ids()) {
   287                         String tag = pp.tagNameForId(id);
   288                         String type = type(tag);
   289                         w.append("  ").append("public final ").
   290                             append(type).append(' ').append(cnstnt(id)).append(" = new ").
   291                             append(type).append("(\"").append(id).append("\");\n");
   292                     }
   293                 }
   294                 w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   295                 w.append(body.toString());
   296                 if (!propsGetSet.isEmpty()) {
   297                     w.write("public " + className + " applyBindings() {\n");
   298                     w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
   299                     w.write(className + ".class, this, ");
   300                     writeStringArray(propsGetSet, w);
   301                     w.append(", ");
   302                     writeStringArray(functions, w);
   303                     w.write(");\n  return this;\n}\n");
   304 
   305                     w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
   306                     w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
   307                     w.write("}\n");
   308                 }
   309                 w.append("}\n");
   310             } finally {
   311                 w.close();
   312             }
   313         } catch (IOException ex) {
   314             err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   315             return false;
   316         }
   317         return ok;
   318     }
   319 
   320     private static String type(String tag) {
   321         if (tag.equals("title")) {
   322             return "Title";
   323         }
   324         if (tag.equals("button")) {
   325             return "Button";
   326         }
   327         if (tag.equals("input")) {
   328             return "Input";
   329         }
   330         if (tag.equals("canvas")) {
   331             return "Canvas";
   332         }
   333         if (tag.equals("img")) {
   334             return "Image";
   335         }
   336         return "Element";
   337     }
   338 
   339     private static String cnstnt(String id) {
   340         return id.replace('.', '_').replace('-', '_');
   341     }
   342 
   343     private boolean initializeOnClick(
   344         String className, TypeElement type, Writer w, ProcessPage pp
   345     ) throws IOException {
   346         boolean ok = true;
   347         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   348         { //for (Element clazz : pe.getEnclosedElements()) {
   349           //  if (clazz.getKind() != ElementKind.CLASS) {
   350             //    continue;
   351            // }
   352             w.append("  public ").append(className).append("() {\n");
   353             StringBuilder dispatch = new StringBuilder();
   354             int dispatchCnt = 0;
   355             for (Element method : type.getEnclosedElements()) {
   356                 On oc = method.getAnnotation(On.class);
   357                 if (oc != null) {
   358                     for (String id : oc.id()) {
   359                         if (pp == null) {
   360                             err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   361                             ok = false;
   362                             continue;
   363                         }
   364                         if (pp.tagNameForId(id) == null) {
   365                             err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   366                             ok = false;
   367                             continue;
   368                         }
   369                         ExecutableElement ee = (ExecutableElement)method;
   370                         CharSequence params = wrapParams(ee, id, className, "ev", null);
   371                         if (!ee.getModifiers().contains(Modifier.STATIC)) {
   372                             err().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   373                             ok = false;
   374                             continue;
   375                         }
   376                         if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   377                             err().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   378                             ok = false;
   379                             continue;
   380                         }
   381                         w.append("  OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
   382                             append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
   383 
   384                         dispatch.
   385                             append("      case ").append(dispatchCnt).append(": ").
   386                             append(type.getSimpleName().toString()).
   387                             append('.').append(ee.getSimpleName()).append("(").
   388                             append(params).
   389                             append("); break;\n");
   390                         
   391                         dispatchCnt++;
   392                     }
   393                 }
   394             }
   395             w.append("  }\n");
   396             if (dispatchCnt > 0) {
   397                 w.append("class OnDispatch implements OnHandler {\n");
   398                 w.append("  private final int dispatch;\n");
   399                 w.append("  OnDispatch(int d) { dispatch = d; }\n");
   400                 w.append("  public void onEvent(Object ev) {\n");
   401                 w.append("    switch (dispatch) {\n");
   402                 w.append(dispatch);
   403                 w.append("    }\n");
   404                 w.append("  }\n");
   405                 w.append("}\n");
   406             }
   407             
   408 
   409         }
   410         return ok;
   411     }
   412 
   413     @Override
   414     public Iterable<? extends Completion> getCompletions(
   415         Element element, AnnotationMirror annotation, 
   416         ExecutableElement member, String userText
   417     ) {
   418         if (!userText.startsWith("\"")) {
   419             return Collections.emptyList();
   420         }
   421         
   422         Element cls = findClass(element);
   423         Page p = cls.getAnnotation(Page.class);
   424         String pkg = findPkgName(cls);
   425         ProcessPage pp;
   426         try {
   427             InputStream is = openStream(pkg, p.xhtml());
   428             pp = ProcessPage.readPage(is);
   429             is.close();
   430         } catch (IOException iOException) {
   431             return Collections.emptyList();
   432         }
   433         
   434         List<Completion> cc = new ArrayList<>();
   435         userText = userText.substring(1);
   436         for (String id : pp.ids()) {
   437             if (id.startsWith(userText)) {
   438                 cc.add(Completions.of("\"" + id + "\"", id));
   439             }
   440         }
   441         return cc;
   442     }
   443     
   444     private static Element findClass(Element e) {
   445         if (e == null) {
   446             return null;
   447         }
   448         Page p = e.getAnnotation(Page.class);
   449         if (p != null) {
   450             return e;
   451         }
   452         return e.getEnclosingElement();
   453     }
   454 
   455     private boolean generateProperties(
   456         Element where,
   457         Writer w, Property[] properties,
   458         Collection<String> props, Map<String,Collection<String>> deps
   459     ) throws IOException {
   460         boolean ok = true;
   461         for (Property p : properties) {
   462             final String tn;
   463             tn = typeName(where, p);
   464             String[] gs = toGetSet(p.name(), tn, p.array());
   465 
   466             if (p.array()) {
   467                 w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
   468                     + p.name() + "\"");
   469                 final Collection<String> dependants = deps.get(p.name());
   470                 if (dependants != null) {
   471                     for (String depProp : dependants) {
   472                         w.write(", ");
   473                         w.write('\"');
   474                         w.write(depProp);
   475                         w.write('\"');
   476                     }
   477                 }
   478                 w.write(");\n");
   479                 w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   480                 w.write("  if (locked) throw new IllegalStateException();\n");
   481                 w.write("  prop_" + p.name() + ".assign(ko);\n");
   482                 w.write("  return prop_" + p.name() + ";\n");
   483                 w.write("}\n");
   484             } else {
   485                 w.write("private " + tn + " prop_" + p.name() + ";\n");
   486                 w.write("public " + tn + " " + gs[0] + "() {\n");
   487                 w.write("  if (locked) throw new IllegalStateException();\n");
   488                 w.write("  return prop_" + p.name() + ";\n");
   489                 w.write("}\n");
   490                 w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   491                 w.write("  if (locked) throw new IllegalStateException();\n");
   492                 w.write("  prop_" + p.name() + " = v;\n");
   493                 w.write("  if (ko != null) {\n");
   494                 w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   495                 final Collection<String> dependants = deps.get(p.name());
   496                 if (dependants != null) {
   497                     for (String depProp : dependants) {
   498                         w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   499                     }
   500                 }
   501                 w.write("  }\n");
   502                 w.write("}\n");
   503             }
   504             
   505             props.add(p.name());
   506             props.add(gs[2]);
   507             props.add(gs[3]);
   508             props.add(gs[0]);
   509         }
   510         return ok;
   511     }
   512 
   513     private boolean generateComputedProperties(
   514         Writer w, Property[] fixedProps,
   515         Collection<? extends Element> arr, Collection<String> props,
   516         Map<String,Collection<String>> deps
   517     ) throws IOException {
   518         boolean ok = true;
   519         for (Element e : arr) {
   520             if (e.getKind() != ElementKind.METHOD) {
   521                 continue;
   522             }
   523             if (e.getAnnotation(ComputedProperty.class) == null) {
   524                 continue;
   525             }
   526             ExecutableElement ee = (ExecutableElement)e;
   527             final TypeMirror rt = ee.getReturnType();
   528             final Types tu = processingEnv.getTypeUtils();
   529             TypeMirror ert = tu.erasure(rt);
   530             String tn = fqn(ert, ee);
   531             boolean array = false;
   532             if (tn.equals("java.util.List")) {
   533                 array = true;
   534             }
   535             
   536             final String sn = ee.getSimpleName().toString();
   537             String[] gs = toGetSet(sn, tn, array);
   538             
   539             w.write("public " + tn + " " + gs[0] + "() {\n");
   540             w.write("  if (locked) throw new IllegalStateException();\n");
   541             int arg = 0;
   542             for (VariableElement pe : ee.getParameters()) {
   543                 final String dn = pe.getSimpleName().toString();
   544                 
   545                 if (!verifyPropName(pe, dn, fixedProps)) {
   546                     ok = false;
   547                 }
   548                 
   549                 final String dt = fqn(pe.asType(), ee);
   550                 String[] call = toGetSet(dn, dt, false);
   551                 w.write("  " + dt + " arg" + (++arg) + " = ");
   552                 w.write(call[0] + "();\n");
   553                 
   554                 Collection<String> depends = deps.get(dn);
   555                 if (depends == null) {
   556                     depends = new LinkedHashSet<>();
   557                     deps.put(dn, depends);
   558                 }
   559                 depends.add(sn);
   560             }
   561             w.write("  try {\n");
   562             w.write("    locked = true;\n");
   563             w.write("    return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "(");
   564             String sep = "";
   565             for (int i = 1; i <= arg; i++) {
   566                 w.write(sep);
   567                 w.write("arg" + i);
   568                 sep = ", ";
   569             }
   570             w.write(");\n");
   571             w.write("  } finally {\n");
   572             w.write("    locked = false;\n");
   573             w.write("  }\n");
   574             w.write("}\n");
   575 
   576             props.add(e.getSimpleName().toString());
   577             props.add(gs[2]);
   578             props.add(null);
   579             props.add(gs[0]);
   580         }
   581         
   582         return ok;
   583     }
   584 
   585     private static String[] toGetSet(String name, String type, boolean array) {
   586         String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
   587         String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
   588         if ("int".equals(type)) {
   589             bck2brwsrType = "I";
   590         }
   591         if ("double".equals(type)) {
   592             bck2brwsrType = "D";
   593         }
   594         String pref = "get";
   595         if ("boolean".equals(type)) {
   596             pref = "is";
   597             bck2brwsrType = "Z";
   598         }
   599         final String nu = n.replace('.', '_');
   600         if (array) {
   601             return new String[] { 
   602                 "get" + n,
   603                 null,
   604                 "get" + nu + "__Ljava_util_List_2",
   605                 null
   606             };
   607         }
   608         return new String[]{
   609             pref + n, 
   610             "set" + n, 
   611             pref + nu + "__" + bck2brwsrType,
   612             "set" + nu + "__V" + bck2brwsrType
   613         };
   614     }
   615 
   616     private String typeName(Element where, Property p) {
   617         String ret;
   618         boolean[] isModel = { false };
   619         boolean[] isEnum = { false };
   620         ret = checkType(p, isModel, isEnum);
   621         if (p.array()) {
   622             String bt = findBoxedType(ret);
   623             if (bt != null) {
   624                 return bt;
   625             }
   626         }
   627         if (!isModel[0] && !"java.lang.String".equals(ret) && !isEnum[0]) {
   628             String bt = findBoxedType(ret);
   629             if (bt == null) {
   630                 err().printMessage(
   631                     Diagnostic.Kind.ERROR, 
   632                     "Only primitive types supported in the mapping. Not " + ret,
   633                     where
   634                 );
   635             }
   636         }
   637         return ret;
   638     }
   639     
   640     private static String findBoxedType(String ret) {
   641         if (ret.equals("boolean")) {
   642             return Boolean.class.getName();
   643         }
   644         if (ret.equals("byte")) {
   645             return Byte.class.getName();
   646         }
   647         if (ret.equals("short")) {
   648             return Short.class.getName();
   649         }
   650         if (ret.equals("char")) {
   651             return Character.class.getName();
   652         }
   653         if (ret.equals("int")) {
   654             return Integer.class.getName();
   655         }
   656         if (ret.equals("long")) {
   657             return Long.class.getName();
   658         }
   659         if (ret.equals("float")) {
   660             return Float.class.getName();
   661         }
   662         if (ret.equals("double")) {
   663             return Double.class.getName();
   664         }
   665         return null;
   666     }
   667 
   668     private boolean verifyPropName(Element e, String propName, Property[] existingProps) {
   669         StringBuilder sb = new StringBuilder();
   670         String sep = "";
   671         for (Property property : existingProps) {
   672             if (property.name().equals(propName)) {
   673                 return true;
   674             }
   675             sb.append(sep);
   676             sb.append('"');
   677             sb.append(property.name());
   678             sb.append('"');
   679             sep = ", ";
   680         }
   681         err().printMessage(Diagnostic.Kind.ERROR,
   682             propName + " is not one of known properties: " + sb
   683             , e
   684         );
   685         return false;
   686     }
   687 
   688     private static String findPkgName(Element e) {
   689         for (;;) {
   690             if (e.getKind() == ElementKind.PACKAGE) {
   691                 return ((PackageElement)e).getQualifiedName().toString();
   692             }
   693             e = e.getEnclosingElement();
   694         }
   695     }
   696 
   697     private boolean generateFunctions(
   698         Element clazz, StringWriter body, String className, 
   699         List<? extends Element> enclosedElements, List<String> functions
   700     ) {
   701         for (Element m : enclosedElements) {
   702             if (m.getKind() != ElementKind.METHOD) {
   703                 continue;
   704             }
   705             ExecutableElement e = (ExecutableElement)m;
   706             OnFunction onF = e.getAnnotation(OnFunction.class);
   707             if (onF == null) {
   708                 continue;
   709             }
   710             if (!e.getModifiers().contains(Modifier.STATIC)) {
   711                 err().printMessage(
   712                     Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e
   713                 );
   714                 return false;
   715             }
   716             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   717                 err().printMessage(
   718                     Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e
   719                 );
   720                 return false;
   721             }
   722             if (e.getReturnType().getKind() != TypeKind.VOID) {
   723                 err().printMessage(
   724                     Diagnostic.Kind.ERROR, "@OnFunction method should return void", e
   725                 );
   726                 return false;
   727             }
   728             String n = e.getSimpleName().toString();
   729             body.append("private void ").append(n).append("(Object data, Object ev) {\n");
   730             body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   731             body.append(wrapParams(e, null, className, "ev", "data"));
   732             body.append(");\n");
   733             body.append("}\n");
   734             
   735             functions.add(n);
   736             functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
   737         }
   738         return true;
   739     }
   740 
   741     private boolean generateReceive(
   742         Element clazz, StringWriter body, String className, 
   743         List<? extends Element> enclosedElements, List<String> functions
   744     ) {
   745         for (Element m : enclosedElements) {
   746             if (m.getKind() != ElementKind.METHOD) {
   747                 continue;
   748             }
   749             ExecutableElement e = (ExecutableElement)m;
   750             OnReceive onR = e.getAnnotation(OnReceive.class);
   751             if (onR == null) {
   752                 continue;
   753             }
   754             if (!e.getModifiers().contains(Modifier.STATIC)) {
   755                 err().printMessage(
   756                     Diagnostic.Kind.ERROR, "@OnReceive method needs to be static", e
   757                 );
   758                 return false;
   759             }
   760             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   761                 err().printMessage(
   762                     Diagnostic.Kind.ERROR, "@OnReceive method cannot be private", e
   763                 );
   764                 return false;
   765             }
   766             if (e.getReturnType().getKind() != TypeKind.VOID) {
   767                 err().printMessage(
   768                     Diagnostic.Kind.ERROR, "@OnReceive method should return void", e
   769                 );
   770                 return false;
   771             }
   772             String modelClass = null;
   773             boolean expectsList = false;
   774             List<String> args = new ArrayList<>();
   775             {
   776                 for (VariableElement ve : e.getParameters()) {
   777                     TypeMirror modelType = null;
   778                     if (ve.asType().toString().equals(className)) {
   779                         args.add(className + ".this");
   780                     } else if (isModel(ve.asType())) {
   781                         modelType = ve.asType();
   782                     } else if (ve.asType().getKind() == TypeKind.ARRAY) {
   783                         modelType = ((ArrayType)ve.asType()).getComponentType();
   784                         expectsList = true;
   785                     }
   786                     if (modelType != null) {
   787                         if (modelClass != null) {
   788                             err().printMessage(Diagnostic.Kind.ERROR, "There can be only one model class among arguments", e);
   789                         } else {
   790                             modelClass = modelType.toString();
   791                             if (expectsList) {
   792                                 args.add("arr");
   793                             } else {
   794                                 args.add("arr[0]");
   795                             }
   796                         }
   797                     }
   798                 }
   799             }
   800             if (modelClass == null) {
   801                 err().printMessage(Diagnostic.Kind.ERROR, "The method needs to have one @Model class as parameter", e);
   802             }
   803             String n = e.getSimpleName().toString();
   804             body.append("public void ").append(n).append("(");
   805             StringBuilder assembleURL = new StringBuilder();
   806             {
   807                 String sep = "";
   808                 for (String p : findParamNames(e, onR.url(), assembleURL)) {
   809                     body.append(sep);
   810                     body.append("String ").append(p);
   811                     sep = ", ";
   812                 }
   813             }
   814             body.append(") {\n");
   815             body.append("  final Object[] result = { null };\n");
   816             body.append(
   817                 "  class ProcessResult implements Runnable {\n" +
   818                 "    @Override\n" +
   819                 "    public void run() {\n" +
   820                 "      Object value = result[0];\n");
   821             body.append(
   822                 "      " + modelClass + "[] arr;\n");
   823             body.append(
   824                 "      if (value instanceof Object[]) {\n" +
   825                 "        Object[] data = ((Object[])value);\n" +
   826                 "        arr = new " + modelClass + "[data.length];\n" +
   827                 "        for (int i = 0; i < data.length; i++) {\n" +
   828                 "          arr[i] = new " + modelClass + "(data[i]);\n" +
   829                 "        }\n" +
   830                 "      } else {\n" +
   831                 "        arr = new " + modelClass + "[1];\n" +
   832                 "        arr[0] = new " + modelClass + "(value);\n" +
   833                 "      }\n"
   834             );
   835             {
   836                 body.append(clazz.getSimpleName()).append(".").append(n).append("(");
   837                 String sep = "";
   838                 for (String arg : args) {
   839                     body.append(sep);
   840                     body.append(arg);
   841                     sep = ", ";
   842                 }
   843                 body.append(");\n");
   844             }
   845             body.append(
   846                 "    }\n" +
   847                 "  }\n"
   848             );
   849             body.append("  org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n      ");
   850             body.append(assembleURL);
   851             body.append(", result, new ProcessResult()\n  );\n");
   852 //            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   853 //            body.append(wrapParams(e, null, className, "ev", "data"));
   854 //            body.append(");\n");
   855             body.append("}\n");
   856         }
   857         return true;
   858     }
   859 
   860     private CharSequence wrapParams(
   861         ExecutableElement ee, String id, String className, String evName, String dataName
   862     ) {
   863         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   864         StringBuilder params = new StringBuilder();
   865         boolean first = true;
   866         for (VariableElement ve : ee.getParameters()) {
   867             if (!first) {
   868                 params.append(", ");
   869             }
   870             first = false;
   871             String toCall = null;
   872             if (ve.asType() == stringType) {
   873                 if (ve.getSimpleName().contentEquals("id")) {
   874                     params.append('"').append(id).append('"');
   875                     continue;
   876                 }
   877                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
   878             }
   879             if (ve.asType().getKind() == TypeKind.DOUBLE) {
   880                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
   881             }
   882             if (ve.asType().getKind() == TypeKind.INT) {
   883                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
   884             }
   885             if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
   886                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
   887             }
   888 
   889             if (toCall != null) {
   890                 params.append(toCall);
   891                 if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
   892                     params.append(dataName);
   893                     params.append(", null");
   894                 } else {
   895                     params.append(evName);
   896                     params.append(", \"");
   897                     params.append(ve.getSimpleName().toString());
   898                     params.append("\"");
   899                 }
   900                 params.append(")");
   901                 continue;
   902             }
   903             String rn = fqn(ve.asType(), ee);
   904             int last = rn.lastIndexOf('.');
   905             if (last >= 0) {
   906                 rn = rn.substring(last + 1);
   907             }
   908             if (rn.equals(className)) {
   909                 params.append(className).append(".this");
   910                 continue;
   911             }
   912             err().printMessage(Diagnostic.Kind.ERROR, 
   913                 "@On method can only accept String named 'id' or " + className + " arguments",
   914                 ee
   915             );
   916         }
   917         return params;
   918     }
   919     
   920     private boolean isModel(TypeMirror tm) {
   921         final Element e = processingEnv.getTypeUtils().asElement(tm);
   922         if (e == null) {
   923             return false;
   924         }
   925         for (Element ch : e.getEnclosedElements()) {
   926             if (ch.getKind() == ElementKind.METHOD) {
   927                 ExecutableElement ee = (ExecutableElement)ch;
   928                 if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
   929                     return true;
   930                 }
   931             }
   932         }
   933         return models.values().contains(e.getSimpleName().toString());
   934     }
   935 
   936     private void writeStringArray(List<String> strings, Writer w) throws IOException {
   937         w.write("new String[] {\n");
   938         String sep = "";
   939         for (String n : strings) {
   940             w.write(sep);
   941             if (n == null) {
   942                 w.write("    null");
   943             } else {
   944                 w.write("    \"" + n + "\"");
   945             }
   946             sep = ",\n";
   947         }
   948         w.write("\n  }");
   949     }
   950     
   951     private void writeToString(Property[] props, Writer w) throws IOException {
   952         w.write("  public String toString() {\n");
   953         w.write("    StringBuilder sb = new StringBuilder();\n");
   954         w.write("    sb.append('{');\n");
   955         String sep = "";
   956         for (Property p : props) {
   957             w.write(sep);
   958             w.append("    sb.append(\"" + p.name() + ": \");\n");
   959             w.append("    sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
   960             w.append(p.name()).append("));\n");
   961             sep =    "    sb.append(',');\n";
   962         }
   963         w.write("    sb.append('}');\n");
   964         w.write("    return sb.toString();\n");
   965         w.write("  }\n");
   966     }
   967     private void writeClone(String className, Property[] props, Writer w) throws IOException {
   968         w.write("  public " + className + " clone() {\n");
   969         w.write("    " + className + " ret = new " + className + "();\n");
   970         for (Property p : props) {
   971             if (!p.array()) {
   972                 boolean isModel[] = { false };
   973                 boolean isEnum[] = { false };
   974                 checkType(p, isModel, isEnum);
   975                 if (!isModel[0]) {
   976                     w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
   977                     continue;
   978                 }
   979             }
   980             w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
   981         }
   982         
   983         w.write("    return ret;\n");
   984         w.write("  }\n");
   985     }
   986 
   987     private String inPckName(Element e) {
   988         StringBuilder sb = new StringBuilder();
   989         while (e.getKind() != ElementKind.PACKAGE) {
   990             if (sb.length() == 0) {
   991                 sb.append(e.getSimpleName());
   992             } else {
   993                 sb.insert(0, '.');
   994                 sb.insert(0, e.getSimpleName());
   995             }
   996             e = e.getEnclosingElement();
   997         }
   998         return sb.toString();
   999     }
  1000 
  1001     private String fqn(TypeMirror pt, Element relative) {
  1002         if (pt.getKind() == TypeKind.ERROR) {
  1003             final Elements eu = processingEnv.getElementUtils();
  1004             PackageElement pckg = eu.getPackageOf(relative);
  1005             return pckg.getQualifiedName() + "." + pt.toString();
  1006         }
  1007         return pt.toString();
  1008     }
  1009 
  1010     private String checkType(Property p, boolean[] isModel, boolean[] isEnum) {
  1011         String ret;
  1012         try {
  1013             ret = p.type().getName();
  1014         } catch (MirroredTypeException ex) {
  1015             TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror());
  1016             final Element e = processingEnv.getTypeUtils().asElement(tm);
  1017             final Model m = e == null ? null : e.getAnnotation(Model.class);
  1018             if (m != null) {
  1019                 ret = findPkgName(e) + '.' + m.className();
  1020                 isModel[0] = true;
  1021                 models.put(e, m.className());
  1022             } else {
  1023                 ret = tm.toString();
  1024             }
  1025             TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
  1026             enm = processingEnv.getTypeUtils().erasure(enm);
  1027             isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
  1028         }
  1029         return ret;
  1030     }
  1031 
  1032     private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
  1033         List<String> params = new ArrayList<>();
  1034 
  1035         for (int pos = 0; ;) {
  1036             int next = url.indexOf('{', pos);
  1037             if (next == -1) {
  1038                 assembleURL.append('"')
  1039                     .append(url.substring(pos))
  1040                     .append('"');
  1041                 return params;
  1042             }
  1043             int close = url.indexOf('}', next);
  1044             if (close == -1) {
  1045                 err().printMessage(Diagnostic.Kind.ERROR, "Unbalanced '{' and '}' in " + url, e);
  1046                 return params;
  1047             }
  1048             final String paramName = url.substring(next + 1, close);
  1049             params.add(paramName);
  1050             assembleURL.append('"')
  1051                 .append(url.substring(pos, next))
  1052                 .append("\" + ").append(paramName).append(" + ");
  1053             pos = close + 1;
  1054         }
  1055     }
  1056 
  1057     private static Property findProperty(Property[] properties, String propName) {
  1058         for (Property p : properties) {
  1059             if (propName.equals(p.name())) {
  1060                 return p;
  1061             }
  1062         }
  1063         return null;
  1064     }
  1065 
  1066     private boolean isPrimitive(String type) {
  1067         return 
  1068             "int".equals(type) ||
  1069             "double".equals(type) ||
  1070             "long".equals(type) ||
  1071             "short".equals(type) ||
  1072             "byte".equals(type) ||
  1073             "float".equals(type);
  1074     }
  1075 }