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