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