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