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