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