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