javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 02 May 2013 14:56:25 +0200
branchelements
changeset 1078 fc4fc1633445
parent 1073 9321b4016d5c
permissions -rw-r--r--
Don't forget to include KOList
     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("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(e == null ? null : ");
   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(" = ret[" + cnt + "] == null ? null : ");
   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("}\n");
   284             } finally {
   285                 w.close();
   286             }
   287         } catch (IOException ex) {
   288             error("Can't create " + className + ".java", e);
   289             return false;
   290         }
   291         return ok;
   292     }
   293     
   294     private boolean processPage(Element e) {
   295         boolean ok = true;
   296         Page p = e.getAnnotation(Page.class);
   297         if (p == null) {
   298             return true;
   299         }
   300         String pkg = findPkgName(e);
   301 
   302             ProcessPage pp;
   303             ElementGenerator eGen = new ElementGenerator(processingEnv);
   304             try {
   305                 InputStream is = openStream(pkg, p.xhtml());
   306                 pp = ProcessPage.readPage(is);
   307                 is.close();
   308             } catch (IOException iOException) {
   309                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
   310                 return false;
   311             }
   312             Writer w;
   313             String className = p.className();
   314             if (className.isEmpty()) {
   315                 int indx = p.xhtml().indexOf('.');
   316                 className = p.xhtml().substring(0, indx);
   317             }
   318             try {
   319                 FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   320                 w = new OutputStreamWriter(java.openOutputStream());
   321                 try {
   322                     w.append("package " + pkg + ";\n");
   323                     w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   324                     w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   325                     w.append("final class ").append(className).append(" {\n");
   326                     w.append("  private boolean locked;\n");
   327                     if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
   328                         return false;
   329                     }
   330                     for (String id : pp.ids()) {
   331                         String tag = pp.tagNameForId(id);
   332                         String type = eGen.getType(pkg, tag, e);
   333                         w.append("  ").append("public final ").
   334                             append(type).append(' ').append(cnstnt(id)).append(" = new ").
   335                             append(type).append("(\"").append(id).append("\");\n");
   336                     }
   337                     List<String> propsGetSet = new ArrayList<String>();
   338                     Map<String, Collection<String>> functionDeps = new HashMap<>();
   339                     Map<String,Collection<String>> propsDeps = new HashMap<String, Collection<String>>();
   340                     List<String> functions = new ArrayList<>();
   341                     Prprt[] props = createProps(e, p.properties());
   342                     if (!generateComputedProperties(w, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
   343                         ok = false;
   344                     }
   345                     if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
   346                         ok = false;
   347                     }
   348                     if (!generateProperties(e, w, props, propsGetSet, propsDeps, functionDeps)) {
   349                         ok = false;
   350                     }
   351                     if (!generateFunctions(e, w, className, e.getEnclosedElements(), functions)) {
   352                         ok = false;
   353                     }
   354                     if (!generateReceive(e, w, className, e.getEnclosedElements(), functions)) {
   355                         ok = false;
   356                     }                    
   357                     w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   358                     if (!propsGetSet.isEmpty()) {
   359                         w.write("public " + className + " applyBindings() {\n");
   360                         w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
   361                         w.write(className + ".class, this, ");
   362                         writeStringArray(propsGetSet, w);
   363                         w.append(", ");
   364                         writeStringArray(functions, w);
   365                         w.write(");\n  return this;\n}\n");
   366 
   367                         w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
   368                         w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
   369                         w.write("}\n");
   370                     }
   371                     w.append("}\n");
   372                 } finally {
   373                     w.close();
   374                 }
   375             } catch (IOException ex) {
   376                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   377                 return false;
   378             }
   379         return ok;
   380     }
   381 
   382     private static String cnstnt(String id) {
   383         return id.replace('.', '_').replace('-', '_');
   384     }
   385 
   386     private boolean initializeOnClick(
   387         String className, TypeElement type, Writer w, ProcessPage pp
   388     ) throws IOException {
   389         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   390         { //for (Element clazz : pe.getEnclosedElements()) {
   391           //  if (clazz.getKind() != ElementKind.CLASS) {
   392             //    continue;
   393            // }
   394             w.append("  public ").append(className).append("() {\n");
   395             StringBuilder dispatch = new StringBuilder();
   396             int dispatchCnt = 0;
   397             for (Element method : type.getEnclosedElements()) {
   398                 On oc = method.getAnnotation(On.class);
   399                 if (oc != null) {
   400                     for (String id : oc.id()) {
   401                         if (pp.tagNameForId(id) == null) {
   402                             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   403                             return false;
   404                         }
   405                         ExecutableElement ee = (ExecutableElement)method;
   406                         StringBuilder params = new StringBuilder();
   407                         {
   408                             boolean first = true;
   409                             for (VariableElement ve : ee.getParameters()) {
   410                                 if (!first) {
   411                                     params.append(", ");
   412                                 }
   413                                 first = false;
   414                                 if (ve.asType() == stringType) {
   415                                     if (ve.getSimpleName().contentEquals("id")) {
   416                                         params.append('"').append(id).append('"');
   417                                         continue;
   418                                     }
   419                                     params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(ev, \"");
   420                                     params.append(ve.getSimpleName().toString());
   421                                     params.append("\")");
   422                                     continue;
   423                                 }
   424                                 if (processingEnv.getTypeUtils().getPrimitiveType(TypeKind.DOUBLE) == ve.asType()) {
   425                                     params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(ev, \"");
   426                                     params.append(ve.getSimpleName().toString());
   427                                     params.append("\")");
   428                                     continue;
   429                                 }
   430                                 String rn = ve.asType().toString();
   431                                 int last = rn.lastIndexOf('.');
   432                                 if (last >= 0) {
   433                                     rn = rn.substring(last + 1);
   434                                 }
   435                                 if (rn.equals(className)) {
   436                                     params.append(className).append(".this");
   437                                     continue;
   438                                 }
   439                                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   440                                     "@On method can only accept String named 'id' or " + className + " arguments",
   441                                     ee
   442                                 );
   443                                 return false;
   444                             }
   445                         }
   446                         if (!ee.getModifiers().contains(Modifier.STATIC)) {
   447                             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   448                             return false;
   449                         }
   450                         if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   451                             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   452                             return false;
   453                         }
   454                         w.append("  OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
   455                             append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
   456 
   457                         dispatch.
   458                             append("      case ").append(dispatchCnt).append(": ").
   459                             append(type.getSimpleName().toString()).
   460                             append('.').append(ee.getSimpleName()).append("(").
   461                             append(params).
   462                             append("); break;\n");
   463                         
   464                         dispatchCnt++;
   465                     }
   466                 }
   467             }
   468             w.append("  }\n");
   469             if (dispatchCnt > 0) {
   470                 w.append("class OnDispatch implements OnHandler {\n");
   471                 w.append("  private final int dispatch;\n");
   472                 w.append("  OnDispatch(int d) { dispatch = d; }\n");
   473                 w.append("  public void onEvent(Object ev) {\n");
   474                 w.append("    switch (dispatch) {\n");
   475                 w.append(dispatch);
   476                 w.append("    }\n");
   477                 w.append("  }\n");
   478                 w.append("}\n");
   479             }
   480             
   481 
   482         }
   483         return true;
   484     }
   485 
   486     @Override
   487     public Iterable<? extends Completion> getCompletions(
   488         Element element, AnnotationMirror annotation, 
   489         ExecutableElement member, String userText
   490     ) {
   491         if (!userText.startsWith("\"")) {
   492             return Collections.emptyList();
   493         }
   494         
   495         Element cls = findClass(element);
   496         Page p = cls.getAnnotation(Page.class);
   497         PackageElement pe = (PackageElement) cls.getEnclosingElement();
   498         String pkg = pe.getQualifiedName().toString();
   499         ProcessPage pp;
   500         try {
   501             InputStream is = openStream(pkg, p.xhtml());
   502             pp = ProcessPage.readPage(is);
   503             is.close();
   504         } catch (IOException iOException) {
   505             return Collections.emptyList();
   506         }
   507         
   508         List<Completion> cc = new ArrayList<Completion>();
   509         userText = userText.substring(1);
   510         for (String id : pp.ids()) {
   511             if (id.startsWith(userText)) {
   512                 cc.add(Completions.of("\"" + id + "\"", id));
   513             }
   514         }
   515         return cc;
   516     }
   517     
   518     private static Element findClass(Element e) {
   519         if (e == null) {
   520             return null;
   521         }
   522         Page p = e.getAnnotation(Page.class);
   523         if (p != null) {
   524             return e;
   525         }
   526         return e.getEnclosingElement();
   527     }
   528 
   529     private boolean generateProperties(
   530         Element where,
   531         Writer w, Prprt[] properties,
   532         Collection<String> props, 
   533         Map<String,Collection<String>> deps,
   534         Map<String,Collection<String>> functionDeps
   535     ) throws IOException {
   536         boolean ok = true;
   537         for (Prprt p : properties) {
   538             final String tn;
   539             tn = typeName(where, p);
   540             String[] gs = toGetSet(p.name(), tn, p.array());
   541 
   542             if (p.array()) {
   543                 w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
   544                     + p.name() + "\"");
   545                 Collection<String> dependants = deps.get(p.name());
   546                 if (dependants != null) {
   547                     for (String depProp : dependants) {
   548                         w.write(", ");
   549                         w.write('\"');
   550                         w.write(depProp);
   551                         w.write('\"');
   552                     }
   553                 }
   554                 w.write(")");
   555                 
   556                 dependants = functionDeps.get(p.name());
   557                 if (dependants != null) {
   558                     w.write(".onChange(new Runnable() { public void run() {\n");
   559                     for (String call : dependants) {
   560                         w.append(call);
   561                     }
   562                     w.write("}})");
   563                 }
   564                 w.write(";\n");
   565                 
   566                 w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   567                 w.write("  if (locked) throw new IllegalStateException();\n");
   568                 w.write("  prop_" + p.name() + ".assign(ko);\n");
   569                 w.write("  return prop_" + p.name() + ";\n");
   570                 w.write("}\n");
   571             } else {
   572                 w.write("private " + tn + " prop_" + p.name() + ";\n");
   573                 w.write("public " + tn + " " + gs[0] + "() {\n");
   574                 w.write("  if (locked) throw new IllegalStateException();\n");
   575                 w.write("  return prop_" + p.name() + ";\n");
   576                 w.write("}\n");
   577                 w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   578                 w.write("  if (locked) throw new IllegalStateException();\n");
   579                 w.write("  prop_" + p.name() + " = v;\n");
   580                 w.write("  if (ko != null) {\n");
   581                 w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   582                 Collection<String> dependants = deps.get(p.name());
   583                 if (dependants != null) {
   584                     for (String depProp : dependants) {
   585                         w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   586                     }
   587                 }
   588                 w.write("  }\n");
   589                 dependants = functionDeps.get(p.name());
   590                 if (dependants != null) {
   591                     for (String call : dependants) {
   592                         w.append(call);
   593                     }
   594                 }
   595                 w.write("}\n");
   596             }
   597             
   598             props.add(p.name());
   599             props.add(gs[2]);
   600             props.add(gs[3]);
   601             props.add(gs[0]);
   602         }
   603         return ok;
   604     }
   605 
   606     private boolean generateComputedProperties(
   607         Writer w, Prprt[] fixedProps,
   608         Collection<? extends Element> arr, Collection<String> props,
   609         Map<String,Collection<String>> deps
   610     ) throws IOException {
   611         boolean ok = true;
   612         for (Element e : arr) {
   613             if (e.getKind() != ElementKind.METHOD) {
   614                 continue;
   615             }
   616             if (e.getAnnotation(ComputedProperty.class) == null) {
   617                 continue;
   618             }
   619             ExecutableElement ee = (ExecutableElement)e;
   620             final TypeMirror rt = ee.getReturnType();
   621             final Types tu = processingEnv.getTypeUtils();
   622             TypeMirror ert = tu.erasure(rt);
   623             String tn = fqn(ert, ee);
   624             boolean array = false;
   625             if (tn.equals("java.util.List")) {
   626                 array = true;
   627             }
   628             
   629             final String sn = ee.getSimpleName().toString();
   630             String[] gs = toGetSet(sn, tn, array);
   631             
   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, Writer body, String className, 
   783         List<? extends Element> enclosedElements, List<String> functions
   784     ) throws IOException {
   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("private void ").append(n).append("(Object data, Object ev) {\n");
   808             body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   809             body.append(wrapParams(e, null, className, "ev", "data"));
   810             body.append(");\n");
   811             body.append("}\n");
   812             
   813             functions.add(n);
   814             functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
   815         }
   816         return true;
   817     }
   818 
   819     private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
   820         Prprt[] properties, String className, 
   821         Map<String, Collection<String>> functionDeps
   822     ) {
   823         for (Element m : clazz.getEnclosedElements()) {
   824             if (m.getKind() != ElementKind.METHOD) {
   825                 continue;
   826             }
   827             ExecutableElement e = (ExecutableElement) m;
   828             OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
   829             if (onPC == null) {
   830                 continue;
   831             }
   832             for (String pn : onPC.value()) {
   833                 if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   834                     error("No Prprt named '" + pn + "' in the model", clazz);
   835                     return false;
   836                 }
   837             }
   838             if (!e.getModifiers().contains(Modifier.STATIC)) {
   839                 error("@OnPrprtChange method needs to be static", e);
   840                 return false;
   841             }
   842             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   843                 error("@OnPrprtChange method cannot be private", e);
   844                 return false;
   845             }
   846             if (e.getReturnType().getKind() != TypeKind.VOID) {
   847                 error("@OnPrprtChange method should return void", e);
   848                 return false;
   849             }
   850             String n = e.getSimpleName().toString();
   851             
   852             
   853             for (String pn : onPC.value()) {
   854                 StringBuilder call = new StringBuilder();
   855                 call.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   856                 call.append(wrapPropName(e, className, "name", pn));
   857                 call.append(");\n");
   858                 
   859                 Collection<String> change = functionDeps.get(pn);
   860                 if (change == null) {
   861                     change = new ArrayList<>();
   862                     functionDeps.put(pn, change);
   863                 }
   864                 change.add(call.toString());
   865                 for (String dpn : findDerivedFrom(propDeps, pn)) {
   866                     change = functionDeps.get(dpn);
   867                     if (change == null) {
   868                         change = new ArrayList<>();
   869                         functionDeps.put(dpn, change);
   870                     }
   871                     change.add(call.toString());
   872                 }
   873             }
   874         }
   875         return true;
   876     }
   877     
   878     private boolean generateReceive(
   879         Element clazz, Writer body, String className, 
   880         List<? extends Element> enclosedElements, List<String> functions
   881     ) throws IOException {
   882         for (Element m : enclosedElements) {
   883             if (m.getKind() != ElementKind.METHOD) {
   884                 continue;
   885             }
   886             ExecutableElement e = (ExecutableElement)m;
   887             OnReceive onR = e.getAnnotation(OnReceive.class);
   888             if (onR == null) {
   889                 continue;
   890             }
   891             if (!e.getModifiers().contains(Modifier.STATIC)) {
   892                 error("@OnReceive method needs to be static", e);
   893                 return false;
   894             }
   895             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   896                 error("@OnReceive method cannot be private", e);
   897                 return false;
   898             }
   899             if (e.getReturnType().getKind() != TypeKind.VOID) {
   900                 error("@OnReceive method should return void", e);
   901                 return false;
   902             }
   903             String modelClass = null;
   904             boolean expectsList = false;
   905             List<String> args = new ArrayList<>();
   906             {
   907                 for (VariableElement ve : e.getParameters()) {
   908                     TypeMirror modelType = null;
   909                     if (ve.asType().toString().equals(className)) {
   910                         args.add(className + ".this");
   911                     } else if (isModel(ve.asType())) {
   912                         modelType = ve.asType();
   913                     } else if (ve.asType().getKind() == TypeKind.ARRAY) {
   914                         modelType = ((ArrayType)ve.asType()).getComponentType();
   915                         expectsList = true;
   916                     }
   917                     if (modelType != null) {
   918                         if (modelClass != null) {
   919                             error("There can be only one model class among arguments", e);
   920                         } else {
   921                             modelClass = modelType.toString();
   922                             if (expectsList) {
   923                                 args.add("arr");
   924                             } else {
   925                                 args.add("arr[0]");
   926                             }
   927                         }
   928                     }
   929                 }
   930             }
   931             if (modelClass == null) {
   932                 error("The method needs to have one @Model class as parameter", e);
   933             }
   934             String n = e.getSimpleName().toString();
   935             body.append("public void ").append(n).append("(");
   936             StringBuilder assembleURL = new StringBuilder();
   937             String jsonpVarName = null;
   938             {
   939                 String sep = "";
   940                 boolean skipJSONP = onR.jsonp().isEmpty();
   941                 for (String p : findParamNames(e, onR.url(), assembleURL)) {
   942                     if (!skipJSONP && p.equals(onR.jsonp())) {
   943                         skipJSONP = true;
   944                         jsonpVarName = p;
   945                         continue;
   946                     }
   947                     body.append(sep);
   948                     body.append("String ").append(p);
   949                     sep = ", ";
   950                 }
   951                 if (!skipJSONP) {
   952                     error(
   953                         "Name of jsonp attribute ('" + onR.jsonp() + 
   954                         "') is not used in url attribute '" + onR.url() + "'", e
   955                     );
   956                 }
   957             }
   958             body.append(") {\n");
   959             body.append("  final Object[] result = { null };\n");
   960             body.append(
   961                 "  class ProcessResult implements Runnable {\n" +
   962                 "    @Override\n" +
   963                 "    public void run() {\n" +
   964                 "      Object value = result[0];\n");
   965             body.append(
   966                 "      " + modelClass + "[] arr;\n");
   967             body.append(
   968                 "      if (value instanceof Object[]) {\n" +
   969                 "        Object[] data = ((Object[])value);\n" +
   970                 "        arr = new " + modelClass + "[data.length];\n" +
   971                 "        for (int i = 0; i < data.length; i++) {\n" +
   972                 "          arr[i] = new " + modelClass + "(data[i]);\n" +
   973                 "        }\n" +
   974                 "      } else {\n" +
   975                 "        arr = new " + modelClass + "[1];\n" +
   976                 "        arr[0] = new " + modelClass + "(value);\n" +
   977                 "      }\n"
   978             );
   979             {
   980                 body.append(clazz.getSimpleName()).append(".").append(n).append("(");
   981                 String sep = "";
   982                 for (String arg : args) {
   983                     body.append(sep);
   984                     body.append(arg);
   985                     sep = ", ";
   986                 }
   987                 body.append(");\n");
   988             }
   989             body.append(
   990                 "    }\n" +
   991                 "  }\n"
   992             );
   993             body.append("  ProcessResult pr = new ProcessResult();\n");
   994             if (jsonpVarName != null) {
   995                 body.append("  String ").append(jsonpVarName).
   996                     append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n");
   997             }
   998             body.append("  org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n      ");
   999             body.append(assembleURL);
  1000             body.append(", result, pr, ").append(jsonpVarName).append("\n  );\n");
  1001 //            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
  1002 //            body.append(wrapParams(e, null, className, "ev", "data"));
  1003 //            body.append(");\n");
  1004             body.append("}\n");
  1005         }
  1006         return true;
  1007     }
  1008 
  1009     private CharSequence wrapParams(
  1010         ExecutableElement ee, String id, String className, String evName, String dataName
  1011     ) {
  1012         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
  1013         StringBuilder params = new StringBuilder();
  1014         boolean first = true;
  1015         for (VariableElement ve : ee.getParameters()) {
  1016             if (!first) {
  1017                 params.append(", ");
  1018             }
  1019             first = false;
  1020             String toCall = null;
  1021             if (ve.asType() == stringType) {
  1022                 if (ve.getSimpleName().contentEquals("id")) {
  1023                     params.append('"').append(id).append('"');
  1024                     continue;
  1025                 }
  1026                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
  1027             }
  1028             if (ve.asType().getKind() == TypeKind.DOUBLE) {
  1029                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
  1030             }
  1031             if (ve.asType().getKind() == TypeKind.INT) {
  1032                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
  1033             }
  1034             if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
  1035                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
  1036             }
  1037 
  1038             if (toCall != null) {
  1039                 params.append(toCall);
  1040                 if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
  1041                     params.append(dataName);
  1042                     params.append(", null");
  1043                 } else {
  1044                     if (evName == null) {
  1045                         final StringBuilder sb = new StringBuilder();
  1046                         sb.append("Unexpected string parameter name.");
  1047                         if (dataName != null) {
  1048                             sb.append(" Try \"").append(dataName).append("\"");
  1049                         }
  1050                         error(sb.toString(), ee);
  1051                     }
  1052                     params.append(evName);
  1053                     params.append(", \"");
  1054                     params.append(ve.getSimpleName().toString());
  1055                     params.append("\"");
  1056                 }
  1057                 params.append(")");
  1058                 continue;
  1059             }
  1060             String rn = fqn(ve.asType(), ee);
  1061             int last = rn.lastIndexOf('.');
  1062             if (last >= 0) {
  1063                 rn = rn.substring(last + 1);
  1064             }
  1065             if (rn.equals(className)) {
  1066                 params.append(className).append(".this");
  1067                 continue;
  1068             }
  1069             error(
  1070                 "@On method can only accept String named 'id' or " + className + " arguments",
  1071                 ee
  1072             );
  1073         }
  1074         return params;
  1075     }
  1076     
  1077     
  1078     private CharSequence wrapPropName(
  1079         ExecutableElement ee, String className, String propName, String propValue
  1080     ) {
  1081         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
  1082         StringBuilder params = new StringBuilder();
  1083         boolean first = true;
  1084         for (VariableElement ve : ee.getParameters()) {
  1085             if (!first) {
  1086                 params.append(", ");
  1087             }
  1088             first = false;
  1089             if (ve.asType() == stringType) {
  1090                 if (propName != null && ve.getSimpleName().contentEquals(propName)) {
  1091                     params.append('"').append(propValue).append('"');
  1092                 } else {
  1093                     error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
  1094                 }
  1095                 continue;
  1096             }
  1097             String rn = fqn(ve.asType(), ee);
  1098             int last = rn.lastIndexOf('.');
  1099             if (last >= 0) {
  1100                 rn = rn.substring(last + 1);
  1101             }
  1102             if (rn.equals(className)) {
  1103                 params.append(className).append(".this");
  1104                 continue;
  1105             }
  1106             error(
  1107                 "@OnPrprtChange method can only accept String or " + className + " arguments",
  1108                 ee);
  1109         }
  1110         return params;
  1111     }
  1112     
  1113     private boolean isModel(TypeMirror tm) {
  1114         final Element e = processingEnv.getTypeUtils().asElement(tm);
  1115         if (e == null) {
  1116             return false;
  1117         }
  1118         for (Element ch : e.getEnclosedElements()) {
  1119             if (ch.getKind() == ElementKind.METHOD) {
  1120                 ExecutableElement ee = (ExecutableElement)ch;
  1121                 if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
  1122                     return true;
  1123                 }
  1124             }
  1125         }
  1126         return models.values().contains(e.getSimpleName().toString());
  1127     }
  1128 
  1129     private void writeStringArray(List<String> strings, Writer w) throws IOException {
  1130         w.write("new String[] {\n");
  1131         String sep = "";
  1132         for (String n : strings) {
  1133             w.write(sep);
  1134             if (n == null) {
  1135                 w.write("    null");
  1136             } else {
  1137                 w.write("    \"" + n + "\"");
  1138             }
  1139             sep = ",\n";
  1140         }
  1141         w.write("\n  }");
  1142     }
  1143     
  1144     private void writeToString(Prprt[] props, Writer w) throws IOException {
  1145         w.write("  public String toString() {\n");
  1146         w.write("    StringBuilder sb = new StringBuilder();\n");
  1147         w.write("    sb.append('{');\n");
  1148         String sep = "";
  1149         for (Prprt p : props) {
  1150             w.write(sep);
  1151             w.append("    sb.append('\"').append(\"" + p.name() + "\")");
  1152                 w.append(".append('\"').append(\":\");\n");
  1153             w.append("    sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
  1154             w.append(p.name()).append("));\n");
  1155             sep =    "    sb.append(',');\n";
  1156         }
  1157         w.write("    sb.append('}');\n");
  1158         w.write("    return sb.toString();\n");
  1159         w.write("  }\n");
  1160     }
  1161     private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
  1162         w.write("  public " + className + " clone() {\n");
  1163         w.write("    " + className + " ret = new " + className + "();\n");
  1164         for (Prprt p : props) {
  1165             if (!p.array()) {
  1166                 boolean isModel[] = { false };
  1167                 boolean isEnum[] = { false };
  1168                 boolean isPrimitive[] = { false };
  1169                 checkType(p, isModel, isEnum, isPrimitive);
  1170                 if (!isModel[0]) {
  1171                     w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
  1172                     continue;
  1173                 }
  1174                 w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
  1175             } else {
  1176                 w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
  1177             }
  1178         }
  1179         
  1180         w.write("    return ret;\n");
  1181         w.write("  }\n");
  1182     }
  1183 
  1184     private String inPckName(Element e) {
  1185         StringBuilder sb = new StringBuilder();
  1186         while (e.getKind() != ElementKind.PACKAGE) {
  1187             if (sb.length() == 0) {
  1188                 sb.append(e.getSimpleName());
  1189             } else {
  1190                 sb.insert(0, '.');
  1191                 sb.insert(0, e.getSimpleName());
  1192             }
  1193             e = e.getEnclosingElement();
  1194         }
  1195         return sb.toString();
  1196     }
  1197 
  1198     private String fqn(TypeMirror pt, Element relative) {
  1199         if (pt.getKind() == TypeKind.ERROR) {
  1200             final Elements eu = processingEnv.getElementUtils();
  1201             PackageElement pckg = eu.getPackageOf(relative);
  1202             return pckg.getQualifiedName() + "." + pt.toString();
  1203         }
  1204         return pt.toString();
  1205     }
  1206 
  1207     private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
  1208         TypeMirror tm;
  1209         try {
  1210             String ret = p.typeName(processingEnv);
  1211             TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
  1212             if (e == null) {
  1213                 isModel[0] = true;
  1214                 isEnum[0] = false;
  1215                 isPrimitive[0] = false;
  1216                 return ret;
  1217             }
  1218             tm = e.asType();
  1219         } catch (MirroredTypeException ex) {
  1220             tm = ex.getTypeMirror();
  1221         }
  1222         tm = processingEnv.getTypeUtils().erasure(tm);
  1223         isPrimitive[0] = tm.getKind().isPrimitive();
  1224         final Element e = processingEnv.getTypeUtils().asElement(tm);
  1225         final Model m = e == null ? null : e.getAnnotation(Model.class);
  1226         
  1227         String ret;
  1228         if (m != null) {
  1229             ret = findPkgName(e) + '.' + m.className();
  1230             isModel[0] = true;
  1231             models.put(e, m.className());
  1232         } else if (findModelForMthd(e)) {
  1233             ret = ((TypeElement)e).getQualifiedName().toString();
  1234             isModel[0] = true;
  1235         } else {
  1236             ret = tm.toString();
  1237         }
  1238         TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
  1239         enm = processingEnv.getTypeUtils().erasure(enm);
  1240         isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
  1241         return ret;
  1242     }
  1243     
  1244     private static boolean findModelForMthd(Element clazz) {
  1245         if (clazz == null) {
  1246             return false;
  1247         }
  1248         for (Element e : clazz.getEnclosedElements()) {
  1249             if (e.getKind() == ElementKind.METHOD) {
  1250                 ExecutableElement ee = (ExecutableElement)e;
  1251                 if (
  1252                     ee.getSimpleName().contentEquals("modelFor") &&
  1253                     ee.getParameters().isEmpty()
  1254                 ) {
  1255                     return true;
  1256                 }
  1257             }
  1258         }
  1259         return false;
  1260     }
  1261 
  1262     private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
  1263         List<String> params = new ArrayList<>();
  1264 
  1265         for (int pos = 0; ;) {
  1266             int next = url.indexOf('{', pos);
  1267             if (next == -1) {
  1268                 assembleURL.append('"')
  1269                     .append(url.substring(pos))
  1270                     .append('"');
  1271                 return params;
  1272             }
  1273             int close = url.indexOf('}', next);
  1274             if (close == -1) {
  1275                 error("Unbalanced '{' and '}' in " + url, e);
  1276                 return params;
  1277             }
  1278             final String paramName = url.substring(next + 1, close);
  1279             params.add(paramName);
  1280             assembleURL.append('"')
  1281                 .append(url.substring(pos, next))
  1282                 .append("\" + ").append(paramName).append(" + ");
  1283             pos = close + 1;
  1284         }
  1285     }
  1286 
  1287     private static Prprt findPrprt(Prprt[] properties, String propName) {
  1288         for (Prprt p : properties) {
  1289             if (propName.equals(p.name())) {
  1290                 return p;
  1291             }
  1292         }
  1293         return null;
  1294     }
  1295 
  1296     private boolean isPrimitive(String type) {
  1297         return 
  1298             "int".equals(type) ||
  1299             "double".equals(type) ||
  1300             "long".equals(type) ||
  1301             "short".equals(type) ||
  1302             "byte".equals(type) ||
  1303             "float".equals(type);
  1304     }
  1305 
  1306     private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
  1307         Set<String> names = new HashSet<>();
  1308         for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
  1309             if (e.getValue().contains(derivedProp)) {
  1310                 names.add(e.getKey());
  1311             }
  1312         }
  1313         return names;
  1314     }
  1315     
  1316     private Prprt[] createProps(Element e, Property[] arr) {
  1317         Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
  1318         Prprt[] prev = verify.put(e, ret);
  1319         if (prev != null) {
  1320             error("Two sets of properties for ", e);
  1321         }
  1322         return ret;
  1323     }
  1324     
  1325     private static class Prprt {
  1326         private final Element e;
  1327         private final AnnotationMirror tm;
  1328         private final Property p;
  1329 
  1330         public Prprt(Element e, AnnotationMirror tm, Property p) {
  1331             this.e = e;
  1332             this.tm = tm;
  1333             this.p = p;
  1334         }
  1335         
  1336         String name() {
  1337             return p.name();
  1338         }
  1339         
  1340         boolean array() {
  1341             return p.array();
  1342         }
  1343 
  1344         String typeName(ProcessingEnvironment env) {
  1345             try {
  1346                 return p.type().getName();
  1347             } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) {
  1348                 for (Object v : getAnnoValues(env)) {
  1349                     String s = v.toString().replace(" ", "");
  1350                     if (s.startsWith("type=") && s.endsWith(".class")) {
  1351                         return s.substring(5, s.length() - 6);
  1352                     }
  1353                 }
  1354                 throw ex;
  1355             }
  1356         }
  1357         
  1358         
  1359         static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
  1360             if (arr.length == 0) {
  1361                 return new Prprt[0];
  1362             }
  1363             
  1364             if (e.getKind() != ElementKind.CLASS) {
  1365                 throw new IllegalStateException("" + e.getKind());
  1366             }
  1367             TypeElement te = (TypeElement)e;
  1368             List<? extends AnnotationValue> val = null;
  1369             for (AnnotationMirror an : te.getAnnotationMirrors()) {
  1370                 for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
  1371                     if (entry.getKey().getSimpleName().contentEquals("properties")) {
  1372                         val = (List)entry.getValue().getValue();
  1373                         break;
  1374                     }
  1375                 }
  1376             }
  1377             if (val == null || val.size() != arr.length) {
  1378                 pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
  1379                 return new Prprt[0];
  1380             }
  1381             Prprt[] ret = new Prprt[arr.length];
  1382             BIG: for (int i = 0; i < ret.length; i++) {
  1383                 AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
  1384                 ret[i] = new Prprt(e, am, arr[i]);
  1385                 
  1386             }
  1387             return ret;
  1388         }
  1389         
  1390         private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
  1391             try {
  1392                 Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
  1393                 Method m = trees.getMethod("instance", ProcessingEnvironment.class);
  1394                 Object instance = m.invoke(null, pe);
  1395                 m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
  1396                 Object path = m.invoke(instance, e, tm);
  1397                 m = path.getClass().getMethod("getLeaf");
  1398                 Object leaf = m.invoke(path);
  1399                 m = leaf.getClass().getMethod("getArguments");
  1400                 return (List)m.invoke(leaf);
  1401             } catch (Exception ex) {
  1402                 return Collections.emptyList();
  1403             }
  1404         }
  1405     }
  1406     
  1407 }