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