json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java
author Jaroslav Tulach <jtulach@netbeans.org>
Wed, 09 Dec 2015 23:59:56 +0100
changeset 1026 cda94194f901
parent 1017 10427ce1c0ee
parent 1025 1122c615fffd
child 1030 02568f34628a
permissions -rw-r--r--
Merge of #257086 into default branch
     1 /**
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * Contributor(s):
    28  *
    29  * The Original Software is NetBeans. The Initial Developer of the Original
    30  * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    31  *
    32  * If you wish your version of this file to be governed by only the CDDL
    33  * or only the GPL Version 2, indicate your decision by adding
    34  * "[Contributor] elects to include this software in this distribution
    35  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    36  * single choice of license, a recipient has the option to distribute
    37  * your version of this file under either the CDDL, the GPL Version 2 or
    38  * to extend the choice of license to its licensees as provided above.
    39  * However, if you add GPL Version 2 code and therefore, elected the GPL
    40  * Version 2 license, then the option applies only if the new code is
    41  * made subject to such option by the copyright holder.
    42  */
    43 package org.netbeans.html.json.impl;
    44 
    45 import java.io.IOException;
    46 import java.io.OutputStreamWriter;
    47 import java.io.StringWriter;
    48 import java.io.Writer;
    49 import java.lang.annotation.AnnotationTypeMismatchException;
    50 import java.lang.annotation.IncompleteAnnotationException;
    51 import java.lang.reflect.Method;
    52 import java.util.ArrayList;
    53 import java.util.Arrays;
    54 import java.util.Collection;
    55 import java.util.Collections;
    56 import java.util.HashMap;
    57 import java.util.HashSet;
    58 import java.util.LinkedHashSet;
    59 import java.util.List;
    60 import java.util.Map;
    61 import java.util.ResourceBundle;
    62 import java.util.Set;
    63 import java.util.WeakHashMap;
    64 import java.util.logging.Level;
    65 import java.util.logging.Logger;
    66 import javax.annotation.processing.AbstractProcessor;
    67 import javax.annotation.processing.Completion;
    68 import javax.annotation.processing.Completions;
    69 import javax.annotation.processing.ProcessingEnvironment;
    70 import javax.annotation.processing.Processor;
    71 import javax.annotation.processing.RoundEnvironment;
    72 import javax.annotation.processing.SupportedAnnotationTypes;
    73 import javax.annotation.processing.SupportedSourceVersion;
    74 import javax.lang.model.SourceVersion;
    75 import javax.lang.model.element.AnnotationMirror;
    76 import javax.lang.model.element.AnnotationValue;
    77 import javax.lang.model.element.Element;
    78 import javax.lang.model.element.ElementKind;
    79 import javax.lang.model.element.ExecutableElement;
    80 import javax.lang.model.element.Modifier;
    81 import javax.lang.model.element.PackageElement;
    82 import javax.lang.model.element.TypeElement;
    83 import javax.lang.model.element.VariableElement;
    84 import javax.lang.model.type.ArrayType;
    85 import javax.lang.model.type.DeclaredType;
    86 import javax.lang.model.type.MirroredTypeException;
    87 import javax.lang.model.type.TypeKind;
    88 import javax.lang.model.type.TypeMirror;
    89 import javax.lang.model.util.Elements;
    90 import javax.lang.model.util.Types;
    91 import javax.tools.Diagnostic;
    92 import javax.tools.FileObject;
    93 import net.java.html.json.ComputedProperty;
    94 import net.java.html.json.Function;
    95 import net.java.html.json.Model;
    96 import net.java.html.json.ModelOperation;
    97 import net.java.html.json.OnPropertyChange;
    98 import net.java.html.json.OnReceive;
    99 import net.java.html.json.Property;
   100 import org.openide.util.lookup.ServiceProvider;
   101 
   102 /** Annotation processor to process {@link Model @Model} annotations and
   103  * generate appropriate model classes.
   104  *
   105  * @author Jaroslav Tulach
   106  */
   107 @ServiceProvider(service=Processor.class)
   108 @SupportedSourceVersion(SourceVersion.RELEASE_6)
   109 @SupportedAnnotationTypes({
   110     "net.java.html.json.Model",
   111     "net.java.html.json.ModelOperation",
   112     "net.java.html.json.Function",
   113     "net.java.html.json.OnReceive",
   114     "net.java.html.json.OnPropertyChange",
   115     "net.java.html.json.ComputedProperty",
   116     "net.java.html.json.Property"
   117 })
   118 public final class ModelProcessor extends AbstractProcessor {
   119     private static final Logger LOG = Logger.getLogger(ModelProcessor.class.getName());
   120     private final Map<Element,String> models = new WeakHashMap<Element,String>();
   121     private final Map<Element,Prprt[]> verify = new WeakHashMap<Element,Prprt[]>();
   122     @Override
   123     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   124         boolean ok = true;
   125         for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
   126             if (!processModel(e)) {
   127                 ok = false;
   128             }
   129         }
   130         if (roundEnv.processingOver()) {
   131             models.clear();
   132             for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
   133                 TypeElement te = (TypeElement)entry.getKey();
   134                 String fqn = te.getQualifiedName().toString();
   135                 Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
   136                 if (finalElem == null) {
   137                     continue;
   138                 }
   139                 Prprt[] props;
   140                 Model m = finalElem.getAnnotation(Model.class);
   141                 if (m == null) {
   142                     continue;
   143                 }
   144                 props = Prprt.wrap(processingEnv, finalElem, m.properties());
   145                 for (Prprt p : props) {
   146                     boolean[] isModel = { false };
   147                     boolean[] isEnum = { false };
   148                     boolean[] isPrimitive = { false };
   149                     String t = checkType(p, isModel, isEnum, isPrimitive);
   150                     if (isEnum[0]) {
   151                         continue;
   152                     }
   153                     if (isPrimitive[0]) {
   154                         continue;
   155                     }
   156                     if (isModel[0]) {
   157                         continue;
   158                     }
   159                     if ("java.lang.String".equals(t)) {
   160                         continue;
   161                     }
   162                     error("The type " + t + " should be defined by @Model annotation", entry.getKey());
   163                 }
   164             }
   165             verify.clear();
   166         }
   167         return ok;
   168     }
   169 
   170     private void error(String msg, Element e) {
   171         processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
   172     }
   173 
   174     private boolean processModel(Element e) {
   175         boolean ok = true;
   176         Model m = e.getAnnotation(Model.class);
   177         if (m == null) {
   178             return true;
   179         }
   180         String pkg = findPkgName(e);
   181         Writer w;
   182         String className = m.className();
   183         models.put(e, className);
   184         try {
   185             StringWriter body = new StringWriter();
   186             StringBuilder onReceiveType = new StringBuilder();
   187             List<GetSet> propsGetSet = new ArrayList<GetSet>();
   188             List<Object> functions = new ArrayList<Object>();
   189             Map<String, Collection<String[]>> propsDeps = new HashMap<String, Collection<String[]>>();
   190             Map<String, Collection<String>> functionDeps = new HashMap<String, Collection<String>>();
   191             Prprt[] props = createProps(e, m.properties());
   192             final String builderPrefix = findBuilderPrefix(e, m);
   193 
   194             if (!generateComputedProperties(className, body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
   195                 ok = false;
   196             }
   197             if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
   198                 ok = false;
   199             }
   200             if (!generateProperties(e, builderPrefix, body, className, props, propsGetSet, propsDeps, functionDeps)) {
   201                 ok = false;
   202             }
   203             if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   204                 ok = false;
   205             }
   206             int functionsCount = functions.size() / 2;
   207             for (int i = 0; i < functions.size(); i += 2) {
   208                 for (Prprt p : props) {
   209                     if (p.name().equals(functions.get(i))) {
   210                         error("Function cannot have the name of an existing property", e);
   211                         ok = false;
   212                     }
   213                 }
   214             }
   215             if (!generateReceive(e, body, className, e.getEnclosedElements(), onReceiveType)) {
   216                 ok = false;
   217             }
   218             if (!generateOperation(e, body, className, e.getEnclosedElements(), functions)) {
   219                 ok = false;
   220             }
   221             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   222             w = new OutputStreamWriter(java.openOutputStream());
   223             try {
   224                 w.append("package " + pkg + ";\n");
   225                 w.append("import net.java.html.json.*;\n");
   226                 final String inPckName = inPckName(e);
   227                 w.append("/** Generated for {@link ").append(inPckName).append("}*/\n");
   228                 w.append("public final class ").append(className).append(" implements Cloneable {\n");
   229                 w.append("  private static Class<").append(inPckName).append("> modelFor() { return ").append(inPckName).append(".class; }\n");
   230                 w.append("  private static final Html4JavaType TYPE = new Html4JavaType();\n");
   231                 w.append("  private final org.netbeans.html.json.spi.Proto proto;\n");
   232                 w.append(body.toString());
   233                 w.append("  private ").append(className).append("(net.java.html.BrwsrCtx context) {\n");
   234                 w.append("    this.proto = TYPE.createProto(this, context);\n");
   235                 for (Prprt p : props) {
   236                     if (p.array()) {
   237                         final String tn = typeName(p);
   238                         String[] gs = toGetSet(p.name(), tn, p.array());
   239                         w.write("    this.prop_" + p.name() + " = proto.createList(\""
   240                             + p.name() + "\"");
   241                         if (functionDeps.containsKey(p.name())) {
   242                             int index = Arrays.asList(functionDeps.keySet().toArray()).indexOf(p.name());
   243                             w.write(", " + index);
   244                         } else {
   245                             w.write(", -1");
   246                         }
   247                         Collection<String[]> dependants = propsDeps.get(p.name());
   248                         if (dependants != null) {
   249                             for (String[] depProp : dependants) {
   250                                 w.write(", ");
   251                                 w.write('\"');
   252                                 w.write(depProp[0]);
   253                                 w.write('\"');
   254                             }
   255                         }
   256                         w.write(")");
   257                         w.write(";\n");
   258                     }
   259                 }
   260                 w.append("  };\n");
   261                 w.append("  public ").append(className).append("() {\n");
   262                 w.append("    this(net.java.html.BrwsrCtx.findDefault(").append(className).append(".class));\n");
   263                 for (Prprt p : props) {
   264                     if (!p.array()) {
   265                         boolean[] isModel = {false};
   266                         boolean[] isEnum = {false};
   267                         boolean isPrimitive[] = {false};
   268                         String tn = checkType(p, isModel, isEnum, isPrimitive);
   269                         if (isModel[0]) {
   270                             w.write("    prop_" + p.name() + " = new " + tn + "();\n");
   271                         }
   272                     }
   273                 }
   274                 w.append("  };\n");
   275                 if (props.length > 0 && builderPrefix == null) {
   276                     StringBuilder constructorWithArguments = new StringBuilder();
   277                     constructorWithArguments.append("  public ").append(className).append("(");
   278                     Prprt firstArray = null;
   279                     String sep = "";
   280                     int parameterCount = 0;
   281                     for (Prprt p : props) {
   282                         if (p.array()) {
   283                             if (firstArray == null) {
   284                                 firstArray = p;
   285                             }
   286                             continue;
   287                         }
   288                         String tn = typeName(p);
   289                         constructorWithArguments.append(sep);
   290                         constructorWithArguments.append(tn);
   291                         String[] third = toGetSet(p.name(), tn, false);
   292                         constructorWithArguments.append(" ").append(third[2]);
   293                         sep = ", ";
   294                         parameterCount++;
   295                     }
   296                     if (firstArray != null) {
   297                         String tn;
   298                         boolean[] isModel = {false};
   299                         boolean[] isEnum = {false};
   300                         boolean isPrimitive[] = {false};
   301                         tn = checkType(firstArray, isModel, isEnum, isPrimitive);
   302                         constructorWithArguments.append(sep);
   303                         constructorWithArguments.append(tn);
   304                         String[] third = toGetSet(firstArray.name(), tn, true);
   305                         constructorWithArguments.append("... ").append(third[2]);
   306                         parameterCount++;
   307                     }
   308                     constructorWithArguments.append(") {\n");
   309                     constructorWithArguments.append("    this(net.java.html.BrwsrCtx.findDefault(").append(className).append(".class));\n");
   310                     for (Prprt p : props) {
   311                         if (p.array()) {
   312                             continue;
   313                         }
   314                         String[] third = toGetSet(p.name(), null, false);
   315                         constructorWithArguments.append("    this.prop_" + p.name() + " = " + third[2] + ";\n");
   316                     }
   317                     if (firstArray != null) {
   318                         String[] third = toGetSet(firstArray.name(), null, true);
   319                         constructorWithArguments.append("    proto.initTo(this.prop_" + firstArray.name() + ", " + third[2] + ");\n");
   320                     }
   321                     constructorWithArguments.append("  };\n");
   322                     if (parameterCount < 255) {
   323                         w.write(constructorWithArguments.toString());
   324                     }
   325                 }
   326                 w.append("  private static class Html4JavaType extends org.netbeans.html.json.spi.Proto.Type<").append(className).append("> {\n");
   327                 w.append("    private Html4JavaType() {\n      super(").append(className).append(".class, ").
   328                     append(inPckName).append(".class, " + propsGetSet.size() + ", "
   329                     + functionsCount + ");\n");
   330                 {
   331                     for (int i = 0; i < propsGetSet.size(); i++) {
   332                         w.append("      registerProperty(\"").append(propsGetSet.get(i).name).append("\", ");
   333                         w.append((i) + ", " + propsGetSet.get(i).readOnly + ");\n");
   334                     }
   335                 }
   336                 {
   337                     for (int i = 0; i < functionsCount; i++) {
   338                         w.append("      registerFunction(\"").append((String)functions.get(i * 2)).append("\", ");
   339                         w.append(i + ");\n");
   340                     }
   341                 }
   342                 w.append("    }\n");
   343                 w.append("    @Override public void setValue(" + className + " data, int type, Object value) {\n");
   344                 w.append("      switch (type) {\n");
   345                 for (int i = 0; i < propsGetSet.size(); i++) {
   346                     final GetSet pgs = propsGetSet.get(i);
   347                     if (pgs.readOnly) {
   348                         continue;
   349                     }
   350                     final String set = pgs.setter;
   351                     String tn = pgs.type;
   352                     String btn = findBoxedType(tn);
   353                     if (btn != null) {
   354                         tn = btn;
   355                     }
   356                     w.append("        case " + i + ": ");
   357                     if (pgs.setter != null) {
   358                         w.append("data.").append(strip(pgs.setter)).append("(TYPE.extractValue(" + tn + ".class, value)); return;\n");
   359                     } else {
   360                         w.append("TYPE.replaceValue(data.").append(strip(pgs.getter)).append("(), " + tn + ".class, value); return;\n");
   361                     }
   362                 }
   363                 w.append("      }\n");
   364                 w.append("      throw new UnsupportedOperationException();\n");
   365                 w.append("    }\n");
   366                 w.append("    @Override public Object getValue(" + className + " data, int type) {\n");
   367                 w.append("      switch (type) {\n");
   368                 for (int i = 0; i < propsGetSet.size(); i++) {
   369                     final String get = propsGetSet.get(i).getter;
   370                     if (get != null) {
   371                         w.append("        case " + i + ": return data." + strip(get) + "();\n");
   372                     }
   373                 }
   374                 w.append("      }\n");
   375                 w.append("      throw new UnsupportedOperationException();\n");
   376                 w.append("    }\n");
   377                 w.append("    @Override public void call(" + className + " model, int type, Object data, Object ev) throws Exception {\n");
   378                 w.append("      switch (type) {\n");
   379                 for (int i = 0; i < functions.size(); i += 2) {
   380                     final String name = (String)functions.get(i);
   381                     final Object param = functions.get(i + 1);
   382                     if (param instanceof ExecutableElement) {
   383                         ExecutableElement ee = (ExecutableElement)param;
   384                         w.append("        case " + (i / 2) + ":\n");
   385                         w.append("          ").append(((TypeElement)e).getQualifiedName()).append(".").append(name).append("(");
   386                         w.append(wrapParams(ee, null, className, "model", "ev", "data"));
   387                         w.append(");\n");
   388                         w.append("          return;\n");
   389                     } else {
   390                         String call = (String)param;
   391                         w.append("        case " + (i / 2) + ":\n"); // model." + name + "(data, ev); return;\n");
   392                         w.append("          ").append(call).append("\n");
   393                         w.append("          return;\n");
   394 
   395                     }
   396                 }
   397                 w.append("      }\n");
   398                 w.append("      throw new UnsupportedOperationException();\n");
   399                 w.append("    }\n");
   400                 w.append("    @Override public org.netbeans.html.json.spi.Proto protoFor(Object obj) {\n");
   401                 w.append("      return ((" + className + ")obj).proto;");
   402                 w.append("    }\n");
   403                 w.append("    @Override public void onChange(" + className + " model, int type) {\n");
   404                 w.append("      switch (type) {\n");
   405                 {
   406                     String[] arr = functionDeps.keySet().toArray(new String[0]);
   407                     for (int i = 0; i < arr.length; i++) {
   408                         Collection<String> onChange = functionDeps.get(arr[i]);
   409                         if (onChange != null) {
   410                             w.append("      case " + i + ":\n");
   411                             for (String call : onChange) {
   412                                 w.append("      ").append(call).append("\n");
   413                             }
   414                             w.write("      return;\n");
   415                         }
   416                     }
   417                 }
   418                 w.append("    }\n");
   419                 w.append("      throw new UnsupportedOperationException();\n");
   420                 w.append("    }\n");
   421                 w.append(onReceiveType);
   422                 w.append("    @Override public " + className + " read(net.java.html.BrwsrCtx c, Object json) { return new " + className + "(c, json); }\n");
   423                 w.append("    @Override public " + className + " cloneTo(" + className + " o, net.java.html.BrwsrCtx c) { return o.clone(c); }\n");
   424                 w.append("  }\n");
   425                 w.append("  private ").append(className).append("(net.java.html.BrwsrCtx c, Object json) {\n");
   426                 w.append("    this(c);\n");
   427                 int values = 0;
   428                 for (int i = 0; i < propsGetSet.size(); i++) {
   429                     Prprt p = findPrprt(props, propsGetSet.get(i).name);
   430                     if (p == null) {
   431                         continue;
   432                     }
   433                     values++;
   434                 }
   435                 w.append("    Object[] ret = new Object[" + values + "];\n");
   436                 w.append("    proto.extract(json, new String[] {\n");
   437                 for (int i = 0; i < propsGetSet.size(); i++) {
   438                     Prprt p = findPrprt(props, propsGetSet.get(i).name);
   439                     if (p == null) {
   440                         continue;
   441                     }
   442                     w.append("      \"").append(propsGetSet.get(i).name).append("\",\n");
   443                 }
   444                 w.append("    }, ret);\n");
   445                 for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i++) {
   446                     final String pn = propsGetSet.get(i).name;
   447                     Prprt p = findPrprt(props, pn);
   448                     if (p == null || prop >= props.length) {
   449                         continue;
   450                     }
   451                     boolean[] isModel = { false };
   452                     boolean[] isEnum = { false };
   453                     boolean isPrimitive[] = { false };
   454                     String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
   455                     if (p.array()) {
   456                         w.append("    for (Object e : useAsArray(ret[" + cnt + "])) {\n");
   457                         if (isModel[0]) {
   458                             w.append("      this.prop_").append(pn).append(".add(proto.read");
   459                             w.append("(" + type + ".class, e));\n");
   460                         } else if (isEnum[0]) {
   461                             w.append("        this.prop_").append(pn);
   462                             w.append(".add(e == null ? null : ");
   463                             w.append(type).append(".valueOf(TYPE.stringValue(e)));\n");
   464                         } else {
   465                             if (isPrimitive(type)) {
   466                                 if (type.equals("char")) {
   467                                     w.append("        this.prop_").append(pn).append(".add((char)TYPE.numberValue(e).");
   468                                     w.append("intValue());\n");
   469                                 } else {
   470                                     w.append("        this.prop_").append(pn).append(".add(TYPE.numberValue(e).");
   471                                     w.append(type).append("Value());\n");
   472                                 }
   473                             } else {
   474                                 w.append("        this.prop_").append(pn).append(".add((");
   475                                 w.append(type).append(")e);\n");
   476                             }
   477                         }
   478                         w.append("    }\n");
   479                     } else {
   480                         if (isEnum[0]) {
   481                             w.append("    try {\n");
   482                             w.append("    this.prop_").append(pn);
   483                             w.append(" = ret[" + cnt + "] == null ? null : ");
   484                             w.append(type).append(".valueOf(TYPE.stringValue(ret[" + cnt + "]));\n");
   485                             w.append("    } catch (IllegalArgumentException ex) {\n");
   486                             w.append("      ex.printStackTrace();\n");
   487                             w.append("    }\n");
   488                         } else if (isPrimitive(type)) {
   489                             w.append("    this.prop_").append(pn);
   490                             w.append(" = ret[" + cnt + "] == null ? ");
   491                             if ("char".equals(type)) {
   492                                 w.append("0 : (TYPE.charValue(");
   493                             } else if ("boolean".equals(type)) {
   494                                 w.append("false : (TYPE.boolValue(");
   495                             } else {
   496                                 w.append("0 : (TYPE.numberValue(");
   497                             }
   498                             w.append("ret[" + cnt + "])).");
   499                             w.append(type).append("Value();\n");
   500                         } else if (isModel[0]) {
   501                             w.append("    this.prop_").append(pn).append(" = proto.read");
   502                             w.append("(" + type + ".class, ");
   503                             w.append("ret[" + cnt + "]);\n");
   504                         }else {
   505                             w.append("    this.prop_").append(pn);
   506                             w.append(" = (").append(type).append(')');
   507                             w.append("ret[" + cnt + "];\n");
   508                         }
   509                     }
   510                     cnt++;
   511                 }
   512                 w.append("  }\n");
   513                 w.append("  private static Object[] useAsArray(Object o) {\n");
   514                 w.append("    return o instanceof Object[] ? ((Object[])o) : o == null ? new Object[0] : new Object[] { o };\n");
   515                 w.append("  }\n");
   516                 writeToString(props, w);
   517                 writeClone(className, props, w);
   518                 String targetId = findTargetId(e);
   519                 if (targetId != null) {
   520                     w.write("  /** Activates this model instance in the current {@link \n"
   521                         + "net.java.html.json.Models#bind(java.lang.Object, net.java.html.BrwsrCtx) browser context}. \n"
   522                         + "In case of using Knockout technology, this means to \n"
   523                         + "bind JSON like data in this model instance with Knockout tags in \n"
   524                         + "the surrounding HTML page.\n"
   525                     );
   526                     if (targetId != null) {
   527                         w.write("This method binds to element '" + targetId + "' on the page\n");
   528                     }
   529                     w.write(""
   530                         + "@return <code>this</code> object\n"
   531                         + "*/\n"
   532                     );
   533                     w.write("  public " + className + " applyBindings() {\n");
   534                     w.write("    proto.applyBindings();\n");
   535     //                w.write("    proto.applyBindings(id);\n");
   536                     w.write("    return this;\n");
   537                     w.write("  }\n");
   538                 } else {
   539                     w.write("  private " + className + " applyBindings() {\n");
   540                     w.write("    throw new IllegalStateException(\"Please specify targetId=\\\"\\\" in your @Model annotation\");\n");
   541                     w.write("  }\n");
   542                 }
   543                 w.write("  public boolean equals(Object o) {\n");
   544                 w.write("    if (o == this) return true;\n");
   545                 w.write("    if (!(o instanceof " + className + ")) return false;\n");
   546                 w.write("    " + className + " p = (" + className + ")o;\n");
   547                 for (Prprt p : props) {
   548                     w.write("    if (!TYPE.isSame(prop_" + p.name() + ", p.prop_" + p.name() + ")) return false;\n");
   549                 }
   550                 w.write("    return true;\n");
   551                 w.write("  }\n");
   552                 w.write("  public int hashCode() {\n");
   553                 w.write("    int h = " + className + ".class.getName().hashCode();\n");
   554                 for (Prprt p : props) {
   555                     w.write("    h = TYPE.hashPlus(prop_" + p.name() + ", h);\n");
   556                 }
   557                 w.write("    return h;\n");
   558                 w.write("  }\n");
   559                 w.write("}\n");
   560             } finally {
   561                 w.close();
   562             }
   563         } catch (IOException ex) {
   564             error("Can't create " + className + ".java", e);
   565             return false;
   566         }
   567         return ok;
   568     }
   569 
   570     private static String findBuilderPrefix(Element e, Model m) {
   571         if (!m.builder().isEmpty()) {
   572             return m.builder();
   573         }
   574         for (AnnotationMirror am : e.getAnnotationMirrors()) {
   575             for (Map.Entry<? extends Object, ? extends Object> entry : am.getElementValues().entrySet()) {
   576                 if ("builder()".equals(entry.getKey().toString())) {
   577                     return "";
   578                 }
   579             }
   580         }
   581         return null;
   582     }
   583 
   584     private static String builderMethod(String builderPrefix, Prprt p) {
   585         if (builderPrefix.isEmpty()) {
   586             return p.name();
   587         }
   588         return builderPrefix + Character.toUpperCase(p.name().charAt(0)) + p.name().substring(1);
   589     }
   590 
   591     private boolean generateProperties(
   592         Element where, String builderPrefix,
   593         Writer w, String className, Prprt[] properties,
   594         List<GetSet> props,
   595         Map<String,Collection<String[]>> deps,
   596         Map<String,Collection<String>> functionDeps
   597     ) throws IOException {
   598         boolean ok = true;
   599         for (Prprt p : properties) {
   600             final String tn;
   601             tn = typeName(p);
   602             String[] gs = toGetSet(p.name(), tn, p.array());
   603             String castTo;
   604 
   605             if (p.array()) {
   606                 w.write("  private final java.util.List<" + tn + "> prop_" + p.name() + ";\n");
   607 
   608                 castTo = "java.util.List";
   609                 w.write("  public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   610                 w.write("    proto.accessProperty(\"" + p.name() + "\");\n");
   611                 w.write("    return prop_" + p.name() + ";\n");
   612                 w.write("  }\n");
   613                 if (builderPrefix != null) {
   614                     boolean[] isModel = {false};
   615                     boolean[] isEnum = {false};
   616                     boolean isPrimitive[] = {false};
   617                     String ret = checkType(p, isModel, isEnum, isPrimitive);
   618                     w.write("  public " + className + " " + builderMethod(builderPrefix, p) + "(" + ret + "... v) {\n");
   619                     w.write("    proto.accessProperty(\"" + p.name() + "\");\n");
   620                     w.append("   TYPE.replaceValue(prop_").append(p.name()).append(", " + tn + ".class, v);\n");
   621                     w.write("    return this;\n");
   622                     w.write("  }\n");
   623                 }
   624             } else {
   625                 castTo = tn;
   626                 boolean isModel[] = { false };
   627                 boolean isEnum[] = { false };
   628                 boolean isPrimitive[] = { false };
   629                 checkType(p, isModel, isEnum, isPrimitive);
   630                 w.write("  private " + tn + " prop_" + p.name() + ";\n");
   631                 w.write("  public " + tn + " " + gs[0] + "() {\n");
   632                 w.write("    proto.accessProperty(\"" + p.name() + "\");\n");
   633                 w.write("    return prop_" + p.name() + ";\n");
   634                 w.write("  }\n");
   635                 w.write("  public void " + gs[1] + "(" + tn + " v) {\n");
   636                 w.write("    proto.verifyUnlocked();\n");
   637                 w.write("    Object o = prop_" + p.name() + ";\n");
   638                 if (isModel[0]) {
   639                     w.write("    if (o == v) return;\n");
   640                     w.write("    prop_" + p.name() + " = v;\n");
   641                 } else {
   642                     w.write("    if (TYPE.isSame(o , v)) return;\n");
   643                     w.write("    prop_" + p.name() + " = v;\n");
   644                 }
   645                 w.write("    proto.valueHasMutated(\"" + p.name() + "\", o, v);\n");
   646                 {
   647                     Collection<String[]> dependants = deps.get(p.name());
   648                     if (dependants != null) {
   649                         for (String[] pair : dependants) {
   650                             w.write("    proto.valueHasMutated(\"" + pair[0] + "\", null, " + pair[1] + "());\n");
   651                         }
   652                     }
   653                 }
   654                 {
   655                     Collection<String> dependants = functionDeps.get(p.name());
   656                     if (dependants != null) {
   657                         w.append("    ");
   658                         w.append(className).append(" model = ").append(className).append(".this;\n");
   659                         for (String call : dependants) {
   660                             w.append("  ").append(call);
   661                         }
   662                     }
   663                 }
   664                 w.write("  }\n");
   665                 if (builderPrefix != null) {
   666                     w.write("  public " + className + " " + builderMethod(builderPrefix, p) + "(" + tn + " v) {\n");
   667                     w.write("    " + gs[1] + "(v);\n");
   668                     w.write("    return this;\n");
   669                     w.write("  }\n");
   670                 }
   671             }
   672 
   673             for (int i = 0; i < props.size(); i++) {
   674                 if (props.get(i).name.equals(p.name())) {
   675                     error("Cannot have the name " + p.name() + " defined twice", where);
   676                     ok = false;
   677                 }
   678             }
   679 
   680             props.add(new GetSet(
   681                 p.name(),
   682                 gs[0],
   683                 gs[1],
   684                 tn,
   685                 gs[3] == null && !p.array()
   686             ));
   687         }
   688         return ok;
   689     }
   690 
   691     private boolean generateComputedProperties(
   692         String className,
   693         Writer w, Prprt[] fixedProps,
   694         Collection<? extends Element> arr, Collection<GetSet> props,
   695         Map<String,Collection<String[]>> deps
   696     ) throws IOException {
   697         boolean ok = true;
   698         for (Element e : arr) {
   699             if (e.getKind() != ElementKind.METHOD) {
   700                 continue;
   701             }
   702             final ComputedProperty cp = e.getAnnotation(ComputedProperty.class);
   703             final Transitive tp = e.getAnnotation(Transitive.class);
   704             if (cp == null) {
   705                 continue;
   706             }
   707             if (!e.getModifiers().contains(Modifier.STATIC)) {
   708                 error("Method " + e.getSimpleName() + " has to be static when annotated by @ComputedProperty", e);
   709                 ok = false;
   710                 continue;
   711             }
   712             ExecutableElement ee = (ExecutableElement)e;
   713             ExecutableElement write = null;
   714             if (!cp.write().isEmpty()) {
   715                 write = findWrite(ee, (TypeElement)e.getEnclosingElement(), cp.write(), className);
   716                 ok = write != null;
   717             }
   718             final TypeMirror rt = ee.getReturnType();
   719             final Types tu = processingEnv.getTypeUtils();
   720             TypeMirror ert = tu.erasure(rt);
   721             String tn = fqn(ert, ee);
   722             boolean array = false;
   723             final TypeMirror toCheck;
   724             if (tn.equals("java.util.List")) {
   725                 array = true;
   726                 toCheck = ((DeclaredType)rt).getTypeArguments().get(0);
   727             } else {
   728                 toCheck = rt;
   729             }
   730 
   731             final String sn = ee.getSimpleName().toString();
   732 
   733             if (toCheck.getKind().isPrimitive()) {
   734                 // OK
   735             } else {
   736                 TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   737                 TypeMirror enumType = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
   738 
   739                 if (tu.isSubtype(toCheck, stringType)) {
   740                     // OK
   741                 } else if (tu.isSubtype(tu.erasure(toCheck), tu.erasure(enumType))) {
   742                     // OK
   743                 } else if (isModel(toCheck)) {
   744                     // OK
   745                 } else {
   746                     try {
   747                         tu.unboxedType(toCheck);
   748                         // boxed types are OK
   749                     } catch (IllegalArgumentException ex) {
   750                         ok = false;
   751                         error(sn + " cannot return " + toCheck, e);
   752                     }
   753                 }
   754             }
   755 
   756             String[] gs = toGetSet(sn, tn, array);
   757 
   758             w.write("  public " + tn);
   759             if (array) {
   760                 w.write("<" + toCheck + ">");
   761             }
   762             w.write(" " + gs[0] + "() {\n");
   763             int arg = 0;
   764             boolean deep = false;
   765             for (VariableElement pe : ee.getParameters()) {
   766                 final String dn = pe.getSimpleName().toString();
   767 
   768                 if (!verifyPropName(pe, dn, fixedProps)) {
   769                     ok = false;
   770                 }
   771                 final TypeMirror pt = pe.asType();
   772                 if (isModel(pt)) {
   773                     deep = true;
   774                 }
   775                 final String dt = fqn(pt, ee);
   776                 if (dt.startsWith("java.util.List") && pt instanceof DeclaredType) {
   777                     final List<? extends TypeMirror> ptArgs = ((DeclaredType)pt).getTypeArguments();
   778                     if (ptArgs.size() == 1 && isModel(ptArgs.get(0))) {
   779                         deep = true;
   780                     }
   781                 }
   782                 String[] call = toGetSet(dn, dt, false);
   783                 w.write("    " + dt + " arg" + (++arg) + " = ");
   784                 w.write(call[0] + "();\n");
   785 
   786                 Collection<String[]> depends = deps.get(dn);
   787                 if (depends == null) {
   788                     depends = new LinkedHashSet<String[]>();
   789                     deps.put(dn, depends);
   790                 }
   791                 depends.add(new String[] { sn, gs[0] });
   792             }
   793             w.write("    try {\n");
   794             if (tp != null) {
   795                 deep = tp.deep();
   796             }
   797             if (deep) {
   798                 w.write("      proto.acquireLock(\"" + sn + "\");\n");
   799             } else {
   800                 w.write("      proto.acquireLock();\n");
   801             }
   802             w.write("      return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
   803             String sep = "";
   804             for (int i = 1; i <= arg; i++) {
   805                 w.write(sep);
   806                 w.write("arg" + i);
   807                 sep = ", ";
   808             }
   809             w.write(");\n");
   810             w.write("    } finally {\n");
   811             w.write("      proto.releaseLock();\n");
   812             w.write("    }\n");
   813             w.write("  }\n");
   814 
   815             if (write == null) {
   816                 props.add(new GetSet(
   817                     e.getSimpleName().toString(),
   818                     gs[0],
   819                     null,
   820                     tn,
   821                     true
   822                 ));
   823             } else {
   824                 w.write("  public void " + gs[4] + "(" + write.getParameters().get(1).asType());
   825                 w.write(" value) {\n");
   826                 w.write("    " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + write.getSimpleName() + "(this, value);\n");
   827                 w.write("  }\n");
   828 
   829                 props.add(new GetSet(
   830                     e.getSimpleName().toString(),
   831                     gs[0],
   832                     gs[4],
   833                     tn,
   834                     false
   835                 ));
   836             }
   837         }
   838 
   839         return ok;
   840     }
   841 
   842     private static String[] toGetSet(String name, String type, boolean array) {
   843         String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
   844         boolean clazz = "class".equals(name);
   845         String pref = clazz ? "access" : "get";
   846         if ("boolean".equals(type) && !array) {
   847             pref = "is";
   848         }
   849         if (array) {
   850             return new String[] {
   851                 pref + n,
   852                 null,
   853                 "a" + n,
   854                 null,
   855                 "set" + n
   856             };
   857         }
   858         return new String[]{
   859             pref + n,
   860             "set" + n,
   861             "a" + n,
   862             "",
   863             "set" + n
   864         };
   865     }
   866 
   867     private String typeName(Prprt p) {
   868         String ret;
   869         boolean[] isModel = { false };
   870         boolean[] isEnum = { false };
   871         boolean isPrimitive[] = { false };
   872         ret = checkType(p, isModel, isEnum, isPrimitive);
   873         if (p.array()) {
   874             String bt = findBoxedType(ret);
   875             if (bt != null) {
   876                 return bt;
   877             }
   878         }
   879         return ret;
   880     }
   881 
   882     private static String findBoxedType(String ret) {
   883         if (ret.equals("boolean")) {
   884             return Boolean.class.getName();
   885         }
   886         if (ret.equals("byte")) {
   887             return Byte.class.getName();
   888         }
   889         if (ret.equals("short")) {
   890             return Short.class.getName();
   891         }
   892         if (ret.equals("char")) {
   893             return Character.class.getName();
   894         }
   895         if (ret.equals("int")) {
   896             return Integer.class.getName();
   897         }
   898         if (ret.equals("long")) {
   899             return Long.class.getName();
   900         }
   901         if (ret.equals("float")) {
   902             return Float.class.getName();
   903         }
   904         if (ret.equals("double")) {
   905             return Double.class.getName();
   906         }
   907         return null;
   908     }
   909 
   910     private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
   911         StringBuilder sb = new StringBuilder();
   912         String sep = "";
   913         for (Prprt Prprt : existingProps) {
   914             if (Prprt.name().equals(propName)) {
   915                 return true;
   916             }
   917             sb.append(sep);
   918             sb.append('"');
   919             sb.append(Prprt.name());
   920             sb.append('"');
   921             sep = ", ";
   922         }
   923         error(
   924             propName + " is not one of known properties: " + sb
   925             , e
   926         );
   927         return false;
   928     }
   929 
   930     private static String findPkgName(Element e) {
   931         for (;;) {
   932             if (e.getKind() == ElementKind.PACKAGE) {
   933                 return ((PackageElement)e).getQualifiedName().toString();
   934             }
   935             e = e.getEnclosingElement();
   936         }
   937     }
   938 
   939     private boolean generateFunctions(
   940         Element clazz, StringWriter body, String className,
   941         List<? extends Element> enclosedElements, List<Object> functions
   942     ) {
   943         for (Element m : enclosedElements) {
   944             if (m.getKind() != ElementKind.METHOD) {
   945                 continue;
   946             }
   947             ExecutableElement e = (ExecutableElement)m;
   948             Function onF = e.getAnnotation(Function.class);
   949             if (onF == null) {
   950                 continue;
   951             }
   952             if (!e.getModifiers().contains(Modifier.STATIC)) {
   953                 error("@OnFunction method needs to be static", e);
   954                 return false;
   955             }
   956             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   957                 error("@OnFunction method cannot be private", e);
   958                 return false;
   959             }
   960             if (e.getReturnType().getKind() != TypeKind.VOID) {
   961                 error("@OnFunction method should return void", e);
   962                 return false;
   963             }
   964             String n = e.getSimpleName().toString();
   965             functions.add(n);
   966             functions.add(e);
   967         }
   968         return true;
   969     }
   970 
   971     private boolean generateOnChange(Element clazz, Map<String,Collection<String[]>> propDeps,
   972         Prprt[] properties, String className,
   973         Map<String, Collection<String>> functionDeps
   974     ) {
   975         for (Element m : clazz.getEnclosedElements()) {
   976             if (m.getKind() != ElementKind.METHOD) {
   977                 continue;
   978             }
   979             ExecutableElement e = (ExecutableElement) m;
   980             OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
   981             if (onPC == null) {
   982                 continue;
   983             }
   984             for (String pn : onPC.value()) {
   985                 if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   986                     error("No Prprt named '" + pn + "' in the model", clazz);
   987                     return false;
   988                 }
   989             }
   990             if (!e.getModifiers().contains(Modifier.STATIC)) {
   991                 error("@OnPrprtChange method needs to be static", e);
   992                 return false;
   993             }
   994             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   995                 error("@OnPrprtChange method cannot be private", e);
   996                 return false;
   997             }
   998             if (e.getReturnType().getKind() != TypeKind.VOID) {
   999                 error("@OnPrprtChange method should return void", e);
  1000                 return false;
  1001             }
  1002             String n = e.getSimpleName().toString();
  1003 
  1004 
  1005             for (String pn : onPC.value()) {
  1006                 StringBuilder call = new StringBuilder();
  1007                 call.append("  ").append(inPckName(clazz)).append(".").append(n).append("(");
  1008                 call.append(wrapPropName(e, className, "name", pn));
  1009                 call.append(");\n");
  1010 
  1011                 Collection<String> change = functionDeps.get(pn);
  1012                 if (change == null) {
  1013                     change = new ArrayList<String>();
  1014                     functionDeps.put(pn, change);
  1015                 }
  1016                 change.add(call.toString());
  1017                 for (String dpn : findDerivedFrom(propDeps, pn)) {
  1018                     change = functionDeps.get(dpn);
  1019                     if (change == null) {
  1020                         change = new ArrayList<String>();
  1021                         functionDeps.put(dpn, change);
  1022                     }
  1023                     change.add(call.toString());
  1024                 }
  1025             }
  1026         }
  1027         return true;
  1028     }
  1029 
  1030     private boolean generateOperation(Element clazz,
  1031         StringWriter body, String className,
  1032         List<? extends Element> enclosedElements,
  1033         List<Object> functions
  1034     ) {
  1035         for (Element m : enclosedElements) {
  1036             if (m.getKind() != ElementKind.METHOD) {
  1037                 continue;
  1038             }
  1039             ExecutableElement e = (ExecutableElement)m;
  1040             ModelOperation mO = e.getAnnotation(ModelOperation.class);
  1041             if (mO == null) {
  1042                 continue;
  1043             }
  1044             if (!e.getModifiers().contains(Modifier.STATIC)) {
  1045                 error("@ModelOperation method needs to be static", e);
  1046                 return false;
  1047             }
  1048             if (e.getModifiers().contains(Modifier.PRIVATE)) {
  1049                 error("@ModelOperation method cannot be private", e);
  1050                 return false;
  1051             }
  1052             if (e.getReturnType().getKind() != TypeKind.VOID) {
  1053                 error("@ModelOperation method should return void", e);
  1054                 return false;
  1055             }
  1056             List<String> args = new ArrayList<String>();
  1057             {
  1058                 body.append("  public void ").append(m.getSimpleName()).append("(");
  1059                 String sep = "";
  1060                 boolean checkFirst = true;
  1061                 for (VariableElement ve : e.getParameters()) {
  1062                     final TypeMirror type = ve.asType();
  1063                     CharSequence simpleName;
  1064                     if (type.getKind() == TypeKind.DECLARED) {
  1065                         simpleName = ((DeclaredType)type).asElement().getSimpleName();
  1066                     } else {
  1067                         simpleName = type.toString();
  1068                     }
  1069                     if (checkFirst && simpleName.toString().equals(className)) {
  1070                         checkFirst = false;
  1071                     } else {
  1072                         if (checkFirst) {
  1073                             error("First parameter of @ModelOperation method must be " + className, m);
  1074                             return false;
  1075                         }
  1076                         args.add(ve.getSimpleName().toString());
  1077                         body.append(sep).append("final ");
  1078                         body.append(ve.asType().toString()).append(" ");
  1079                         body.append(ve.toString());
  1080                         sep = ", ";
  1081                     }
  1082                 }
  1083                 body.append(") {\n");
  1084                 int idx = functions.size() / 2;
  1085                 functions.add(m.getSimpleName().toString());
  1086                 body.append("    proto.runInBrowser(" + idx);
  1087                 for (String s : args) {
  1088                     body.append(", ").append(s);
  1089                 }
  1090                 body.append(");\n");
  1091                 body.append("  }\n");
  1092 
  1093                 StringBuilder call = new StringBuilder();
  1094                 call.append("{ Object[] arr = (Object[])data; ");
  1095                 call.append(inPckName(clazz)).append(".").append(m.getSimpleName()).append("(");
  1096                 int i = 0;
  1097                 for (VariableElement ve : e.getParameters()) {
  1098                     if (i++ == 0) {
  1099                         call.append("model");
  1100                         continue;
  1101                     }
  1102                     String type = ve.asType().toString();
  1103                     String boxedType = findBoxedType(type);
  1104                     if (boxedType != null) {
  1105                         type = boxedType;
  1106                     }
  1107                     call.append(", ").append("(").append(type).append(")arr[").append(i - 2).append("]");
  1108                 }
  1109                 call.append("); }");
  1110                 functions.add(call.toString());
  1111             }
  1112 
  1113         }
  1114         return true;
  1115     }
  1116 
  1117 
  1118     private boolean generateReceive(
  1119         Element clazz, StringWriter body, String className,
  1120         List<? extends Element> enclosedElements, StringBuilder inType
  1121     ) {
  1122         boolean ret = generateReceiveImpl(clazz, body, className, enclosedElements, inType);
  1123         if (!ret) {
  1124             inType.setLength(0);
  1125         }
  1126         return ret;
  1127     }
  1128     private boolean generateReceiveImpl(
  1129         Element clazz, StringWriter body, String className,
  1130         List<? extends Element> enclosedElements, StringBuilder inType
  1131     ) {
  1132         inType.append("  @Override public void onMessage(").append(className).append(" model, int index, int type, Object data, Object[] params) {\n");
  1133         inType.append("    switch (index) {\n");
  1134         int index = 0;
  1135         boolean ok = true;
  1136         for (Element m : enclosedElements) {
  1137             if (m.getKind() != ElementKind.METHOD) {
  1138                 continue;
  1139             }
  1140             ExecutableElement e = (ExecutableElement)m;
  1141             OnReceive onR = e.getAnnotation(OnReceive.class);
  1142             if (onR == null) {
  1143                 continue;
  1144             }
  1145             if (!e.getModifiers().contains(Modifier.STATIC)) {
  1146                 error("@OnReceive method needs to be static", e);
  1147                 return false;
  1148             }
  1149             if (e.getModifiers().contains(Modifier.PRIVATE)) {
  1150                 error("@OnReceive method cannot be private", e);
  1151                 return false;
  1152             }
  1153             if (e.getReturnType().getKind() != TypeKind.VOID) {
  1154                 error("@OnReceive method should return void", e);
  1155                 return false;
  1156             }
  1157             if (!onR.jsonp().isEmpty() && !"GET".equals(onR.method())) {
  1158                 error("JSONP works only with GET transport method", e);
  1159             }
  1160             String dataMirror = findDataSpecified(e, onR);
  1161             if ("PUT".equals(onR.method()) && dataMirror == null) {
  1162                 error("PUT method needs to specify a data() class", e);
  1163                 return false;
  1164             }
  1165             if ("POST".equals(onR.method()) && dataMirror == null) {
  1166                 error("POST method needs to specify a data() class", e);
  1167                 return false;
  1168             }
  1169             if (e.getParameters().size() < 2) {
  1170                 error("@OnReceive method needs at least two parameters", e);
  1171             }
  1172             final boolean isWebSocket = "WebSocket".equals(onR.method());
  1173             if (isWebSocket && dataMirror == null) {
  1174                 error("WebSocket method needs to specify a data() class", e);
  1175             }
  1176             int expectsList = 0;
  1177             List<String> args = new ArrayList<String>();
  1178             List<String> params = new ArrayList<String>();
  1179             // first argument is model class
  1180             {
  1181                 TypeMirror type = e.getParameters().get(0).asType();
  1182                 CharSequence simpleName;
  1183                 if (type.getKind() == TypeKind.DECLARED) {
  1184                     simpleName = ((DeclaredType) type).asElement().getSimpleName();
  1185                 } else {
  1186                     simpleName = type.toString();
  1187                 }
  1188                 if (simpleName.toString().equals(className)) {
  1189                     args.add("model");
  1190                 } else {
  1191                     error("First parameter needs to be " + className, e);
  1192                     return false;
  1193                 }
  1194             }
  1195 
  1196             String modelClass;
  1197             {
  1198                 final Types tu = processingEnv.getTypeUtils();
  1199                 TypeMirror type = e.getParameters().get(1).asType();
  1200                 TypeMirror modelType = null;
  1201                 TypeMirror ert = tu.erasure(type);
  1202 
  1203                 if (isModel(type)) {
  1204                     modelType = type;
  1205                 } else if (type.getKind() == TypeKind.ARRAY) {
  1206                     modelType = ((ArrayType)type).getComponentType();
  1207                     expectsList = 1;
  1208                 } else if ("java.util.List".equals(fqn(ert, e))) {
  1209                     List<? extends TypeMirror> typeArgs = ((DeclaredType)type).getTypeArguments();
  1210                     if (typeArgs.size() == 1) {
  1211                         modelType = typeArgs.get(0);
  1212                         expectsList = 2;
  1213                     }
  1214                 } else if (type.toString().equals("java.lang.String")) {
  1215                     modelType = type;
  1216                 }
  1217                 if (modelType == null) {
  1218                     error("Second arguments needs to be a model, String or array or List of models", e);
  1219                     return false;
  1220                 }
  1221                 modelClass = modelType.toString();
  1222                 if (expectsList == 1) {
  1223                     args.add("arr");
  1224                 } else if (expectsList == 2) {
  1225                     args.add("java.util.Arrays.asList(arr)");
  1226                 } else {
  1227                     args.add("arr[0]");
  1228                 }
  1229             }
  1230             String n = e.getSimpleName().toString();
  1231             if (isWebSocket) {
  1232                 body.append("  /** Performs WebSocket communication. Call with <code>null</code> data parameter\n");
  1233                 body.append("  * to open the connection (even if not required). Call with non-null data to\n");
  1234                 body.append("  * send messages to server. Call again with <code>null</code> data to close the socket.\n");
  1235                 body.append("  */\n");
  1236                 if (onR.headers().length > 0) {
  1237                     error("WebSocket spec does not support headers", e);
  1238                 }
  1239             }
  1240             body.append("  public void ").append(n).append("(");
  1241             StringBuilder urlBefore = new StringBuilder();
  1242             StringBuilder urlAfter = new StringBuilder();
  1243             StringBuilder headers = new StringBuilder();
  1244             String jsonpVarName = null;
  1245             {
  1246                 String sep = "";
  1247                 boolean skipJSONP = onR.jsonp().isEmpty();
  1248                 Set<String> receiveParams = new LinkedHashSet<String>();
  1249                 findParamNames(receiveParams, e, onR.url(), onR.jsonp(), urlBefore, urlAfter);
  1250                 for (String headerLine : onR.headers()) {
  1251                     if (headerLine.contains("\r") || headerLine.contains("\n")) {
  1252                         error("Header line cannot contain line separator", e);
  1253                     }
  1254                     findParamNames(receiveParams, e, headerLine, null, headers);
  1255                     headers.append("+ \"\\r\\n\" +\n");
  1256                 }
  1257                 if (headers.length() > 0) {
  1258                     headers.append("\"\"");
  1259                 }
  1260                 for (String p : receiveParams) {
  1261                     if (!skipJSONP && p.equals(onR.jsonp())) {
  1262                         skipJSONP = true;
  1263                         jsonpVarName = p;
  1264                         continue;
  1265                     }
  1266                     body.append(sep);
  1267                     body.append("String ").append(p);
  1268                     sep = ", ";
  1269                 }
  1270                 if (!skipJSONP) {
  1271                     error(
  1272                         "Name of jsonp attribute ('" + onR.jsonp() +
  1273                         "') is not used in url attribute '" + onR.url() + "'", e
  1274                     );
  1275                 }
  1276                 if (dataMirror != null) {
  1277                     body.append(sep).append(dataMirror.toString()).append(" data");
  1278                 }
  1279                 for (int i = 2; i < e.getParameters().size(); i++) {
  1280                     if (isWebSocket) {
  1281                         error("@OnReceive(method=\"WebSocket\") can only have two arguments", e);
  1282                         ok = false;
  1283                     }
  1284 
  1285                     VariableElement ve = e.getParameters().get(i);
  1286                     body.append(sep).append(ve.asType().toString()).append(" ").append(ve.getSimpleName());
  1287                     final String tp = ve.asType().toString();
  1288                     String btn = findBoxedType(tp);
  1289                     if (btn == null) {
  1290                         btn = tp;
  1291                     }
  1292                     args.add("(" + btn + ")params[" + (i - 2) + "]");
  1293                     params.add(ve.getSimpleName().toString());
  1294                     sep = ", ";
  1295                 }
  1296             }
  1297             body.append(") {\n");
  1298             boolean webSocket = onR.method().equals("WebSocket");
  1299             if (webSocket) {
  1300                 if (generateWSReceiveBody(index++, body, inType, onR, e, clazz, className, expectsList != 0, modelClass, n, args, params, urlBefore, jsonpVarName, urlAfter, dataMirror, headers)) {
  1301                     return false;
  1302                 }
  1303                 body.append("  }\n");
  1304                 body.append("  private Object ws_" + e.getSimpleName() + ";\n");
  1305             } else {
  1306                 if (generateJSONReceiveBody(index++, body, inType, onR, e, clazz, className, expectsList != 0, modelClass, n, args, params, urlBefore, jsonpVarName, urlAfter, dataMirror, headers)) {
  1307                     ok = false;
  1308                 }
  1309                 body.append("  }\n");
  1310             }
  1311         }
  1312         inType.append("    }\n");
  1313         inType.append("    throw new UnsupportedOperationException(\"index: \" + index + \" type: \" + type);\n");
  1314         inType.append("  }\n");
  1315         return ok;
  1316     }
  1317 
  1318     private boolean generateJSONReceiveBody(int index, StringWriter method, StringBuilder body, OnReceive onR, ExecutableElement e, Element clazz, String className, boolean expectsList, String modelClass, String n, List<String> args, List<String> params, StringBuilder urlBefore, String jsonpVarName, StringBuilder urlAfter, String dataMirror, StringBuilder headers) {
  1319         boolean error = false;
  1320         body.append(
  1321             "    case " + index + ": {\n" +
  1322             "      if (type == 2) { /* on error */\n" +
  1323             "        Exception ex = (Exception)data;\n"
  1324             );
  1325         if (onR.onError().isEmpty()) {
  1326             body.append(
  1327                 "        ex.printStackTrace();\n"
  1328                 );
  1329         } else {
  1330             error = !findOnError(e, ((TypeElement)clazz), onR.onError(), className);
  1331             body.append("        ").append(clazz.getSimpleName()).append(".").append(onR.onError()).append("(");
  1332             body.append("model, ex);\n");
  1333         }
  1334         body.append(
  1335             "        return;\n" +
  1336             "      } else if (type == 1) {\n" +
  1337             "        Object[] ev = (Object[])data;\n"
  1338             );
  1339         if (expectsList) {
  1340             body.append(
  1341                 "        " + modelClass + "[] arr = new " + modelClass + "[ev.length];\n"
  1342                 );
  1343         } else {
  1344             body.append(
  1345                 "        " + modelClass + "[] arr = { null };\n"
  1346                 );
  1347         }
  1348         body.append(
  1349             "        TYPE.copyJSON(model.proto.getContext(), ev, " + modelClass + ".class, arr);\n"
  1350         );
  1351         {
  1352             body.append("        ").append(clazz.getSimpleName()).append(".").append(n).append("(");
  1353             String sep = "";
  1354             for (String arg : args) {
  1355                 body.append(sep);
  1356                 body.append(arg);
  1357                 sep = ", ";
  1358             }
  1359             body.append(");\n");
  1360         }
  1361         body.append(
  1362             "        return;\n" +
  1363             "      }\n" +
  1364             "    }\n"
  1365             );
  1366         method.append("    proto.loadJSONWithHeaders(" + index + ",\n        ");
  1367         method.append(headers.length() == 0 ? "null" : headers).append(",\n        ");
  1368         method.append(urlBefore).append(", ");
  1369         if (jsonpVarName != null) {
  1370             method.append(urlAfter);
  1371         } else {
  1372             method.append("null");
  1373         }
  1374         if (!"GET".equals(onR.method()) || dataMirror != null) {
  1375             method.append(", \"").append(onR.method()).append('"');
  1376             if (dataMirror != null) {
  1377                 method.append(", data");
  1378             } else {
  1379                 method.append(", null");
  1380             }
  1381         } else {
  1382             method.append(", null, null");
  1383         }
  1384         for (String a : params) {
  1385             method.append(", ").append(a);
  1386         }
  1387         method.append(");\n");
  1388         return error;
  1389     }
  1390 
  1391     private boolean generateWSReceiveBody(int index, StringWriter method, StringBuilder body, OnReceive onR, ExecutableElement e, Element clazz, String className, boolean expectsList, String modelClass, String n, List<String> args, List<String> params, StringBuilder urlBefore, String jsonpVarName, StringBuilder urlAfter, String dataMirror, StringBuilder headers) {
  1392         body.append(
  1393             "    case " + index + ": {\n" +
  1394             "      if (type == 0) { /* on open */\n" +
  1395             "        ").append(inPckName(clazz)).append(".").append(n).append("(");
  1396         {
  1397             String sep = "";
  1398             for (String arg : args) {
  1399                 body.append(sep);
  1400                 if (arg.startsWith("arr") || arg.startsWith("java.util.Array")) {
  1401                     body.append("null");
  1402                 } else {
  1403                     body.append(arg);
  1404                 }
  1405                 sep = ", ";
  1406             }
  1407         }
  1408         body.append(");\n");
  1409         body.append(
  1410             "        return;\n" +
  1411             "      } else if (type == 2) { /* on error */\n" +
  1412             "        Exception value = (Exception)data;\n"
  1413             );
  1414         if (onR.onError().isEmpty()) {
  1415             body.append(
  1416                 "        value.printStackTrace();\n"
  1417                 );
  1418         } else {
  1419             if (!findOnError(e, ((TypeElement)clazz), onR.onError(), className)) {
  1420                 return true;
  1421             }
  1422             body.append("        ").append(inPckName(clazz)).append(".").append(onR.onError()).append("(");
  1423             body.append("model, value);\n");
  1424         }
  1425         body.append(
  1426             "        return;\n" +
  1427             "      } else if (type == 1) {\n" +
  1428             "        Object[] ev = (Object[])data;\n"
  1429         );
  1430         if (expectsList) {
  1431             body.append(
  1432                 "        " + modelClass + "[] arr = new " + modelClass + "[ev.length];\n"
  1433                 );
  1434         } else {
  1435             body.append(
  1436                 "        " + modelClass + "[] arr = { null };\n"
  1437                 );
  1438         }
  1439         body.append(
  1440             "        TYPE.copyJSON(model.proto.getContext(), ev, " + modelClass + ".class, arr);\n"
  1441         );
  1442         {
  1443             body.append("        ").append(inPckName(clazz)).append(".").append(n).append("(");
  1444             String sep = "";
  1445             for (String arg : args) {
  1446                 body.append(sep);
  1447                 body.append(arg);
  1448                 sep = ", ";
  1449             }
  1450             body.append(");\n");
  1451         }
  1452         body.append(
  1453             "        return;\n" +
  1454             "      }"
  1455         );
  1456         if (!onR.onError().isEmpty()) {
  1457             body.append(" else if (type == 3) { /* on close */\n");
  1458             body.append("        ").append(inPckName(clazz)).append(".").append(onR.onError()).append("(");
  1459             body.append("model, null);\n");
  1460             body.append(
  1461                 "        return;" +
  1462                 "      }"
  1463             );
  1464         }
  1465         body.append("\n");
  1466         body.append("    }\n");
  1467         method.append("    if (this.ws_").append(e.getSimpleName()).append(" == null) {\n");
  1468         method.append("      this.ws_").append(e.getSimpleName());
  1469         method.append("= proto.wsOpen(" + index + ", ");
  1470         method.append(urlBefore).append(", data);\n");
  1471         method.append("    } else {\n");
  1472         method.append("      proto.wsSend(this.ws_").append(e.getSimpleName()).append(", ").append(urlBefore).append(", data");
  1473         for (String a : params) {
  1474             method.append(", ").append(a);
  1475         }
  1476         method.append(");\n");
  1477         method.append("    }\n");
  1478         return false;
  1479     }
  1480 
  1481     private CharSequence wrapParams(
  1482         ExecutableElement ee, String id, String className, String classRef, String evName, String dataName
  1483     ) {
  1484         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
  1485         StringBuilder params = new StringBuilder();
  1486         boolean first = true;
  1487         for (VariableElement ve : ee.getParameters()) {
  1488             if (!first) {
  1489                 params.append(", ");
  1490             }
  1491             first = false;
  1492             String toCall = null;
  1493             String toFinish = null;
  1494             boolean addNull = true;
  1495             if (ve.asType() == stringType) {
  1496                 if (ve.getSimpleName().contentEquals("id")) {
  1497                     params.append('"').append(id).append('"');
  1498                     continue;
  1499                 }
  1500                 toCall = classRef + ".proto.toString(";
  1501             }
  1502             if (ve.asType().getKind() == TypeKind.DOUBLE) {
  1503                 toCall = classRef + ".proto.toNumber(";
  1504                 toFinish = ".doubleValue()";
  1505             }
  1506             if (ve.asType().getKind() == TypeKind.FLOAT) {
  1507                 toCall = classRef + ".proto.toNumber(";
  1508                 toFinish = ".floatValue()";
  1509             }
  1510             if (ve.asType().getKind() == TypeKind.INT) {
  1511                 toCall = classRef + ".proto.toNumber(";
  1512                 toFinish = ".intValue()";
  1513             }
  1514             if (ve.asType().getKind() == TypeKind.BYTE) {
  1515                 toCall = classRef + ".proto.toNumber(";
  1516                 toFinish = ".byteValue()";
  1517             }
  1518             if (ve.asType().getKind() == TypeKind.SHORT) {
  1519                 toCall = classRef + ".proto.toNumber(";
  1520                 toFinish = ".shortValue()";
  1521             }
  1522             if (ve.asType().getKind() == TypeKind.LONG) {
  1523                 toCall = classRef + ".proto.toNumber(";
  1524                 toFinish = ".longValue()";
  1525             }
  1526             if (ve.asType().getKind() == TypeKind.BOOLEAN) {
  1527                 toCall = "\"true\".equals(" + classRef + ".proto.toString(";
  1528                 toFinish = ")";
  1529             }
  1530             if (ve.asType().getKind() == TypeKind.CHAR) {
  1531                 toCall = "(char)" + classRef + ".proto.toNumber(";
  1532                 toFinish = ".intValue()";
  1533             }
  1534             if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
  1535                 toCall = classRef + ".proto.toModel(" + ve.asType() + ".class, ";
  1536                 addNull = false;
  1537             }
  1538 
  1539             if (toCall != null) {
  1540                 params.append(toCall);
  1541                 if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
  1542                     params.append(dataName);
  1543                     if (addNull) {
  1544                         params.append(", null");
  1545                     }
  1546                 } else {
  1547                     if (evName == null) {
  1548                         final StringBuilder sb = new StringBuilder();
  1549                         sb.append("Unexpected string parameter name.");
  1550                         if (dataName != null) {
  1551                             sb.append(" Try \"").append(dataName).append("\"");
  1552                         }
  1553                         error(sb.toString(), ee);
  1554                     }
  1555                     params.append(evName);
  1556                     params.append(", \"");
  1557                     params.append(ve.getSimpleName().toString());
  1558                     params.append("\"");
  1559                 }
  1560                 params.append(")");
  1561                 if (toFinish != null) {
  1562                     params.append(toFinish);
  1563                 }
  1564                 continue;
  1565             }
  1566             String rn = fqn(ve.asType(), ee);
  1567             int last = rn.lastIndexOf('.');
  1568             if (last >= 0) {
  1569                 rn = rn.substring(last + 1);
  1570             }
  1571             if (rn.equals(className)) {
  1572                 params.append(classRef);
  1573                 continue;
  1574             }
  1575             StringBuilder err = new StringBuilder();
  1576             err.append("Argument ").
  1577                 append(ve.getSimpleName()).
  1578                 append(" is not valid. The annotated method can only accept ").
  1579                 append(className).
  1580                 append(" argument");
  1581             if (dataName != null) {
  1582                 err.append(" or argument named '").append(dataName).append("'");
  1583             }
  1584             err.append(".");
  1585             error(err.toString(), ee);
  1586         }
  1587         return params;
  1588     }
  1589 
  1590 
  1591     private CharSequence wrapPropName(
  1592         ExecutableElement ee, String className, String propName, String propValue
  1593     ) {
  1594         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
  1595         StringBuilder params = new StringBuilder();
  1596         boolean first = true;
  1597         for (VariableElement ve : ee.getParameters()) {
  1598             if (!first) {
  1599                 params.append(", ");
  1600             }
  1601             first = false;
  1602             if (ve.asType() == stringType) {
  1603                 if (propName != null && ve.getSimpleName().contentEquals(propName)) {
  1604                     params.append('"').append(propValue).append('"');
  1605                 } else {
  1606                     error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
  1607                 }
  1608                 continue;
  1609             }
  1610             String rn = fqn(ve.asType(), ee);
  1611             int last = rn.lastIndexOf('.');
  1612             if (last >= 0) {
  1613                 rn = rn.substring(last + 1);
  1614             }
  1615             if (rn.equals(className)) {
  1616                 params.append("model");
  1617                 continue;
  1618             }
  1619             error(
  1620                 "@OnPrprtChange method can only accept String or " + className + " arguments",
  1621                 ee);
  1622         }
  1623         return params;
  1624     }
  1625 
  1626     private boolean isModel(TypeMirror tm) {
  1627         if (tm.getKind() == TypeKind.ERROR) {
  1628             return true;
  1629         }
  1630         final Element e = processingEnv.getTypeUtils().asElement(tm);
  1631         if (e == null) {
  1632             return false;
  1633         }
  1634         for (Element ch : e.getEnclosedElements()) {
  1635             if (ch.getKind() == ElementKind.METHOD) {
  1636                 ExecutableElement ee = (ExecutableElement)ch;
  1637                 if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
  1638                     return true;
  1639                 }
  1640             }
  1641         }
  1642         return models.values().contains(e.getSimpleName().toString());
  1643     }
  1644 
  1645     private void writeToString(Prprt[] props, Writer w) throws IOException {
  1646         w.write("  public String toString() {\n");
  1647         w.write("    StringBuilder sb = new StringBuilder();\n");
  1648         w.write("    sb.append('{');\n");
  1649         String sep = "";
  1650         for (Prprt p : props) {
  1651             w.write(sep);
  1652             w.append("    sb.append('\"').append(\"" + p.name() + "\")");
  1653                 w.append(".append('\"').append(\":\");\n");
  1654             w.append("    sb.append(TYPE.toJSON(prop_");
  1655             w.append(p.name()).append("));\n");
  1656             sep =    "    sb.append(',');\n";
  1657         }
  1658         w.write("    sb.append('}');\n");
  1659         w.write("    return sb.toString();\n");
  1660         w.write("  }\n");
  1661     }
  1662     private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
  1663         w.write("  public " + className + " clone() {\n");
  1664         w.write("    return clone(proto.getContext());\n");
  1665         w.write("  }\n");
  1666         w.write("  private " + className + " clone(net.java.html.BrwsrCtx ctx) {\n");
  1667         w.write("    " + className + " ret = new " + className + "(ctx);\n");
  1668         for (Prprt p : props) {
  1669             String tn = typeName(p);
  1670             String[] gs = toGetSet(p.name(), tn, p.array());
  1671             if (!p.array()) {
  1672                 boolean isModel[] = { false };
  1673                 boolean isEnum[] = { false };
  1674                 boolean isPrimitive[] = { false };
  1675                 checkType(p, isModel, isEnum, isPrimitive);
  1676                 if (!isModel[0]) {
  1677                     w.write("    ret.prop_" + p.name() + " = " + gs[0] + "();\n");
  1678                     continue;
  1679                 }
  1680                 w.write("    ret.prop_" + p.name() + " =  " + gs[0] + "()  == null ? null : prop_" + p.name() + ".clone();\n");
  1681             } else {
  1682                 w.write("    proto.cloneList(ret." + gs[0] + "(), ctx, prop_" + p.name() + ");\n");
  1683             }
  1684         }
  1685 
  1686         w.write("    return ret;\n");
  1687         w.write("  }\n");
  1688     }
  1689 
  1690     private String inPckName(Element e) {
  1691         StringBuilder sb = new StringBuilder();
  1692         while (e.getKind() != ElementKind.PACKAGE) {
  1693             if (sb.length() == 0) {
  1694                 sb.append(e.getSimpleName());
  1695             } else {
  1696                 sb.insert(0, '.');
  1697                 sb.insert(0, e.getSimpleName());
  1698             }
  1699             e = e.getEnclosingElement();
  1700         }
  1701         return sb.toString();
  1702     }
  1703 
  1704     private String fqn(TypeMirror pt, Element relative) {
  1705         if (pt.getKind() == TypeKind.ERROR) {
  1706             final Elements eu = processingEnv.getElementUtils();
  1707             PackageElement pckg = eu.getPackageOf(relative);
  1708             return pckg.getQualifiedName() + "." + pt.toString();
  1709         }
  1710         return pt.toString();
  1711     }
  1712 
  1713     private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
  1714         TypeMirror tm;
  1715         try {
  1716             String ret = p.typeName(processingEnv);
  1717             TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
  1718             if (e == null) {
  1719                 isModel[0] = true;
  1720                 isEnum[0] = false;
  1721                 isPrimitive[0] = false;
  1722                 return ret;
  1723             }
  1724             tm = e.asType();
  1725         } catch (MirroredTypeException ex) {
  1726             tm = ex.getTypeMirror();
  1727         }
  1728         tm = processingEnv.getTypeUtils().erasure(tm);
  1729         if (isPrimitive[0] = tm.getKind().isPrimitive()) {
  1730             isEnum[0] = false;
  1731             isModel[0] = false;
  1732             return tm.toString();
  1733         }
  1734         final Element e = processingEnv.getTypeUtils().asElement(tm);
  1735         if (e.getKind() == ElementKind.CLASS && tm.getKind() == TypeKind.ERROR) {
  1736             isModel[0] = true;
  1737             isEnum[0] = false;
  1738             return e.getSimpleName().toString();
  1739         }
  1740 
  1741         final Model m = e == null ? null : e.getAnnotation(Model.class);
  1742         String ret;
  1743         if (m != null) {
  1744             ret = findPkgName(e) + '.' + m.className();
  1745             isModel[0] = true;
  1746             models.put(e, m.className());
  1747         } else if (findModelForMthd(e)) {
  1748             ret = ((TypeElement)e).getQualifiedName().toString();
  1749             isModel[0] = true;
  1750         } else {
  1751             ret = tm.toString();
  1752         }
  1753         TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
  1754         enm = processingEnv.getTypeUtils().erasure(enm);
  1755         isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
  1756         return ret;
  1757     }
  1758 
  1759     private static boolean findModelForMthd(Element clazz) {
  1760         if (clazz == null) {
  1761             return false;
  1762         }
  1763         for (Element e : clazz.getEnclosedElements()) {
  1764             if (e.getKind() == ElementKind.METHOD) {
  1765                 ExecutableElement ee = (ExecutableElement)e;
  1766                 if (
  1767                     ee.getSimpleName().contentEquals("modelFor") &&
  1768                     ee.getParameters().isEmpty()
  1769                 ) {
  1770                     return true;
  1771                 }
  1772             }
  1773         }
  1774         return false;
  1775     }
  1776 
  1777     private void findParamNames(
  1778         Set<String> params, Element e, String url, String jsonParam, StringBuilder... both
  1779     ) {
  1780         int wasJSON = 0;
  1781 
  1782         for (int pos = 0; ;) {
  1783             int next = url.indexOf('{', pos);
  1784             if (next == -1) {
  1785                 both[wasJSON].append('"')
  1786                     .append(url.substring(pos).replace("\"", "\\\""))
  1787                     .append('"');
  1788                 return;
  1789             }
  1790             int close = url.indexOf('}', next);
  1791             if (close == -1) {
  1792                 error("Unbalanced '{' and '}' in " + url, e);
  1793                 return;
  1794             }
  1795             final String paramName = url.substring(next + 1, close);
  1796             params.add(paramName);
  1797             if (paramName.equals(jsonParam) && !jsonParam.isEmpty()) {
  1798                 both[wasJSON].append('"')
  1799                     .append(url.substring(pos, next).replace("\"", "\\\""))
  1800                     .append('"');
  1801                 wasJSON = 1;
  1802             } else {
  1803                 both[wasJSON].append('"')
  1804                     .append(url.substring(pos, next).replace("\"", "\\\""))
  1805                     .append("\" + ").append(paramName).append(" + ");
  1806             }
  1807             pos = close + 1;
  1808         }
  1809     }
  1810 
  1811     private static Prprt findPrprt(Prprt[] properties, String propName) {
  1812         for (Prprt p : properties) {
  1813             if (propName.equals(p.name())) {
  1814                 return p;
  1815             }
  1816         }
  1817         return null;
  1818     }
  1819 
  1820     private boolean isPrimitive(String type) {
  1821         return
  1822             "int".equals(type) ||
  1823             "double".equals(type) ||
  1824             "long".equals(type) ||
  1825             "short".equals(type) ||
  1826             "byte".equals(type) ||
  1827             "char".equals(type) ||
  1828             "boolean".equals(type) ||
  1829             "float".equals(type);
  1830     }
  1831 
  1832     private static Collection<String> findDerivedFrom(Map<String, Collection<String[]>> propsDeps, String derivedProp) {
  1833         Set<String> names = new HashSet<String>();
  1834         for (Map.Entry<String, Collection<String[]>> e : propsDeps.entrySet()) {
  1835             for (String[] pair : e.getValue()) {
  1836                 if (pair[0].equals(derivedProp)) {
  1837                     names.add(e.getKey());
  1838                     break;
  1839                 }
  1840             }
  1841         }
  1842         return names;
  1843     }
  1844 
  1845     private Prprt[] createProps(Element e, Property[] arr) {
  1846         Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
  1847         Prprt[] prev = verify.put(e, ret);
  1848         if (prev != null) {
  1849             error("Two sets of properties for ", e);
  1850         }
  1851         return ret;
  1852     }
  1853 
  1854     private static String strip(String s) {
  1855         int indx = s.indexOf("__");
  1856         if (indx >= 0) {
  1857             return s.substring(0, indx);
  1858         } else {
  1859             return s;
  1860         }
  1861     }
  1862 
  1863     private String findDataSpecified(ExecutableElement e, OnReceive onR) {
  1864         try {
  1865             return onR.data().getName();
  1866         } catch (MirroredTypeException ex) {
  1867             final TypeMirror tm = ex.getTypeMirror();
  1868             String name;
  1869             final Element te = processingEnv.getTypeUtils().asElement(tm);
  1870             if (te.getKind() == ElementKind.CLASS && tm.getKind() == TypeKind.ERROR) {
  1871                 name = te.getSimpleName().toString();
  1872             } else {
  1873                 name = tm.toString();
  1874             }
  1875             return "java.lang.Object".equals(name) ? null : name;
  1876         } catch (Exception ex) {
  1877             // fallback
  1878         }
  1879 
  1880         AnnotationMirror found = null;
  1881         for (AnnotationMirror am : e.getAnnotationMirrors()) {
  1882             if (am.getAnnotationType().toString().equals(OnReceive.class.getName())) {
  1883                 found = am;
  1884             }
  1885         }
  1886         if (found == null) {
  1887             return null;
  1888         }
  1889 
  1890         for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : found.getElementValues().entrySet()) {
  1891             ExecutableElement ee = entry.getKey();
  1892             AnnotationValue av = entry.getValue();
  1893             if (ee.getSimpleName().contentEquals("data")) {
  1894                 List<? extends Object> values = getAnnoValues(processingEnv, e, found);
  1895                 for (Object v : values) {
  1896                     String sv = v.toString();
  1897                     if (sv.startsWith("data = ") && sv.endsWith(".class")) {
  1898                         return sv.substring(7, sv.length() - 6);
  1899                     }
  1900                 }
  1901                 return "error";
  1902             }
  1903         }
  1904         return null;
  1905     }
  1906 
  1907     static List<? extends Object> getAnnoValues(ProcessingEnvironment pe, Element e, AnnotationMirror am) {
  1908         try {
  1909             Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
  1910             Method m = trees.getMethod("instance", ProcessingEnvironment.class);
  1911             Object instance = m.invoke(null, pe);
  1912             m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
  1913             Object path = m.invoke(instance, e, am);
  1914             m = path.getClass().getMethod("getLeaf");
  1915             Object leaf = m.invoke(path);
  1916             m = leaf.getClass().getMethod("getArguments");
  1917             return (List) m.invoke(leaf);
  1918         } catch (Exception ex) {
  1919             return Collections.emptyList();
  1920         }
  1921     }
  1922 
  1923     private static String findTargetId(Element e) {
  1924         for (AnnotationMirror m : e.getAnnotationMirrors()) {
  1925             if (m.getAnnotationType().toString().equals(Model.class.getName())) {
  1926                 for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entrySet : m.getElementValues().entrySet()) {
  1927                     ExecutableElement key = entrySet.getKey();
  1928                     AnnotationValue value = entrySet.getValue();
  1929                     if ("targetId()".equals(key.toString())) {
  1930                         return value.toString();
  1931                     }
  1932                 }
  1933             }
  1934         }
  1935         return null;
  1936     }
  1937 
  1938     private static class Prprt {
  1939         private final Element e;
  1940         private final AnnotationMirror tm;
  1941         private final Property p;
  1942 
  1943         public Prprt(Element e, AnnotationMirror tm, Property p) {
  1944             this.e = e;
  1945             this.tm = tm;
  1946             this.p = p;
  1947         }
  1948 
  1949         String name() {
  1950             return p.name();
  1951         }
  1952 
  1953         boolean array() {
  1954             return p.array();
  1955         }
  1956 
  1957         String typeName(ProcessingEnvironment env) {
  1958             RuntimeException ex;
  1959             try {
  1960                 return p.type().getName();
  1961             } catch (IncompleteAnnotationException e) {
  1962                 ex = e;
  1963             } catch (AnnotationTypeMismatchException e) {
  1964                 ex = e;
  1965             }
  1966             for (Object v : getAnnoValues(env, e, tm)) {
  1967                 String s = v.toString().replace(" ", "");
  1968                 if (s.startsWith("type=") && s.endsWith(".class")) {
  1969                     return s.substring(5, s.length() - 6);
  1970                 }
  1971             }
  1972             throw ex;
  1973         }
  1974 
  1975 
  1976         static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
  1977             if (arr.length == 0) {
  1978                 return new Prprt[0];
  1979             }
  1980 
  1981             if (e.getKind() != ElementKind.CLASS) {
  1982                 throw new IllegalStateException("" + e.getKind());
  1983             }
  1984             TypeElement te = (TypeElement)e;
  1985             List<? extends AnnotationValue> val = null;
  1986             for (AnnotationMirror an : te.getAnnotationMirrors()) {
  1987                 for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
  1988                     if (entry.getKey().getSimpleName().contentEquals("properties")) {
  1989                         val = (List)entry.getValue().getValue();
  1990                         break;
  1991                     }
  1992                 }
  1993             }
  1994             if (val == null || val.size() != arr.length) {
  1995                 pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
  1996                 return new Prprt[0];
  1997             }
  1998             Prprt[] ret = new Prprt[arr.length];
  1999             BIG: for (int i = 0; i < ret.length; i++) {
  2000                 AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
  2001                 ret[i] = new Prprt(e, am, arr[i]);
  2002 
  2003             }
  2004             return ret;
  2005         }
  2006     } // end of Prprt
  2007 
  2008     private static final class GetSet {
  2009         final String name;
  2010         final String getter;
  2011         final String setter;
  2012         final String type;
  2013         final boolean readOnly;
  2014         GetSet(String name, String getter, String setter, String type, boolean readOnly) {
  2015             this.name = name;
  2016             this.getter = getter;
  2017             this.setter = setter;
  2018             this.type = type;
  2019             this.readOnly = readOnly;
  2020         }
  2021     }
  2022 
  2023     @Override
  2024     public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
  2025         final Level l = Level.FINE;
  2026         LOG.log(l, " element: {0}", element);
  2027         LOG.log(l, " annotation: {0}", annotation);
  2028         LOG.log(l, " member: {0}", member);
  2029         LOG.log(l, " userText: {0}", userText);
  2030         LOG.log(l, "str: {0}", annotation.getAnnotationType().toString());
  2031         if (annotation.getAnnotationType().toString().equals(OnReceive.class.getName())) {
  2032             if (member.getSimpleName().contentEquals("method")) {
  2033                 return Arrays.asList(
  2034                     methodOf("GET"),
  2035                     methodOf("POST"),
  2036                     methodOf("PUT"),
  2037                     methodOf("DELETE"),
  2038                     methodOf("HEAD"),
  2039                     methodOf("WebSocket")
  2040                 );
  2041             }
  2042         }
  2043 
  2044         return super.getCompletions(element, annotation, member, userText);
  2045     }
  2046 
  2047     private static final Completion methodOf(String method) {
  2048         ResourceBundle rb = ResourceBundle.getBundle("org.netbeans.html.json.impl.Bundle");
  2049         return Completions.of('"' + method + '"', rb.getString("MSG_Completion_" + method));
  2050     }
  2051 
  2052     private boolean findOnError(ExecutableElement errElem, TypeElement te, String name, String className) {
  2053         String err = null;
  2054         METHODS:
  2055         for (Element e : te.getEnclosedElements()) {
  2056             if (e.getKind() != ElementKind.METHOD) {
  2057                 continue;
  2058             }
  2059             if (!e.getSimpleName().contentEquals(name)) {
  2060                 continue;
  2061             }
  2062             if (!e.getModifiers().contains(Modifier.STATIC)) {
  2063                 errElem = (ExecutableElement) e;
  2064                 err = "Would have to be static";
  2065                 continue;
  2066             }
  2067             ExecutableElement ee = (ExecutableElement) e;
  2068             TypeMirror excType = processingEnv.getElementUtils().getTypeElement(Exception.class.getName()).asType();
  2069             final List<? extends VariableElement> params = ee.getParameters();
  2070             boolean error = false;
  2071             if (params.size() != 2) {
  2072                 error = true;
  2073             } else {
  2074                 String firstType = params.get(0).asType().toString();
  2075                 int lastDot = firstType.lastIndexOf('.');
  2076                 if (lastDot != -1) {
  2077                     firstType = firstType.substring(lastDot + 1);
  2078                 }
  2079                 if (!firstType.equals(className)) {
  2080                     error = true;
  2081                 }
  2082                 if (!processingEnv.getTypeUtils().isAssignable(excType, params.get(1).asType())) {
  2083                     error = true;
  2084                 }
  2085             }
  2086             if (error) {
  2087                 errElem = (ExecutableElement) e;
  2088                 err = "Error method first argument needs to be " + className + " and second Exception";
  2089                 continue;
  2090             }
  2091             return true;
  2092         }
  2093         if (err == null) {
  2094             err = "Cannot find " + name + "(" + className + ", Exception) method in this class";
  2095         }
  2096         error(err, errElem);
  2097         return false;
  2098     }
  2099 
  2100     private ExecutableElement findWrite(ExecutableElement computedPropElem, TypeElement te, String name, String className) {
  2101         String err = null;
  2102         METHODS:
  2103         for (Element e : te.getEnclosedElements()) {
  2104             if (e.getKind() != ElementKind.METHOD) {
  2105                 continue;
  2106             }
  2107             if (!e.getSimpleName().contentEquals(name)) {
  2108                 continue;
  2109             }
  2110             if (e.equals(computedPropElem)) {
  2111                 continue;
  2112             }
  2113             if (!e.getModifiers().contains(Modifier.STATIC)) {
  2114                 computedPropElem = (ExecutableElement) e;
  2115                 err = "Would have to be static";
  2116                 continue;
  2117             }
  2118             ExecutableElement ee = (ExecutableElement) e;
  2119             if (ee.getReturnType().getKind() != TypeKind.VOID) {
  2120                 computedPropElem = (ExecutableElement) e;
  2121                 err = "Write method has to return void";
  2122                 continue;
  2123             }
  2124             TypeMirror retType = computedPropElem.getReturnType();
  2125             final List<? extends VariableElement> params = ee.getParameters();
  2126             boolean error = false;
  2127             if (params.size() != 2) {
  2128                 error = true;
  2129             } else {
  2130                 String firstType = params.get(0).asType().toString();
  2131                 int lastDot = firstType.lastIndexOf('.');
  2132                 if (lastDot != -1) {
  2133                     firstType = firstType.substring(lastDot + 1);
  2134                 }
  2135                 if (!firstType.equals(className)) {
  2136                     error = true;
  2137                 }
  2138                 if (!processingEnv.getTypeUtils().isAssignable(retType, params.get(1).asType())) {
  2139                     error = true;
  2140                 }
  2141             }
  2142             if (error) {
  2143                 computedPropElem = (ExecutableElement) e;
  2144                 err = "Write method first argument needs to be " + className + " and second " + retType + " or Object";
  2145                 continue;
  2146             }
  2147             return ee;
  2148         }
  2149         if (err == null) {
  2150             err = "Cannot find " + name + "(" + className + ", value) method in this class";
  2151         }
  2152         error(err, computedPropElem);
  2153         return null;
  2154     }
  2155 
  2156 }