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