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