javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 17 Apr 2013 13:46:06 +0200
branchfx
changeset 1003 bf8b1d7d76e0
parent 979 83f4aa79c130
parent 1001 748dc75f709a
child 1011 9cc253aa9405
permissions -rw-r--r--
Merge of the processor fix into fx branch, so the TwitterClient class can find TwitterModel
     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.lang.annotation.AnnotationTypeMismatchException;
    26 import java.lang.annotation.IncompleteAnnotationException;
    27 import java.lang.reflect.Method;
    28 import java.util.ArrayList;
    29 import java.util.Collection;
    30 import java.util.Collections;
    31 import java.util.HashMap;
    32 import java.util.HashSet;
    33 import java.util.LinkedHashSet;
    34 import java.util.List;
    35 import java.util.Map;
    36 import java.util.Set;
    37 import java.util.WeakHashMap;
    38 import javax.annotation.processing.AbstractProcessor;
    39 import javax.annotation.processing.Completion;
    40 import javax.annotation.processing.Completions;
    41 import javax.annotation.processing.ProcessingEnvironment;
    42 import javax.annotation.processing.Processor;
    43 import javax.annotation.processing.RoundEnvironment;
    44 import javax.annotation.processing.SupportedAnnotationTypes;
    45 import javax.lang.model.element.AnnotationMirror;
    46 import javax.lang.model.element.AnnotationValue;
    47 import javax.lang.model.element.Element;
    48 import javax.lang.model.element.ElementKind;
    49 import javax.lang.model.element.ExecutableElement;
    50 import javax.lang.model.element.Modifier;
    51 import javax.lang.model.element.PackageElement;
    52 import javax.lang.model.element.TypeElement;
    53 import javax.lang.model.element.VariableElement;
    54 import javax.lang.model.type.ArrayType;
    55 import javax.lang.model.type.MirroredTypeException;
    56 import javax.lang.model.type.TypeKind;
    57 import javax.lang.model.type.TypeMirror;
    58 import javax.lang.model.util.Elements;
    59 import javax.lang.model.util.Types;
    60 import javax.tools.Diagnostic;
    61 import javax.tools.FileObject;
    62 import javax.tools.StandardLocation;
    63 import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
    64 import org.apidesign.bck2brwsr.htmlpage.api.Model;
    65 import org.apidesign.bck2brwsr.htmlpage.api.On;
    66 import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
    67 import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange;
    68 import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
    69 import org.apidesign.bck2brwsr.htmlpage.api.Page;
    70 import org.apidesign.bck2brwsr.htmlpage.api.Property;
    71 import org.openide.util.lookup.ServiceProvider;
    72 
    73 /** Annotation processor to process an XHTML page and generate appropriate 
    74  * "id" file.
    75  *
    76  * @author Jaroslav Tulach <jtulach@netbeans.org>
    77  */
    78 @ServiceProvider(service=Processor.class)
    79 @SupportedAnnotationTypes({
    80     "org.apidesign.bck2brwsr.htmlpage.api.Model",
    81     "org.apidesign.bck2brwsr.htmlpage.api.Page",
    82     "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
    83     "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
    84     "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange",
    85     "org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty",
    86     "org.apidesign.bck2brwsr.htmlpage.api.On"
    87 })
    88 public final class PageProcessor extends AbstractProcessor {
    89     private final Map<Element,String> models = new WeakHashMap<>();
    90     private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
    91     @Override
    92     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    93         boolean ok = true;
    94         for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
    95             if (!processModel(e)) {
    96                 ok = false;
    97             }
    98         }
    99         for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
   100             if (!processPage(e)) {
   101                 ok = false;
   102             }
   103         }
   104         if (roundEnv.processingOver()) {
   105             models.clear();
   106             for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
   107                 TypeElement te = (TypeElement)entry.getKey();
   108                 String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
   109                 Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
   110                 if (finalElem == null) {
   111                     continue;
   112                 }
   113                 Prprt[] props;
   114                 Model m = finalElem.getAnnotation(Model.class);
   115                 if (m != null) {
   116                     props = Prprt.wrap(processingEnv, finalElem, m.properties());
   117                 } else {
   118                     Page p = finalElem.getAnnotation(Page.class);
   119                     props = Prprt.wrap(processingEnv, finalElem, p.properties());
   120                 }
   121                 for (Prprt p : props) {
   122                     boolean[] isModel = { false };
   123                     boolean[] isEnum = { false };
   124                     boolean[] isPrimitive = { false };
   125                     String t = checkType(p, isModel, isEnum, isPrimitive);
   126                     if (isEnum[0]) {
   127                         continue;
   128                     }
   129                     if (isPrimitive[0]) {
   130                         continue;
   131                     }
   132                     if (isModel[0]) {
   133                         continue;
   134                     }
   135                     if ("java.lang.String".equals(t)) {
   136                         continue;
   137                     }
   138                     error("The type " + t + " should be defined by @Model annotation", entry.getKey());
   139                 }
   140             }
   141             verify.clear();
   142         }
   143         return ok;
   144     }
   145 
   146     private InputStream openStream(String pkg, String name) throws IOException {
   147         try {
   148             FileObject fo = processingEnv.getFiler().getResource(
   149                 StandardLocation.SOURCE_PATH, pkg, name);
   150             return fo.openInputStream();
   151         } catch (IOException ex) {
   152             return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
   153         }
   154     }
   155 
   156     private void error(String msg, Element e) {
   157         processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
   158     }
   159     
   160     private boolean processModel(Element e) {
   161         boolean ok = true;
   162         Model m = e.getAnnotation(Model.class);
   163         if (m == null) {
   164             return true;
   165         }
   166         String pkg = findPkgName(e);
   167         Writer w;
   168         String className = m.className();
   169         models.put(e, className);
   170         try {
   171             StringWriter body = new StringWriter();
   172             List<String> propsGetSet = new ArrayList<>();
   173             List<String> functions = new ArrayList<>();
   174             Map<String, Collection<String>> propsDeps = new HashMap<>();
   175             Map<String, Collection<String>> functionDeps = new HashMap<>();
   176             Prprt[] props = createProps(e, m.properties());
   177             
   178             if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
   179                 ok = false;
   180             }
   181             if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
   182                 ok = false;
   183             }
   184             if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
   185                 ok = false;
   186             }
   187             if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   188                 ok = false;
   189             }
   190             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   191             w = new OutputStreamWriter(java.openOutputStream());
   192             try {
   193                 w.append("package " + pkg + ";\n");
   194                 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   195                 w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   196                 w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
   197                 w.append("public final class ").append(className).append(" implements Cloneable {\n");
   198                 w.append("  private boolean locked;\n");
   199                 w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   200                 w.append(body.toString());
   201                 w.append("  private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
   202                 w.append("  public ").append(className).append("() {\n");
   203                 w.append("    intKnckt();\n");
   204                 w.append("  };\n");
   205                 w.append("  private void intKnckt() {\n");
   206                 w.append("    ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
   207                 writeStringArray(propsGetSet, w);
   208                 w.append(", ");
   209                 writeStringArray(functions, w);
   210                 w.append("    );\n");
   211                 w.append("  };\n");
   212                 w.append("  ").append(className).append("(Object json) {\n");
   213                 int values = 0;
   214                 for (int i = 0; i < propsGetSet.size(); i += 4) {
   215                     Prprt p = findPrprt(props, propsGetSet.get(i));
   216                     if (p == null) {
   217                         continue;
   218                     }
   219                     values++;
   220                 }
   221                 w.append("    Object[] ret = new Object[" + values + "];\n");
   222                 w.append("    org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
   223                 for (int i = 0; i < propsGetSet.size(); i += 4) {
   224                     Prprt p = findPrprt(props, propsGetSet.get(i));
   225                     if (p == null) {
   226                         continue;
   227                     }
   228                     w.append("      \"").append(propsGetSet.get(i)).append("\",\n");
   229                 }
   230                 w.append("    }, ret);\n");
   231                 for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
   232                     final String pn = propsGetSet.get(i);
   233                     Prprt p = findPrprt(props, pn);
   234                     if (p == null) {
   235                         continue;
   236                     }
   237                     boolean[] isModel = { false };
   238                     boolean[] isEnum = { false };
   239                     boolean isPrimitive[] = { false };
   240                     String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
   241                     if (p.array()) {
   242                         w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
   243                         w.append("  for (Object e : ((Object[])ret[" + cnt + "])) {\n");
   244                         if (isModel[0]) {
   245                             w.append("    this.prop_").append(pn).append(".add(new ");
   246                             w.append(type).append("(e));\n");
   247                         } else if (isEnum[0]) {
   248                             w.append("    this.prop_").append(pn);
   249                             w.append(".add(");
   250                             w.append(type).append(".valueOf((String)e));\n");
   251                         } else {
   252                             if (isPrimitive(type)) {
   253                                 w.append("    this.prop_").append(pn).append(".add(((Number)e).");
   254                                 w.append(type).append("Value());\n");
   255                             } else {
   256                                 w.append("    this.prop_").append(pn).append(".add((");
   257                                 w.append(type).append(")e);\n");
   258                             }
   259                         }
   260                         w.append("  }\n");
   261                         w.append("}\n");
   262                     } else {
   263                         if (isEnum[0]) {
   264                             w.append("    this.prop_").append(pn);
   265                             w.append(" = ");
   266                             w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
   267                         } else if (isPrimitive(type)) {
   268                             w.append("    this.prop_").append(pn);
   269                             w.append(" = ((Number)").append("ret[" + cnt + "]).");
   270                             w.append(type).append("Value();\n");
   271                         } else {
   272                             w.append("    this.prop_").append(pn);
   273                             w.append(" = (").append(type).append(')');
   274                             w.append("ret[" + cnt + "];\n");
   275                         }
   276                     }
   277                     cnt++;
   278                 }
   279                 w.append("    intKnckt();\n");
   280                 w.append("  };\n");
   281                 writeToString(props, w);
   282                 writeClone(className, props, w);
   283                 w.append("  public Object koData() {\n");
   284                 w.append("    return ko.koData();\n");
   285                 w.append("  }\n");
   286                 w.append("}\n");
   287             } finally {
   288                 w.close();
   289             }
   290         } catch (IOException ex) {
   291             error("Can't create " + className + ".java", e);
   292             return false;
   293         }
   294         return ok;
   295     }
   296     
   297     private boolean processPage(Element e) {
   298         boolean ok = true;
   299         Page p = e.getAnnotation(Page.class);
   300         if (p == null) {
   301             return true;
   302         }
   303         String pkg = findPkgName(e);
   304 
   305         ProcessPage pp;
   306         try (InputStream is = openStream(pkg, p.xhtml())) {
   307             pp = ProcessPage.readPage(is);
   308             is.close();
   309         } catch (IOException iOException) {
   310             error("Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
   311             ok = false;
   312             pp = null;
   313         }
   314         Writer w;
   315         String className = p.className();
   316         if (className.isEmpty()) {
   317             int indx = p.xhtml().indexOf('.');
   318             className = p.xhtml().substring(0, indx);
   319         }
   320         try {
   321             StringWriter body = new StringWriter();
   322             List<String> propsGetSet = new ArrayList<>();
   323             List<String> functions = new ArrayList<>();
   324             Map<String, Collection<String>> propsDeps = new HashMap<>();
   325             Map<String, Collection<String>> functionDeps = new HashMap<>();
   326             
   327             Prprt[] props = createProps(e, p.properties());
   328             if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
   329                 ok = false;
   330             }
   331             if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
   332                 ok = false;
   333             }
   334             if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
   335                 ok = false;
   336             }
   337             if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   338                 ok = false;
   339             }
   340             if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) {
   341                 ok = false;
   342             }
   343             
   344             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   345             w = new OutputStreamWriter(java.openOutputStream());
   346             try {
   347                 w.append("package " + pkg + ";\n");
   348                 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   349                 w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   350                 w.append("final class ").append(className).append(" {\n");
   351                 w.append("  private boolean locked;\n");
   352                 if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
   353                     ok = false;
   354                 } else {
   355                     if (pp != null) for (String id : pp.ids()) {
   356                         String tag = pp.tagNameForId(id);
   357                         String type = type(tag);
   358                         w.append("  ").append("public final ").
   359                             append(type).append(' ').append(cnstnt(id)).append(" = new ").
   360                             append(type).append("(\"").append(id).append("\");\n");
   361                     }
   362                 }
   363                 w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   364                 w.append(body.toString());
   365                 if (!propsGetSet.isEmpty()) {
   366                     w.write("public " + className + " applyBindings() {\n");
   367                     w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
   368                     w.write(className + ".class, this, ");
   369                     writeStringArray(propsGetSet, w);
   370                     w.append(", ");
   371                     writeStringArray(functions, w);
   372                     w.write(");\n  return this;\n}\n");
   373 
   374                     w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
   375                     w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
   376                     w.write("}\n");
   377                 }
   378                 w.append("}\n");
   379             } finally {
   380                 w.close();
   381             }
   382         } catch (IOException ex) {
   383             error("Can't create " + className + ".java", e);
   384             return false;
   385         }
   386         return ok;
   387     }
   388 
   389     private static String type(String tag) {
   390         if (tag.equals("title")) {
   391             return "Title";
   392         }
   393         if (tag.equals("button")) {
   394             return "Button";
   395         }
   396         if (tag.equals("input")) {
   397             return "Input";
   398         }
   399         if (tag.equals("canvas")) {
   400             return "Canvas";
   401         }
   402         if (tag.equals("img")) {
   403             return "Image";
   404         }
   405         return "Element";
   406     }
   407 
   408     private static String cnstnt(String id) {
   409         return id.replace('.', '_').replace('-', '_');
   410     }
   411 
   412     private boolean initializeOnClick(
   413         String className, TypeElement type, Writer w, ProcessPage pp
   414     ) throws IOException {
   415         boolean ok = true;
   416         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   417         { //for (Element clazz : pe.getEnclosedElements()) {
   418           //  if (clazz.getKind() != ElementKind.CLASS) {
   419             //    continue;
   420            // }
   421             w.append("  public ").append(className).append("() {\n");
   422             StringBuilder dispatch = new StringBuilder();
   423             int dispatchCnt = 0;
   424             for (Element method : type.getEnclosedElements()) {
   425                 On oc = method.getAnnotation(On.class);
   426                 if (oc != null) {
   427                     for (String id : oc.id()) {
   428                         if (pp == null) {
   429                             error("id = " + id + " not found in HTML page.", method);
   430                             ok = false;
   431                             continue;
   432                         }
   433                         if (pp.tagNameForId(id) == null) {
   434                             error("id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   435                             ok = false;
   436                             continue;
   437                         }
   438                         ExecutableElement ee = (ExecutableElement)method;
   439                         CharSequence params = wrapParams(ee, id, className, "ev", null);
   440                         if (!ee.getModifiers().contains(Modifier.STATIC)) {
   441                             error("@On method has to be static", ee);
   442                             ok = false;
   443                             continue;
   444                         }
   445                         if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   446                             error("@On method can't be private", ee);
   447                             ok = false;
   448                             continue;
   449                         }
   450                         w.append("  OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
   451                             append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
   452 
   453                         dispatch.
   454                             append("      case ").append(dispatchCnt).append(": ").
   455                             append(type.getSimpleName().toString()).
   456                             append('.').append(ee.getSimpleName()).append("(").
   457                             append(params).
   458                             append("); break;\n");
   459                         
   460                         dispatchCnt++;
   461                     }
   462                 }
   463             }
   464             w.append("  }\n");
   465             if (dispatchCnt > 0) {
   466                 // XXX: sun.misc.Trampoline cannot call public methods in non-public classes
   467                 w.append("public class OnDispatch implements OnHandler {\n");
   468                 w.append("  private final int dispatch;\n");
   469                 w.append("  OnDispatch(int d) { dispatch = d; }\n");
   470                 w.append("  public void onEvent(Object ev) {\n");
   471                 w.append("    switch (dispatch) {\n");
   472                 w.append(dispatch);
   473                 w.append("    }\n");
   474                 w.append("  }\n");
   475                 w.append("}\n");
   476             }
   477             
   478 
   479         }
   480         return ok;
   481     }
   482 
   483     @Override
   484     public Iterable<? extends Completion> getCompletions(
   485         Element element, AnnotationMirror annotation, 
   486         ExecutableElement member, String userText
   487     ) {
   488         if (!userText.startsWith("\"")) {
   489             return Collections.emptyList();
   490         }
   491         
   492         Element cls = findClass(element);
   493         Page p = cls.getAnnotation(Page.class);
   494         String pkg = findPkgName(cls);
   495         ProcessPage pp;
   496         try {
   497             InputStream is = openStream(pkg, p.xhtml());
   498             pp = ProcessPage.readPage(is);
   499             is.close();
   500         } catch (IOException iOException) {
   501             return Collections.emptyList();
   502         }
   503         
   504         List<Completion> cc = new ArrayList<>();
   505         userText = userText.substring(1);
   506         for (String id : pp.ids()) {
   507             if (id.startsWith(userText)) {
   508                 cc.add(Completions.of("\"" + id + "\"", id));
   509             }
   510         }
   511         return cc;
   512     }
   513     
   514     private static Element findClass(Element e) {
   515         if (e == null) {
   516             return null;
   517         }
   518         Page p = e.getAnnotation(Page.class);
   519         if (p != null) {
   520             return e;
   521         }
   522         return e.getEnclosingElement();
   523     }
   524 
   525     private boolean generateProperties(
   526         Element where,
   527         Writer w, Prprt[] properties,
   528         Collection<String> props, 
   529         Map<String,Collection<String>> deps,
   530         Map<String,Collection<String>> functionDeps
   531     ) throws IOException {
   532         boolean ok = true;
   533         for (Prprt p : properties) {
   534             final String tn;
   535             tn = typeName(where, p);
   536             String[] gs = toGetSet(p.name(), tn, p.array());
   537 
   538             if (p.array()) {
   539                 w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
   540                     + p.name() + "\"");
   541                 Collection<String> dependants = deps.get(p.name());
   542                 if (dependants != null) {
   543                     for (String depProp : dependants) {
   544                         w.write(", ");
   545                         w.write('\"');
   546                         w.write(depProp);
   547                         w.write('\"');
   548                     }
   549                 }
   550                 w.write(")");
   551                 
   552                 dependants = functionDeps.get(p.name());
   553                 if (dependants != null) {
   554                     w.write(".onChange(new Runnable() { public void run() {\n");
   555                     for (String call : dependants) {
   556                         w.append(call);
   557                     }
   558                     w.write("}})");
   559                 }
   560                 w.write(";\n");
   561                 
   562                 w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   563                 w.write("  if (locked) throw new IllegalStateException();\n");
   564                 w.write("  prop_" + p.name() + ".assign(ko);\n");
   565                 w.write("  return prop_" + p.name() + ";\n");
   566                 w.write("}\n");
   567             } else {
   568                 w.write("private " + tn + " prop_" + p.name() + ";\n");
   569                 w.write("public " + tn + " " + gs[0] + "() {\n");
   570                 w.write("  if (locked) throw new IllegalStateException();\n");
   571                 w.write("  return prop_" + p.name() + ";\n");
   572                 w.write("}\n");
   573                 w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   574                 w.write("  if (locked) throw new IllegalStateException();\n");
   575                 w.write("  prop_" + p.name() + " = v;\n");
   576                 w.write("  if (ko != null) {\n");
   577                 w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   578                 Collection<String> dependants = deps.get(p.name());
   579                 if (dependants != null) {
   580                     for (String depProp : dependants) {
   581                         w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   582                     }
   583                 }
   584                 w.write("  }\n");
   585                 dependants = functionDeps.get(p.name());
   586                 if (dependants != null) {
   587                     for (String call : dependants) {
   588                         w.append(call);
   589                     }
   590                 }
   591                 w.write("}\n");
   592             }
   593             
   594             props.add(p.name());
   595             props.add(gs[2]);
   596             props.add(gs[3]);
   597             props.add(gs[0]);
   598         }
   599         return ok;
   600     }
   601 
   602     private boolean generateComputedProperties(
   603         Writer w, Prprt[] fixedProps,
   604         Collection<? extends Element> arr, Collection<String> props,
   605         Map<String,Collection<String>> deps
   606     ) throws IOException {
   607         boolean ok = true;
   608         for (Element e : arr) {
   609             if (e.getKind() != ElementKind.METHOD) {
   610                 continue;
   611             }
   612             if (e.getAnnotation(ComputedProperty.class) == null) {
   613                 continue;
   614             }
   615             ExecutableElement ee = (ExecutableElement)e;
   616             final TypeMirror rt = ee.getReturnType();
   617             final Types tu = processingEnv.getTypeUtils();
   618             TypeMirror ert = tu.erasure(rt);
   619             String tn = fqn(ert, ee);
   620             boolean array = false;
   621             if (tn.equals("java.util.List")) {
   622                 array = true;
   623             }
   624             
   625             final String sn = ee.getSimpleName().toString();
   626             String[] gs = toGetSet(sn, tn, array);
   627             
   628             w.write("public " + tn + " " + gs[0] + "() {\n");
   629             w.write("  if (locked) throw new IllegalStateException();\n");
   630             int arg = 0;
   631             for (VariableElement pe : ee.getParameters()) {
   632                 final String dn = pe.getSimpleName().toString();
   633                 
   634                 if (!verifyPropName(pe, dn, fixedProps)) {
   635                     ok = false;
   636                 }
   637                 
   638                 final String dt = fqn(pe.asType(), ee);
   639                 String[] call = toGetSet(dn, dt, false);
   640                 w.write("  " + dt + " arg" + (++arg) + " = ");
   641                 w.write(call[0] + "();\n");
   642                 
   643                 Collection<String> depends = deps.get(dn);
   644                 if (depends == null) {
   645                     depends = new LinkedHashSet<>();
   646                     deps.put(dn, depends);
   647                 }
   648                 depends.add(sn);
   649             }
   650             w.write("  try {\n");
   651             w.write("    locked = true;\n");
   652             w.write("    return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
   653             String sep = "";
   654             for (int i = 1; i <= arg; i++) {
   655                 w.write(sep);
   656                 w.write("arg" + i);
   657                 sep = ", ";
   658             }
   659             w.write(");\n");
   660             w.write("  } finally {\n");
   661             w.write("    locked = false;\n");
   662             w.write("  }\n");
   663             w.write("}\n");
   664 
   665             props.add(e.getSimpleName().toString());
   666             props.add(gs[2]);
   667             props.add(null);
   668             props.add(gs[0]);
   669         }
   670         
   671         return ok;
   672     }
   673 
   674     private static String[] toGetSet(String name, String type, boolean array) {
   675         String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
   676         String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
   677         if ("int".equals(type)) {
   678             bck2brwsrType = "I";
   679         }
   680         if ("double".equals(type)) {
   681             bck2brwsrType = "D";
   682         }
   683         String pref = "get";
   684         if ("boolean".equals(type)) {
   685             pref = "is";
   686             bck2brwsrType = "Z";
   687         }
   688         final String nu = n.replace('.', '_');
   689         if (array) {
   690             return new String[] { 
   691                 "get" + n,
   692                 null,
   693                 "get" + nu + "__Ljava_util_List_2",
   694                 null
   695             };
   696         }
   697         return new String[]{
   698             pref + n, 
   699             "set" + n, 
   700             pref + nu + "__" + bck2brwsrType,
   701             "set" + nu + "__V" + bck2brwsrType
   702         };
   703     }
   704 
   705     private String typeName(Element where, Prprt p) {
   706         String ret;
   707         boolean[] isModel = { false };
   708         boolean[] isEnum = { false };
   709         boolean isPrimitive[] = { false };
   710         ret = checkType(p, isModel, isEnum, isPrimitive);
   711         if (p.array()) {
   712             String bt = findBoxedType(ret);
   713             if (bt != null) {
   714                 return bt;
   715             }
   716         }
   717         return ret;
   718     }
   719     
   720     private static String findBoxedType(String ret) {
   721         if (ret.equals("boolean")) {
   722             return Boolean.class.getName();
   723         }
   724         if (ret.equals("byte")) {
   725             return Byte.class.getName();
   726         }
   727         if (ret.equals("short")) {
   728             return Short.class.getName();
   729         }
   730         if (ret.equals("char")) {
   731             return Character.class.getName();
   732         }
   733         if (ret.equals("int")) {
   734             return Integer.class.getName();
   735         }
   736         if (ret.equals("long")) {
   737             return Long.class.getName();
   738         }
   739         if (ret.equals("float")) {
   740             return Float.class.getName();
   741         }
   742         if (ret.equals("double")) {
   743             return Double.class.getName();
   744         }
   745         return null;
   746     }
   747 
   748     private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
   749         StringBuilder sb = new StringBuilder();
   750         String sep = "";
   751         for (Prprt Prprt : existingProps) {
   752             if (Prprt.name().equals(propName)) {
   753                 return true;
   754             }
   755             sb.append(sep);
   756             sb.append('"');
   757             sb.append(Prprt.name());
   758             sb.append('"');
   759             sep = ", ";
   760         }
   761         error(
   762             propName + " is not one of known properties: " + sb
   763             , e
   764         );
   765         return false;
   766     }
   767 
   768     private static String findPkgName(Element e) {
   769         for (;;) {
   770             if (e.getKind() == ElementKind.PACKAGE) {
   771                 return ((PackageElement)e).getQualifiedName().toString();
   772             }
   773             e = e.getEnclosingElement();
   774         }
   775     }
   776 
   777     private boolean generateFunctions(
   778         Element clazz, StringWriter body, String className, 
   779         List<? extends Element> enclosedElements, List<String> functions
   780     ) {
   781         for (Element m : enclosedElements) {
   782             if (m.getKind() != ElementKind.METHOD) {
   783                 continue;
   784             }
   785             ExecutableElement e = (ExecutableElement)m;
   786             OnFunction onF = e.getAnnotation(OnFunction.class);
   787             if (onF == null) {
   788                 continue;
   789             }
   790             if (!e.getModifiers().contains(Modifier.STATIC)) {
   791                 error("@OnFunction method needs to be static", e);
   792                 return false;
   793             }
   794             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   795                 error("@OnFunction method cannot be private", e);
   796                 return false;
   797             }
   798             if (e.getReturnType().getKind() != TypeKind.VOID) {
   799                 error("@OnFunction method should return void", e);
   800                 return false;
   801             }
   802             String n = e.getSimpleName().toString();
   803             body.append("private void ").append(n).append("(Object data, Object ev) {\n");
   804             body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   805             body.append(wrapParams(e, null, className, "ev", "data"));
   806             body.append(");\n");
   807             body.append("}\n");
   808             
   809             functions.add(n);
   810             functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
   811         }
   812         return true;
   813     }
   814 
   815     private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
   816         Prprt[] properties, String className, 
   817         Map<String, Collection<String>> functionDeps
   818     ) {
   819         for (Element m : clazz.getEnclosedElements()) {
   820             if (m.getKind() != ElementKind.METHOD) {
   821                 continue;
   822             }
   823             ExecutableElement e = (ExecutableElement) m;
   824             OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
   825             if (onPC == null) {
   826                 continue;
   827             }
   828             for (String pn : onPC.value()) {
   829                 if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   830                     error("No Prprt named '" + pn + "' in the model", clazz);
   831                     return false;
   832                 }
   833             }
   834             if (!e.getModifiers().contains(Modifier.STATIC)) {
   835                 error("@OnPrprtChange method needs to be static", e);
   836                 return false;
   837             }
   838             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   839                 error("@OnPrprtChange method cannot be private", e);
   840                 return false;
   841             }
   842             if (e.getReturnType().getKind() != TypeKind.VOID) {
   843                 error("@OnPrprtChange method should return void", e);
   844                 return false;
   845             }
   846             String n = e.getSimpleName().toString();
   847             
   848             
   849             for (String pn : onPC.value()) {
   850                 StringBuilder call = new StringBuilder();
   851                 call.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   852                 call.append(wrapPropName(e, className, "name", pn));
   853                 call.append(");\n");
   854                 
   855                 Collection<String> change = functionDeps.get(pn);
   856                 if (change == null) {
   857                     change = new ArrayList<>();
   858                     functionDeps.put(pn, change);
   859                 }
   860                 change.add(call.toString());
   861                 for (String dpn : findDerivedFrom(propDeps, pn)) {
   862                     change = functionDeps.get(dpn);
   863                     if (change == null) {
   864                         change = new ArrayList<>();
   865                         functionDeps.put(dpn, change);
   866                     }
   867                     change.add(call.toString());
   868                 }
   869             }
   870         }
   871         return true;
   872     }
   873     
   874     private boolean generateReceive(
   875         Element clazz, StringWriter body, String className, 
   876         List<? extends Element> enclosedElements, List<String> functions
   877     ) {
   878         for (Element m : enclosedElements) {
   879             if (m.getKind() != ElementKind.METHOD) {
   880                 continue;
   881             }
   882             ExecutableElement e = (ExecutableElement)m;
   883             OnReceive onR = e.getAnnotation(OnReceive.class);
   884             if (onR == null) {
   885                 continue;
   886             }
   887             if (!e.getModifiers().contains(Modifier.STATIC)) {
   888                 error("@OnReceive method needs to be static", e);
   889                 return false;
   890             }
   891             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   892                 error("@OnReceive method cannot be private", e);
   893                 return false;
   894             }
   895             if (e.getReturnType().getKind() != TypeKind.VOID) {
   896                 error("@OnReceive method should return void", e);
   897                 return false;
   898             }
   899             String modelClass = null;
   900             boolean expectsList = false;
   901             List<String> args = new ArrayList<>();
   902             {
   903                 for (VariableElement ve : e.getParameters()) {
   904                     TypeMirror modelType = null;
   905                     if (ve.asType().toString().equals(className)) {
   906                         args.add(className + ".this");
   907                     } else if (isModel(ve.asType())) {
   908                         modelType = ve.asType();
   909                     } else if (ve.asType().getKind() == TypeKind.ARRAY) {
   910                         modelType = ((ArrayType)ve.asType()).getComponentType();
   911                         expectsList = true;
   912                     }
   913                     if (modelType != null) {
   914                         if (modelClass != null) {
   915                             error("There can be only one model class among arguments", e);
   916                         } else {
   917                             modelClass = modelType.toString();
   918                             if (expectsList) {
   919                                 args.add("arr");
   920                             } else {
   921                                 args.add("arr[0]");
   922                             }
   923                         }
   924                     }
   925                 }
   926             }
   927             if (modelClass == null) {
   928                 error("The method needs to have one @Model class as parameter", e);
   929             }
   930             String n = e.getSimpleName().toString();
   931             body.append("public void ").append(n).append("(");
   932             StringBuilder assembleURL = new StringBuilder();
   933             String jsonpVarName = null;
   934             {
   935                 String sep = "";
   936                 boolean skipJSONP = onR.jsonp().isEmpty();
   937                 for (String p : findParamNames(e, onR.url(), assembleURL)) {
   938                     if (!skipJSONP && p.equals(onR.jsonp())) {
   939                         skipJSONP = true;
   940                         jsonpVarName = p;
   941                         continue;
   942                     }
   943                     body.append(sep);
   944                     body.append("String ").append(p);
   945                     sep = ", ";
   946                 }
   947                 if (!skipJSONP) {
   948                     error(
   949                         "Name of jsonp attribute ('" + onR.jsonp() + 
   950                         "') is not used in url attribute '" + onR.url() + "'", e
   951                     );
   952                 }
   953             }
   954             body.append(") {\n");
   955             body.append("  final Object[] result = { null };\n");
   956             body.append(
   957                 "  class ProcessResult implements Runnable {\n" +
   958                 "    @Override\n" +
   959                 "    public void run() {\n" +
   960                 "      Object value = result[0];\n");
   961             body.append(
   962                 "      " + modelClass + "[] arr;\n");
   963             body.append(
   964                 "      if (value instanceof Object[]) {\n" +
   965                 "        Object[] data = ((Object[])value);\n" +
   966                 "        arr = new " + modelClass + "[data.length];\n" +
   967                 "        for (int i = 0; i < data.length; i++) {\n" +
   968                 "          arr[i] = new " + modelClass + "(data[i]);\n" +
   969                 "        }\n" +
   970                 "      } else {\n" +
   971                 "        arr = new " + modelClass + "[1];\n" +
   972                 "        arr[0] = new " + modelClass + "(value);\n" +
   973                 "      }\n"
   974             );
   975             {
   976                 body.append(clazz.getSimpleName()).append(".").append(n).append("(");
   977                 String sep = "";
   978                 for (String arg : args) {
   979                     body.append(sep);
   980                     body.append(arg);
   981                     sep = ", ";
   982                 }
   983                 body.append(");\n");
   984             }
   985             body.append(
   986                 "    }\n" +
   987                 "  }\n"
   988             );
   989             body.append("  ProcessResult pr = new ProcessResult();\n");
   990             if (jsonpVarName != null) {
   991                 body.append("  String ").append(jsonpVarName).
   992                     append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n");
   993             }
   994             body.append("  org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n      ");
   995             body.append(assembleURL);
   996             body.append(", result, pr, ").append(jsonpVarName).append("\n  );\n");
   997 //            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   998 //            body.append(wrapParams(e, null, className, "ev", "data"));
   999 //            body.append(");\n");
  1000             body.append("}\n");
  1001         }
  1002         return true;
  1003     }
  1004 
  1005     private CharSequence wrapParams(
  1006         ExecutableElement ee, String id, String className, String evName, String dataName
  1007     ) {
  1008         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
  1009         StringBuilder params = new StringBuilder();
  1010         boolean first = true;
  1011         for (VariableElement ve : ee.getParameters()) {
  1012             if (!first) {
  1013                 params.append(", ");
  1014             }
  1015             first = false;
  1016             String toCall = null;
  1017             if (ve.asType() == stringType) {
  1018                 if (ve.getSimpleName().contentEquals("id")) {
  1019                     params.append('"').append(id).append('"');
  1020                     continue;
  1021                 }
  1022                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
  1023             }
  1024             if (ve.asType().getKind() == TypeKind.DOUBLE) {
  1025                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
  1026             }
  1027             if (ve.asType().getKind() == TypeKind.INT) {
  1028                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
  1029             }
  1030             if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
  1031                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
  1032             }
  1033 
  1034             if (toCall != null) {
  1035                 params.append(toCall);
  1036                 if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
  1037                     params.append(dataName);
  1038                     params.append(", null");
  1039                 } else {
  1040                     if (evName == null) {
  1041                         final StringBuilder sb = new StringBuilder();
  1042                         sb.append("Unexpected string parameter name.");
  1043                         if (dataName != null) {
  1044                             sb.append(" Try \"").append(dataName).append("\"");
  1045                         }
  1046                         error(sb.toString(), ee);
  1047                     }
  1048                     params.append(evName);
  1049                     params.append(", \"");
  1050                     params.append(ve.getSimpleName().toString());
  1051                     params.append("\"");
  1052                 }
  1053                 params.append(")");
  1054                 continue;
  1055             }
  1056             String rn = fqn(ve.asType(), ee);
  1057             int last = rn.lastIndexOf('.');
  1058             if (last >= 0) {
  1059                 rn = rn.substring(last + 1);
  1060             }
  1061             if (rn.equals(className)) {
  1062                 params.append(className).append(".this");
  1063                 continue;
  1064             }
  1065             error(
  1066                 "@On method can only accept String named 'id' or " + className + " arguments",
  1067                 ee
  1068             );
  1069         }
  1070         return params;
  1071     }
  1072     
  1073     
  1074     private CharSequence wrapPropName(
  1075         ExecutableElement ee, String className, String propName, String propValue
  1076     ) {
  1077         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
  1078         StringBuilder params = new StringBuilder();
  1079         boolean first = true;
  1080         for (VariableElement ve : ee.getParameters()) {
  1081             if (!first) {
  1082                 params.append(", ");
  1083             }
  1084             first = false;
  1085             if (ve.asType() == stringType) {
  1086                 if (propName != null && ve.getSimpleName().contentEquals(propName)) {
  1087                     params.append('"').append(propValue).append('"');
  1088                 } else {
  1089                     error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
  1090                 }
  1091                 continue;
  1092             }
  1093             String rn = fqn(ve.asType(), ee);
  1094             int last = rn.lastIndexOf('.');
  1095             if (last >= 0) {
  1096                 rn = rn.substring(last + 1);
  1097             }
  1098             if (rn.equals(className)) {
  1099                 params.append(className).append(".this");
  1100                 continue;
  1101             }
  1102             error(
  1103                 "@OnPrprtChange method can only accept String or " + className + " arguments",
  1104                 ee);
  1105         }
  1106         return params;
  1107     }
  1108     
  1109     private boolean isModel(TypeMirror tm) {
  1110         final Element e = processingEnv.getTypeUtils().asElement(tm);
  1111         if (e == null) {
  1112             return false;
  1113         }
  1114         for (Element ch : e.getEnclosedElements()) {
  1115             if (ch.getKind() == ElementKind.METHOD) {
  1116                 ExecutableElement ee = (ExecutableElement)ch;
  1117                 if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
  1118                     return true;
  1119                 }
  1120             }
  1121         }
  1122         return models.values().contains(e.getSimpleName().toString());
  1123     }
  1124 
  1125     private void writeStringArray(List<String> strings, Writer w) throws IOException {
  1126         w.write("new String[] {\n");
  1127         String sep = "";
  1128         for (String n : strings) {
  1129             w.write(sep);
  1130             if (n == null) {
  1131                 w.write("    null");
  1132             } else {
  1133                 w.write("    \"" + n + "\"");
  1134             }
  1135             sep = ",\n";
  1136         }
  1137         w.write("\n  }");
  1138     }
  1139     
  1140     private void writeToString(Prprt[] props, Writer w) throws IOException {
  1141         w.write("  public String toString() {\n");
  1142         w.write("    StringBuilder sb = new StringBuilder();\n");
  1143         w.write("    sb.append('{');\n");
  1144         String sep = "";
  1145         for (Prprt p : props) {
  1146             w.write(sep);
  1147             w.append("    sb.append(\"" + p.name() + ": \");\n");
  1148             w.append("    sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
  1149             w.append(p.name()).append("));\n");
  1150             sep =    "    sb.append(',');\n";
  1151         }
  1152         w.write("    sb.append('}');\n");
  1153         w.write("    return sb.toString();\n");
  1154         w.write("  }\n");
  1155     }
  1156     private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
  1157         w.write("  public " + className + " clone() {\n");
  1158         w.write("    " + className + " ret = new " + className + "();\n");
  1159         for (Prprt p : props) {
  1160             if (!p.array()) {
  1161                 boolean isModel[] = { false };
  1162                 boolean isEnum[] = { false };
  1163                 boolean isPrimitive[] = { false };
  1164                 checkType(p, isModel, isEnum, isPrimitive);
  1165                 if (!isModel[0]) {
  1166                     w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
  1167                     continue;
  1168                 }
  1169                 w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
  1170             } else {
  1171                 w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
  1172             }
  1173         }
  1174         
  1175         w.write("    return ret;\n");
  1176         w.write("  }\n");
  1177     }
  1178 
  1179     private String inPckName(Element e) {
  1180         StringBuilder sb = new StringBuilder();
  1181         while (e.getKind() != ElementKind.PACKAGE) {
  1182             if (sb.length() == 0) {
  1183                 sb.append(e.getSimpleName());
  1184             } else {
  1185                 sb.insert(0, '.');
  1186                 sb.insert(0, e.getSimpleName());
  1187             }
  1188             e = e.getEnclosingElement();
  1189         }
  1190         return sb.toString();
  1191     }
  1192 
  1193     private String fqn(TypeMirror pt, Element relative) {
  1194         if (pt.getKind() == TypeKind.ERROR) {
  1195             final Elements eu = processingEnv.getElementUtils();
  1196             PackageElement pckg = eu.getPackageOf(relative);
  1197             return pckg.getQualifiedName() + "." + pt.toString();
  1198         }
  1199         return pt.toString();
  1200     }
  1201 
  1202     private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
  1203         TypeMirror tm;
  1204         try {
  1205             String ret = p.typeName(processingEnv);
  1206             TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
  1207             if (e == null) {
  1208                 isModel[0] = true;
  1209                 isEnum[0] = false;
  1210                 isPrimitive[0] = false;
  1211                 return ret;
  1212             }
  1213             tm = e.asType();
  1214         } catch (MirroredTypeException ex) {
  1215             tm = ex.getTypeMirror();
  1216         }
  1217         tm = processingEnv.getTypeUtils().erasure(tm);
  1218         isPrimitive[0] = tm.getKind().isPrimitive();
  1219         final Element e = processingEnv.getTypeUtils().asElement(tm);
  1220         final Model m = e == null ? null : e.getAnnotation(Model.class);
  1221         
  1222         String ret;
  1223         if (m != null) {
  1224             ret = findPkgName(e) + '.' + m.className();
  1225             isModel[0] = true;
  1226             models.put(e, m.className());
  1227         } else if (findModelForMthd(e)) {
  1228             ret = ((TypeElement)e).getQualifiedName().toString();
  1229             isModel[0] = true;
  1230         } else {
  1231             ret = tm.toString();
  1232         }
  1233         TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
  1234         enm = processingEnv.getTypeUtils().erasure(enm);
  1235         isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
  1236         return ret;
  1237     }
  1238     
  1239     private static boolean findModelForMthd(Element clazz) {
  1240         if (clazz == null) {
  1241             return false;
  1242         }
  1243         for (Element e : clazz.getEnclosedElements()) {
  1244             if (e.getKind() == ElementKind.METHOD) {
  1245                 ExecutableElement ee = (ExecutableElement)e;
  1246                 if (
  1247                     ee.getSimpleName().contentEquals("modelFor") &&
  1248                     ee.getParameters().isEmpty()
  1249                 ) {
  1250                     return true;
  1251                 }
  1252             }
  1253         }
  1254         return false;
  1255     }
  1256 
  1257     private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
  1258         List<String> params = new ArrayList<>();
  1259 
  1260         for (int pos = 0; ;) {
  1261             int next = url.indexOf('{', pos);
  1262             if (next == -1) {
  1263                 assembleURL.append('"')
  1264                     .append(url.substring(pos))
  1265                     .append('"');
  1266                 return params;
  1267             }
  1268             int close = url.indexOf('}', next);
  1269             if (close == -1) {
  1270                 error("Unbalanced '{' and '}' in " + url, e);
  1271                 return params;
  1272             }
  1273             final String paramName = url.substring(next + 1, close);
  1274             params.add(paramName);
  1275             assembleURL.append('"')
  1276                 .append(url.substring(pos, next))
  1277                 .append("\" + ").append(paramName).append(" + ");
  1278             pos = close + 1;
  1279         }
  1280     }
  1281 
  1282     private static Prprt findPrprt(Prprt[] properties, String propName) {
  1283         for (Prprt p : properties) {
  1284             if (propName.equals(p.name())) {
  1285                 return p;
  1286             }
  1287         }
  1288         return null;
  1289     }
  1290 
  1291     private boolean isPrimitive(String type) {
  1292         return 
  1293             "int".equals(type) ||
  1294             "double".equals(type) ||
  1295             "long".equals(type) ||
  1296             "short".equals(type) ||
  1297             "byte".equals(type) ||
  1298             "float".equals(type);
  1299     }
  1300 
  1301     private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
  1302         Set<String> names = new HashSet<>();
  1303         for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
  1304             if (e.getValue().contains(derivedProp)) {
  1305                 names.add(e.getKey());
  1306             }
  1307         }
  1308         return names;
  1309     }
  1310     
  1311     private Prprt[] createProps(Element e, Property[] arr) {
  1312         Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
  1313         Prprt[] prev = verify.put(e, ret);
  1314         if (prev != null) {
  1315             error("Two sets of properties for ", e);
  1316         }
  1317         return ret;
  1318     }
  1319     
  1320     private static class Prprt {
  1321         private final Element e;
  1322         private final AnnotationMirror tm;
  1323         private final Property p;
  1324 
  1325         public Prprt(Element e, AnnotationMirror tm, Property p) {
  1326             this.e = e;
  1327             this.tm = tm;
  1328             this.p = p;
  1329         }
  1330         
  1331         String name() {
  1332             return p.name();
  1333         }
  1334         
  1335         boolean array() {
  1336             return p.array();
  1337         }
  1338 
  1339         String typeName(ProcessingEnvironment env) {
  1340             try {
  1341                 return p.type().getName();
  1342             } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) {
  1343                 for (Object v : getAnnoValues(env)) {
  1344                     String s = v.toString().replace(" ", "");
  1345                     if (s.startsWith("type=") && s.endsWith(".class")) {
  1346                         return s.substring(5, s.length() - 6);
  1347                     }
  1348                 }
  1349                 throw ex;
  1350             }
  1351         }
  1352         
  1353         
  1354         static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
  1355             if (arr.length == 0) {
  1356                 return new Prprt[0];
  1357             }
  1358             
  1359             if (e.getKind() != ElementKind.CLASS) {
  1360                 throw new IllegalStateException("" + e.getKind());
  1361             }
  1362             TypeElement te = (TypeElement)e;
  1363             List<? extends AnnotationValue> val = null;
  1364             for (AnnotationMirror an : te.getAnnotationMirrors()) {
  1365                 for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
  1366                     if (entry.getKey().getSimpleName().contentEquals("properties")) {
  1367                         val = (List)entry.getValue().getValue();
  1368                         break;
  1369                     }
  1370                 }
  1371             }
  1372             if (val == null || val.size() != arr.length) {
  1373                 pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
  1374                 return new Prprt[0];
  1375             }
  1376             Prprt[] ret = new Prprt[arr.length];
  1377             BIG: for (int i = 0; i < ret.length; i++) {
  1378                 AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
  1379                 ret[i] = new Prprt(e, am, arr[i]);
  1380                 
  1381             }
  1382             return ret;
  1383         }
  1384         
  1385         private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
  1386             try {
  1387                 Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
  1388                 Method m = trees.getMethod("instance", ProcessingEnvironment.class);
  1389                 Object instance = m.invoke(null, pe);
  1390                 m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
  1391                 Object path = m.invoke(instance, e, tm);
  1392                 m = path.getClass().getMethod("getLeaf");
  1393                 Object leaf = m.invoke(path);
  1394                 m = leaf.getClass().getMethod("getArguments");
  1395                 return (List)m.invoke(leaf);
  1396             } catch (Exception ex) {
  1397                 return Collections.emptyList();
  1398             }
  1399         }
  1400     }
  1401     
  1402 }