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