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