1st test to verify warnings coming from annotation processor
authorJaroslav Tulach <jtulach@netbeans.org>
Fri, 19 Apr 2013 11:11:07 +0200
changeset 3f7e90209da93
parent 2 dbb103216f69
child 4 d519a7891d77
1st test to verify warnings coming from annotation processor
json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java
json/src/main/java/org/apidesign/html/json/impl/PageProcessor.java
json/src/test/java/net/java/html/json/Compile.java
json/src/test/java/net/java/html/json/ModelProcessorTest.java
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java	Fri Apr 19 11:11:07 2013 +0200
     1.3 @@ -0,0 +1,1162 @@
     1.4 +/**
     1.5 + * HTML via Java(tm) Language Bindings
     1.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     1.7 + *
     1.8 + * This program is free software: you can redistribute it and/or modify
     1.9 + * it under the terms of the GNU General Public License as published by
    1.10 + * the Free Software Foundation, version 2 of the License.
    1.11 + *
    1.12 + * This program is distributed in the hope that it will be useful,
    1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.15 + * GNU General Public License for more details. apidesign.org
    1.16 + * designates this particular file as subject to the
    1.17 + * "Classpath" exception as provided by apidesign.org
    1.18 + * in the License file that accompanied this code.
    1.19 + *
    1.20 + * You should have received a copy of the GNU General Public License
    1.21 + * along with this program. Look for COPYING file in the top folder.
    1.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    1.23 + */
    1.24 +package org.apidesign.html.json.impl;
    1.25 +
    1.26 +import java.io.IOException;
    1.27 +import java.io.InputStream;
    1.28 +import java.io.OutputStreamWriter;
    1.29 +import java.io.StringWriter;
    1.30 +import java.io.Writer;
    1.31 +import java.lang.annotation.AnnotationTypeMismatchException;
    1.32 +import java.lang.annotation.IncompleteAnnotationException;
    1.33 +import java.lang.reflect.Method;
    1.34 +import java.util.ArrayList;
    1.35 +import java.util.Collection;
    1.36 +import java.util.Collections;
    1.37 +import java.util.HashMap;
    1.38 +import java.util.HashSet;
    1.39 +import java.util.LinkedHashSet;
    1.40 +import java.util.List;
    1.41 +import java.util.Map;
    1.42 +import java.util.Set;
    1.43 +import java.util.WeakHashMap;
    1.44 +import javax.annotation.processing.AbstractProcessor;
    1.45 +import javax.annotation.processing.ProcessingEnvironment;
    1.46 +import javax.annotation.processing.Processor;
    1.47 +import javax.annotation.processing.RoundEnvironment;
    1.48 +import javax.annotation.processing.SupportedAnnotationTypes;
    1.49 +import javax.lang.model.element.AnnotationMirror;
    1.50 +import javax.lang.model.element.AnnotationValue;
    1.51 +import javax.lang.model.element.Element;
    1.52 +import javax.lang.model.element.ElementKind;
    1.53 +import javax.lang.model.element.ExecutableElement;
    1.54 +import javax.lang.model.element.Modifier;
    1.55 +import javax.lang.model.element.PackageElement;
    1.56 +import javax.lang.model.element.TypeElement;
    1.57 +import javax.lang.model.element.VariableElement;
    1.58 +import javax.lang.model.type.ArrayType;
    1.59 +import javax.lang.model.type.MirroredTypeException;
    1.60 +import javax.lang.model.type.TypeKind;
    1.61 +import javax.lang.model.type.TypeMirror;
    1.62 +import javax.lang.model.util.Elements;
    1.63 +import javax.lang.model.util.Types;
    1.64 +import javax.tools.Diagnostic;
    1.65 +import javax.tools.FileObject;
    1.66 +import javax.tools.StandardLocation;
    1.67 +import net.java.html.json.ComputedProperty;
    1.68 +import net.java.html.json.Model;
    1.69 +import net.java.html.json.Function;
    1.70 +import net.java.html.json.OnPropertyChange;
    1.71 +import net.java.html.json.OnReceive;
    1.72 +import net.java.html.json.Property;
    1.73 +import org.openide.util.lookup.ServiceProvider;
    1.74 +
    1.75 +/** Annotation processor to process an XHTML page and generate appropriate 
    1.76 + * "id" file.
    1.77 + *
    1.78 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    1.79 + */
    1.80 +@ServiceProvider(service=Processor.class)
    1.81 +@SupportedAnnotationTypes({
    1.82 +    "net.java.html.json.Model",
    1.83 +    "net.java.html.json.Function",
    1.84 +    "net.java.html.json.OnReceive",
    1.85 +    "net.java.html.json.OnPropertyChange",
    1.86 +    "net.java.html.json.ComputedProperty",
    1.87 +    "net.java.html.json.Property"
    1.88 +})
    1.89 +public final class ModelProcessor extends AbstractProcessor {
    1.90 +    private final Map<Element,String> models = new WeakHashMap<>();
    1.91 +    private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
    1.92 +    @Override
    1.93 +    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    1.94 +        boolean ok = true;
    1.95 +        for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
    1.96 +            if (!processModel(e)) {
    1.97 +                ok = false;
    1.98 +            }
    1.99 +        }
   1.100 +        if (roundEnv.processingOver()) {
   1.101 +            models.clear();
   1.102 +            for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
   1.103 +                TypeElement te = (TypeElement)entry.getKey();
   1.104 +                String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
   1.105 +                Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
   1.106 +                if (finalElem == null) {
   1.107 +                    continue;
   1.108 +                }
   1.109 +                Prprt[] props;
   1.110 +                Model m = finalElem.getAnnotation(Model.class);
   1.111 +                if (m == null) {
   1.112 +                    continue;
   1.113 +                }
   1.114 +                props = Prprt.wrap(processingEnv, finalElem, m.properties());
   1.115 +                for (Prprt p : props) {
   1.116 +                    boolean[] isModel = { false };
   1.117 +                    boolean[] isEnum = { false };
   1.118 +                    boolean[] isPrimitive = { false };
   1.119 +                    String t = checkType(p, isModel, isEnum, isPrimitive);
   1.120 +                    if (isEnum[0]) {
   1.121 +                        continue;
   1.122 +                    }
   1.123 +                    if (isPrimitive[0]) {
   1.124 +                        continue;
   1.125 +                    }
   1.126 +                    if (isModel[0]) {
   1.127 +                        continue;
   1.128 +                    }
   1.129 +                    if ("java.lang.String".equals(t)) {
   1.130 +                        continue;
   1.131 +                    }
   1.132 +                    error("The type " + t + " should be defined by @Model annotation", entry.getKey());
   1.133 +                }
   1.134 +            }
   1.135 +            verify.clear();
   1.136 +        }
   1.137 +        return ok;
   1.138 +    }
   1.139 +
   1.140 +    private InputStream openStream(String pkg, String name) throws IOException {
   1.141 +        try {
   1.142 +            FileObject fo = processingEnv.getFiler().getResource(
   1.143 +                StandardLocation.SOURCE_PATH, pkg, name);
   1.144 +            return fo.openInputStream();
   1.145 +        } catch (IOException ex) {
   1.146 +            return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
   1.147 +        }
   1.148 +    }
   1.149 +
   1.150 +    private void error(String msg, Element e) {
   1.151 +        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
   1.152 +    }
   1.153 +    
   1.154 +    private boolean processModel(Element e) {
   1.155 +        boolean ok = true;
   1.156 +        Model m = e.getAnnotation(Model.class);
   1.157 +        if (m == null) {
   1.158 +            return true;
   1.159 +        }
   1.160 +        String pkg = findPkgName(e);
   1.161 +        Writer w;
   1.162 +        String className = m.className();
   1.163 +        models.put(e, className);
   1.164 +        try {
   1.165 +            StringWriter body = new StringWriter();
   1.166 +            List<String> propsGetSet = new ArrayList<>();
   1.167 +            List<String> functions = new ArrayList<>();
   1.168 +            Map<String, Collection<String>> propsDeps = new HashMap<>();
   1.169 +            Map<String, Collection<String>> functionDeps = new HashMap<>();
   1.170 +            Prprt[] props = createProps(e, m.properties());
   1.171 +            
   1.172 +            if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
   1.173 +                ok = false;
   1.174 +            }
   1.175 +            if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
   1.176 +                ok = false;
   1.177 +            }
   1.178 +            if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
   1.179 +                ok = false;
   1.180 +            }
   1.181 +            if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   1.182 +                ok = false;
   1.183 +            }
   1.184 +            FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   1.185 +            w = new OutputStreamWriter(java.openOutputStream());
   1.186 +            try {
   1.187 +                w.append("package " + pkg + ";\n");
   1.188 +                w.append("import net.java.html.json.*;\n");
   1.189 +                w.append("public final class ").append(className).append(" implements Cloneable {\n");
   1.190 +                w.append("  private boolean locked;\n");
   1.191 +                w.append("  private org.apidesign.html.json.impl.Bindings ko;\n");
   1.192 +                w.append(body.toString());
   1.193 +                w.append("  private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
   1.194 +                w.append("  public ").append(className).append("() {\n");
   1.195 +                w.append("  };\n");
   1.196 +                w.append("  private org.apidesign.html.json.impl.Bindings intKnckt() {\n");
   1.197 +                w.append("    if (ko != null) return ko;\n");
   1.198 +                w.append("    return ko = org.apidesign.html.json.impl.Bindings.apply(this, ");
   1.199 +                writeStringArray(propsGetSet, w);
   1.200 +                w.append(", ");
   1.201 +                writeStringArray(functions, w);
   1.202 +                w.append("    );\n");
   1.203 +                w.append("  };\n");
   1.204 +                w.append("  ").append(className).append("(Object json) {\n");
   1.205 +                int values = 0;
   1.206 +                for (int i = 0; i < propsGetSet.size(); i += 4) {
   1.207 +                    Prprt p = findPrprt(props, propsGetSet.get(i));
   1.208 +                    if (p == null) {
   1.209 +                        continue;
   1.210 +                    }
   1.211 +                    values++;
   1.212 +                }
   1.213 +                w.append("    Object[] ret = new Object[" + values + "];\n");
   1.214 +                w.append("    org.apidesign.html.json.impl.JSON.extract(json, new String[] {\n");
   1.215 +                for (int i = 0; i < propsGetSet.size(); i += 4) {
   1.216 +                    Prprt p = findPrprt(props, propsGetSet.get(i));
   1.217 +                    if (p == null) {
   1.218 +                        continue;
   1.219 +                    }
   1.220 +                    w.append("      \"").append(propsGetSet.get(i)).append("\",\n");
   1.221 +                }
   1.222 +                w.append("    }, ret);\n");
   1.223 +                for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
   1.224 +                    final String pn = propsGetSet.get(i);
   1.225 +                    Prprt p = findPrprt(props, pn);
   1.226 +                    if (p == null) {
   1.227 +                        continue;
   1.228 +                    }
   1.229 +                    boolean[] isModel = { false };
   1.230 +                    boolean[] isEnum = { false };
   1.231 +                    boolean isPrimitive[] = { false };
   1.232 +                    String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
   1.233 +                    if (p.array()) {
   1.234 +                        w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
   1.235 +                        w.append("  for (Object e : ((Object[])ret[" + cnt + "])) {\n");
   1.236 +                        if (isModel[0]) {
   1.237 +                            w.append("    this.prop_").append(pn).append(".add(new ");
   1.238 +                            w.append(type).append("(e));\n");
   1.239 +                        } else if (isEnum[0]) {
   1.240 +                            w.append("    this.prop_").append(pn);
   1.241 +                            w.append(".add(e == null ? null : ");
   1.242 +                            w.append(type).append(".valueOf((String)e));\n");
   1.243 +                        } else {
   1.244 +                            if (isPrimitive(type)) {
   1.245 +                                w.append("    this.prop_").append(pn).append(".add(((Number)e).");
   1.246 +                                w.append(type).append("Value());\n");
   1.247 +                            } else {
   1.248 +                                w.append("    this.prop_").append(pn).append(".add((");
   1.249 +                                w.append(type).append(")e);\n");
   1.250 +                            }
   1.251 +                        }
   1.252 +                        w.append("  }\n");
   1.253 +                        w.append("}\n");
   1.254 +                    } else {
   1.255 +                        if (isEnum[0]) {
   1.256 +                            w.append("    this.prop_").append(pn);
   1.257 +                            w.append(" = ret[" + cnt + "] == null ? null : ");
   1.258 +                            w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
   1.259 +                        } else if (isPrimitive(type)) {
   1.260 +                            w.append("    this.prop_").append(pn);
   1.261 +                            w.append(" = ((Number)").append("ret[" + cnt + "]).");
   1.262 +                            w.append(type).append("Value();\n");
   1.263 +                        } else {
   1.264 +                            w.append("    this.prop_").append(pn);
   1.265 +                            w.append(" = (").append(type).append(')');
   1.266 +                            w.append("ret[" + cnt + "];\n");
   1.267 +                        }
   1.268 +                    }
   1.269 +                    cnt++;
   1.270 +                }
   1.271 +                w.append("  };\n");
   1.272 +                writeToString(props, w);
   1.273 +                writeClone(className, props, w);
   1.274 +                w.append("  public Object koData() {\n");
   1.275 +                w.append("    return intKnckt().koData();\n");
   1.276 +                w.append("  }\n");
   1.277 +                w.append("}\n");
   1.278 +            } finally {
   1.279 +                w.close();
   1.280 +            }
   1.281 +        } catch (IOException ex) {
   1.282 +            error("Can't create " + className + ".java", e);
   1.283 +            return false;
   1.284 +        }
   1.285 +        return ok;
   1.286 +    }
   1.287 +    
   1.288 +    private boolean generateProperties(
   1.289 +        Element where,
   1.290 +        Writer w, Prprt[] properties,
   1.291 +        Collection<String> props, 
   1.292 +        Map<String,Collection<String>> deps,
   1.293 +        Map<String,Collection<String>> functionDeps
   1.294 +    ) throws IOException {
   1.295 +        boolean ok = true;
   1.296 +        for (Prprt p : properties) {
   1.297 +            final String tn;
   1.298 +            tn = typeName(where, p);
   1.299 +            String[] gs = toGetSet(p.name(), tn, p.array());
   1.300 +
   1.301 +            if (p.array()) {
   1.302 +                w.write("private org.apidesign.html.json.impl.JSONList<" + tn + "> prop_" + p.name() + " = new org.apidesign.html.json.impl.JSONList<" + tn + ">(\""
   1.303 +                    + p.name() + "\"");
   1.304 +                Collection<String> dependants = deps.get(p.name());
   1.305 +                if (dependants != null) {
   1.306 +                    for (String depProp : dependants) {
   1.307 +                        w.write(", ");
   1.308 +                        w.write('\"');
   1.309 +                        w.write(depProp);
   1.310 +                        w.write('\"');
   1.311 +                    }
   1.312 +                }
   1.313 +                w.write(")");
   1.314 +                
   1.315 +                dependants = functionDeps.get(p.name());
   1.316 +                if (dependants != null) {
   1.317 +                    w.write(".onChange(new Runnable() { public void run() {\n");
   1.318 +                    for (String call : dependants) {
   1.319 +                        w.append(call);
   1.320 +                    }
   1.321 +                    w.write("}})");
   1.322 +                }
   1.323 +                w.write(";\n");
   1.324 +                
   1.325 +                w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   1.326 +                w.write("  if (locked) throw new IllegalStateException();\n");
   1.327 +                w.write("  prop_" + p.name() + ".assign(ko);\n");
   1.328 +                w.write("  return prop_" + p.name() + ";\n");
   1.329 +                w.write("}\n");
   1.330 +            } else {
   1.331 +                w.write("private " + tn + " prop_" + p.name() + ";\n");
   1.332 +                w.write("public " + tn + " " + gs[0] + "() {\n");
   1.333 +                w.write("  if (locked) throw new IllegalStateException();\n");
   1.334 +                w.write("  return prop_" + p.name() + ";\n");
   1.335 +                w.write("}\n");
   1.336 +                w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   1.337 +                w.write("  if (locked) throw new IllegalStateException();\n");
   1.338 +                w.write("  prop_" + p.name() + " = v;\n");
   1.339 +                w.write("  if (ko != null) {\n");
   1.340 +                w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   1.341 +                Collection<String> dependants = deps.get(p.name());
   1.342 +                if (dependants != null) {
   1.343 +                    for (String depProp : dependants) {
   1.344 +                        w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   1.345 +                    }
   1.346 +                }
   1.347 +                w.write("  }\n");
   1.348 +                dependants = functionDeps.get(p.name());
   1.349 +                if (dependants != null) {
   1.350 +                    for (String call : dependants) {
   1.351 +                        w.append(call);
   1.352 +                    }
   1.353 +                }
   1.354 +                w.write("}\n");
   1.355 +            }
   1.356 +            
   1.357 +            props.add(p.name());
   1.358 +            props.add(gs[2]);
   1.359 +            props.add(gs[3]);
   1.360 +            props.add(gs[0]);
   1.361 +        }
   1.362 +        return ok;
   1.363 +    }
   1.364 +
   1.365 +    private boolean generateComputedProperties(
   1.366 +        Writer w, Prprt[] fixedProps,
   1.367 +        Collection<? extends Element> arr, Collection<String> props,
   1.368 +        Map<String,Collection<String>> deps
   1.369 +    ) throws IOException {
   1.370 +        boolean ok = true;
   1.371 +        for (Element e : arr) {
   1.372 +            if (e.getKind() != ElementKind.METHOD) {
   1.373 +                continue;
   1.374 +            }
   1.375 +            if (e.getAnnotation(ComputedProperty.class) == null) {
   1.376 +                continue;
   1.377 +            }
   1.378 +            ExecutableElement ee = (ExecutableElement)e;
   1.379 +            final TypeMirror rt = ee.getReturnType();
   1.380 +            final Types tu = processingEnv.getTypeUtils();
   1.381 +            TypeMirror ert = tu.erasure(rt);
   1.382 +            String tn = fqn(ert, ee);
   1.383 +            boolean array = false;
   1.384 +            if (tn.equals("java.util.List")) {
   1.385 +                array = true;
   1.386 +            }
   1.387 +            
   1.388 +            final String sn = ee.getSimpleName().toString();
   1.389 +            String[] gs = toGetSet(sn, tn, array);
   1.390 +            
   1.391 +            w.write("public " + tn + " " + gs[0] + "() {\n");
   1.392 +            w.write("  if (locked) throw new IllegalStateException();\n");
   1.393 +            int arg = 0;
   1.394 +            for (VariableElement pe : ee.getParameters()) {
   1.395 +                final String dn = pe.getSimpleName().toString();
   1.396 +                
   1.397 +                if (!verifyPropName(pe, dn, fixedProps)) {
   1.398 +                    ok = false;
   1.399 +                }
   1.400 +                
   1.401 +                final String dt = fqn(pe.asType(), ee);
   1.402 +                String[] call = toGetSet(dn, dt, false);
   1.403 +                w.write("  " + dt + " arg" + (++arg) + " = ");
   1.404 +                w.write(call[0] + "();\n");
   1.405 +                
   1.406 +                Collection<String> depends = deps.get(dn);
   1.407 +                if (depends == null) {
   1.408 +                    depends = new LinkedHashSet<>();
   1.409 +                    deps.put(dn, depends);
   1.410 +                }
   1.411 +                depends.add(sn);
   1.412 +            }
   1.413 +            w.write("  try {\n");
   1.414 +            w.write("    locked = true;\n");
   1.415 +            w.write("    return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
   1.416 +            String sep = "";
   1.417 +            for (int i = 1; i <= arg; i++) {
   1.418 +                w.write(sep);
   1.419 +                w.write("arg" + i);
   1.420 +                sep = ", ";
   1.421 +            }
   1.422 +            w.write(");\n");
   1.423 +            w.write("  } finally {\n");
   1.424 +            w.write("    locked = false;\n");
   1.425 +            w.write("  }\n");
   1.426 +            w.write("}\n");
   1.427 +
   1.428 +            props.add(e.getSimpleName().toString());
   1.429 +            props.add(gs[2]);
   1.430 +            props.add(null);
   1.431 +            props.add(gs[0]);
   1.432 +        }
   1.433 +        
   1.434 +        return ok;
   1.435 +    }
   1.436 +
   1.437 +    private static String[] toGetSet(String name, String type, boolean array) {
   1.438 +        String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
   1.439 +        String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
   1.440 +        if ("int".equals(type)) {
   1.441 +            bck2brwsrType = "I";
   1.442 +        }
   1.443 +        if ("double".equals(type)) {
   1.444 +            bck2brwsrType = "D";
   1.445 +        }
   1.446 +        String pref = "get";
   1.447 +        if ("boolean".equals(type)) {
   1.448 +            pref = "is";
   1.449 +            bck2brwsrType = "Z";
   1.450 +        }
   1.451 +        final String nu = n.replace('.', '_');
   1.452 +        if (array) {
   1.453 +            return new String[] { 
   1.454 +                "get" + n,
   1.455 +                null,
   1.456 +                "get" + nu + "__Ljava_util_List_2",
   1.457 +                null
   1.458 +            };
   1.459 +        }
   1.460 +        return new String[]{
   1.461 +            pref + n, 
   1.462 +            "set" + n, 
   1.463 +            pref + nu + "__" + bck2brwsrType,
   1.464 +            "set" + nu + "__V" + bck2brwsrType
   1.465 +        };
   1.466 +    }
   1.467 +
   1.468 +    private String typeName(Element where, Prprt p) {
   1.469 +        String ret;
   1.470 +        boolean[] isModel = { false };
   1.471 +        boolean[] isEnum = { false };
   1.472 +        boolean isPrimitive[] = { false };
   1.473 +        ret = checkType(p, isModel, isEnum, isPrimitive);
   1.474 +        if (p.array()) {
   1.475 +            String bt = findBoxedType(ret);
   1.476 +            if (bt != null) {
   1.477 +                return bt;
   1.478 +            }
   1.479 +        }
   1.480 +        return ret;
   1.481 +    }
   1.482 +    
   1.483 +    private static String findBoxedType(String ret) {
   1.484 +        if (ret.equals("boolean")) {
   1.485 +            return Boolean.class.getName();
   1.486 +        }
   1.487 +        if (ret.equals("byte")) {
   1.488 +            return Byte.class.getName();
   1.489 +        }
   1.490 +        if (ret.equals("short")) {
   1.491 +            return Short.class.getName();
   1.492 +        }
   1.493 +        if (ret.equals("char")) {
   1.494 +            return Character.class.getName();
   1.495 +        }
   1.496 +        if (ret.equals("int")) {
   1.497 +            return Integer.class.getName();
   1.498 +        }
   1.499 +        if (ret.equals("long")) {
   1.500 +            return Long.class.getName();
   1.501 +        }
   1.502 +        if (ret.equals("float")) {
   1.503 +            return Float.class.getName();
   1.504 +        }
   1.505 +        if (ret.equals("double")) {
   1.506 +            return Double.class.getName();
   1.507 +        }
   1.508 +        return null;
   1.509 +    }
   1.510 +
   1.511 +    private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
   1.512 +        StringBuilder sb = new StringBuilder();
   1.513 +        String sep = "";
   1.514 +        for (Prprt Prprt : existingProps) {
   1.515 +            if (Prprt.name().equals(propName)) {
   1.516 +                return true;
   1.517 +            }
   1.518 +            sb.append(sep);
   1.519 +            sb.append('"');
   1.520 +            sb.append(Prprt.name());
   1.521 +            sb.append('"');
   1.522 +            sep = ", ";
   1.523 +        }
   1.524 +        error(
   1.525 +            propName + " is not one of known properties: " + sb
   1.526 +            , e
   1.527 +        );
   1.528 +        return false;
   1.529 +    }
   1.530 +
   1.531 +    private static String findPkgName(Element e) {
   1.532 +        for (;;) {
   1.533 +            if (e.getKind() == ElementKind.PACKAGE) {
   1.534 +                return ((PackageElement)e).getQualifiedName().toString();
   1.535 +            }
   1.536 +            e = e.getEnclosingElement();
   1.537 +        }
   1.538 +    }
   1.539 +
   1.540 +    private boolean generateFunctions(
   1.541 +        Element clazz, StringWriter body, String className, 
   1.542 +        List<? extends Element> enclosedElements, List<String> functions
   1.543 +    ) {
   1.544 +        for (Element m : enclosedElements) {
   1.545 +            if (m.getKind() != ElementKind.METHOD) {
   1.546 +                continue;
   1.547 +            }
   1.548 +            ExecutableElement e = (ExecutableElement)m;
   1.549 +            Function onF = e.getAnnotation(Function.class);
   1.550 +            if (onF == null) {
   1.551 +                continue;
   1.552 +            }
   1.553 +            if (!e.getModifiers().contains(Modifier.STATIC)) {
   1.554 +                error("@OnFunction method needs to be static", e);
   1.555 +                return false;
   1.556 +            }
   1.557 +            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   1.558 +                error("@OnFunction method cannot be private", e);
   1.559 +                return false;
   1.560 +            }
   1.561 +            if (e.getReturnType().getKind() != TypeKind.VOID) {
   1.562 +                error("@OnFunction method should return void", e);
   1.563 +                return false;
   1.564 +            }
   1.565 +            String n = e.getSimpleName().toString();
   1.566 +            body.append("private void ").append(n).append("(Object data, Object ev) {\n");
   1.567 +            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   1.568 +            body.append(wrapParams(e, null, className, "ev", "data"));
   1.569 +            body.append(");\n");
   1.570 +            body.append("}\n");
   1.571 +            
   1.572 +            functions.add(n);
   1.573 +            functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
   1.574 +        }
   1.575 +        return true;
   1.576 +    }
   1.577 +
   1.578 +    private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
   1.579 +        Prprt[] properties, String className, 
   1.580 +        Map<String, Collection<String>> functionDeps
   1.581 +    ) {
   1.582 +        for (Element m : clazz.getEnclosedElements()) {
   1.583 +            if (m.getKind() != ElementKind.METHOD) {
   1.584 +                continue;
   1.585 +            }
   1.586 +            ExecutableElement e = (ExecutableElement) m;
   1.587 +            OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
   1.588 +            if (onPC == null) {
   1.589 +                continue;
   1.590 +            }
   1.591 +            for (String pn : onPC.value()) {
   1.592 +                if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   1.593 +                    error("No Prprt named '" + pn + "' in the model", clazz);
   1.594 +                    return false;
   1.595 +                }
   1.596 +            }
   1.597 +            if (!e.getModifiers().contains(Modifier.STATIC)) {
   1.598 +                error("@OnPrprtChange method needs to be static", e);
   1.599 +                return false;
   1.600 +            }
   1.601 +            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   1.602 +                error("@OnPrprtChange method cannot be private", e);
   1.603 +                return false;
   1.604 +            }
   1.605 +            if (e.getReturnType().getKind() != TypeKind.VOID) {
   1.606 +                error("@OnPrprtChange method should return void", e);
   1.607 +                return false;
   1.608 +            }
   1.609 +            String n = e.getSimpleName().toString();
   1.610 +            
   1.611 +            
   1.612 +            for (String pn : onPC.value()) {
   1.613 +                StringBuilder call = new StringBuilder();
   1.614 +                call.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   1.615 +                call.append(wrapPropName(e, className, "name", pn));
   1.616 +                call.append(");\n");
   1.617 +                
   1.618 +                Collection<String> change = functionDeps.get(pn);
   1.619 +                if (change == null) {
   1.620 +                    change = new ArrayList<>();
   1.621 +                    functionDeps.put(pn, change);
   1.622 +                }
   1.623 +                change.add(call.toString());
   1.624 +                for (String dpn : findDerivedFrom(propDeps, pn)) {
   1.625 +                    change = functionDeps.get(dpn);
   1.626 +                    if (change == null) {
   1.627 +                        change = new ArrayList<>();
   1.628 +                        functionDeps.put(dpn, change);
   1.629 +                    }
   1.630 +                    change.add(call.toString());
   1.631 +                }
   1.632 +            }
   1.633 +        }
   1.634 +        return true;
   1.635 +    }
   1.636 +    
   1.637 +    private boolean generateReceive(
   1.638 +        Element clazz, StringWriter body, String className, 
   1.639 +        List<? extends Element> enclosedElements, List<String> functions
   1.640 +    ) {
   1.641 +        for (Element m : enclosedElements) {
   1.642 +            if (m.getKind() != ElementKind.METHOD) {
   1.643 +                continue;
   1.644 +            }
   1.645 +            ExecutableElement e = (ExecutableElement)m;
   1.646 +            OnReceive onR = e.getAnnotation(OnReceive.class);
   1.647 +            if (onR == null) {
   1.648 +                continue;
   1.649 +            }
   1.650 +            if (!e.getModifiers().contains(Modifier.STATIC)) {
   1.651 +                error("@OnReceive method needs to be static", e);
   1.652 +                return false;
   1.653 +            }
   1.654 +            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   1.655 +                error("@OnReceive method cannot be private", e);
   1.656 +                return false;
   1.657 +            }
   1.658 +            if (e.getReturnType().getKind() != TypeKind.VOID) {
   1.659 +                error("@OnReceive method should return void", e);
   1.660 +                return false;
   1.661 +            }
   1.662 +            String modelClass = null;
   1.663 +            boolean expectsList = false;
   1.664 +            List<String> args = new ArrayList<>();
   1.665 +            {
   1.666 +                for (VariableElement ve : e.getParameters()) {
   1.667 +                    TypeMirror modelType = null;
   1.668 +                    if (ve.asType().toString().equals(className)) {
   1.669 +                        args.add(className + ".this");
   1.670 +                    } else if (isModel(ve.asType())) {
   1.671 +                        modelType = ve.asType();
   1.672 +                    } else if (ve.asType().getKind() == TypeKind.ARRAY) {
   1.673 +                        modelType = ((ArrayType)ve.asType()).getComponentType();
   1.674 +                        expectsList = true;
   1.675 +                    }
   1.676 +                    if (modelType != null) {
   1.677 +                        if (modelClass != null) {
   1.678 +                            error("There can be only one model class among arguments", e);
   1.679 +                        } else {
   1.680 +                            modelClass = modelType.toString();
   1.681 +                            if (expectsList) {
   1.682 +                                args.add("arr");
   1.683 +                            } else {
   1.684 +                                args.add("arr[0]");
   1.685 +                            }
   1.686 +                        }
   1.687 +                    }
   1.688 +                }
   1.689 +            }
   1.690 +            if (modelClass == null) {
   1.691 +                error("The method needs to have one @Model class as parameter", e);
   1.692 +            }
   1.693 +            String n = e.getSimpleName().toString();
   1.694 +            body.append("public void ").append(n).append("(");
   1.695 +            StringBuilder assembleURL = new StringBuilder();
   1.696 +            String jsonpVarName = null;
   1.697 +            {
   1.698 +                String sep = "";
   1.699 +                boolean skipJSONP = onR.jsonp().isEmpty();
   1.700 +                for (String p : findParamNames(e, onR.url(), assembleURL)) {
   1.701 +                    if (!skipJSONP && p.equals(onR.jsonp())) {
   1.702 +                        skipJSONP = true;
   1.703 +                        jsonpVarName = p;
   1.704 +                        continue;
   1.705 +                    }
   1.706 +                    body.append(sep);
   1.707 +                    body.append("String ").append(p);
   1.708 +                    sep = ", ";
   1.709 +                }
   1.710 +                if (!skipJSONP) {
   1.711 +                    error(
   1.712 +                        "Name of jsonp attribute ('" + onR.jsonp() + 
   1.713 +                        "') is not used in url attribute '" + onR.url() + "'", e
   1.714 +                    );
   1.715 +                }
   1.716 +            }
   1.717 +            body.append(") {\n");
   1.718 +            body.append("  final Object[] result = { null };\n");
   1.719 +            body.append(
   1.720 +                "  class ProcessResult implements Runnable {\n" +
   1.721 +                "    @Override\n" +
   1.722 +                "    public void run() {\n" +
   1.723 +                "      Object value = result[0];\n");
   1.724 +            body.append(
   1.725 +                "      " + modelClass + "[] arr;\n");
   1.726 +            body.append(
   1.727 +                "      if (value instanceof Object[]) {\n" +
   1.728 +                "        Object[] data = ((Object[])value);\n" +
   1.729 +                "        arr = new " + modelClass + "[data.length];\n" +
   1.730 +                "        for (int i = 0; i < data.length; i++) {\n" +
   1.731 +                "          arr[i] = new " + modelClass + "(data[i]);\n" +
   1.732 +                "        }\n" +
   1.733 +                "      } else {\n" +
   1.734 +                "        arr = new " + modelClass + "[1];\n" +
   1.735 +                "        arr[0] = new " + modelClass + "(value);\n" +
   1.736 +                "      }\n"
   1.737 +            );
   1.738 +            {
   1.739 +                body.append(clazz.getSimpleName()).append(".").append(n).append("(");
   1.740 +                String sep = "";
   1.741 +                for (String arg : args) {
   1.742 +                    body.append(sep);
   1.743 +                    body.append(arg);
   1.744 +                    sep = ", ";
   1.745 +                }
   1.746 +                body.append(");\n");
   1.747 +            }
   1.748 +            body.append(
   1.749 +                "    }\n" +
   1.750 +                "  }\n"
   1.751 +            );
   1.752 +            body.append("  ProcessResult pr = new ProcessResult();\n");
   1.753 +            if (jsonpVarName != null) {
   1.754 +                body.append("  String ").append(jsonpVarName).
   1.755 +                    append(" = org.apidesign.html.json.impl.JSON.createJSONP(result, pr);\n");
   1.756 +            }
   1.757 +            body.append("  org.apidesign.html.json.impl.JSON.loadJSON(\n      ");
   1.758 +            body.append(assembleURL);
   1.759 +            body.append(", result, pr, ").append(jsonpVarName).append("\n  );\n");
   1.760 +//            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   1.761 +//            body.append(wrapParams(e, null, className, "ev", "data"));
   1.762 +//            body.append(");\n");
   1.763 +            body.append("}\n");
   1.764 +        }
   1.765 +        return true;
   1.766 +    }
   1.767 +
   1.768 +    private CharSequence wrapParams(
   1.769 +        ExecutableElement ee, String id, String className, String evName, String dataName
   1.770 +    ) {
   1.771 +        TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   1.772 +        StringBuilder params = new StringBuilder();
   1.773 +        boolean first = true;
   1.774 +        for (VariableElement ve : ee.getParameters()) {
   1.775 +            if (!first) {
   1.776 +                params.append(", ");
   1.777 +            }
   1.778 +            first = false;
   1.779 +            String toCall = null;
   1.780 +            if (ve.asType() == stringType) {
   1.781 +                if (ve.getSimpleName().contentEquals("id")) {
   1.782 +                    params.append('"').append(id).append('"');
   1.783 +                    continue;
   1.784 +                }
   1.785 +                toCall = "org.apidesign.html.json.impl.JSON.toString(";
   1.786 +            }
   1.787 +            if (ve.asType().getKind() == TypeKind.DOUBLE) {
   1.788 +                toCall = "org.apidesign.html.json.impl.JSON.toDouble(";
   1.789 +            }
   1.790 +            if (ve.asType().getKind() == TypeKind.INT) {
   1.791 +                toCall = "org.apidesign.html.json.impl.JSON.toInt(";
   1.792 +            }
   1.793 +            if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
   1.794 +                toCall = "org.apidesign.html.json.impl.JSON.toModel(" + ve.asType() + ".class, ";
   1.795 +            }
   1.796 +
   1.797 +            if (toCall != null) {
   1.798 +                params.append(toCall);
   1.799 +                if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
   1.800 +                    params.append(dataName);
   1.801 +                    params.append(", null");
   1.802 +                } else {
   1.803 +                    if (evName == null) {
   1.804 +                        final StringBuilder sb = new StringBuilder();
   1.805 +                        sb.append("Unexpected string parameter name.");
   1.806 +                        if (dataName != null) {
   1.807 +                            sb.append(" Try \"").append(dataName).append("\"");
   1.808 +                        }
   1.809 +                        error(sb.toString(), ee);
   1.810 +                    }
   1.811 +                    params.append(evName);
   1.812 +                    params.append(", \"");
   1.813 +                    params.append(ve.getSimpleName().toString());
   1.814 +                    params.append("\"");
   1.815 +                }
   1.816 +                params.append(")");
   1.817 +                continue;
   1.818 +            }
   1.819 +            String rn = fqn(ve.asType(), ee);
   1.820 +            int last = rn.lastIndexOf('.');
   1.821 +            if (last >= 0) {
   1.822 +                rn = rn.substring(last + 1);
   1.823 +            }
   1.824 +            if (rn.equals(className)) {
   1.825 +                params.append(className).append(".this");
   1.826 +                continue;
   1.827 +            }
   1.828 +            error(
   1.829 +                "@On method can only accept String named 'id' or " + className + " arguments",
   1.830 +                ee
   1.831 +            );
   1.832 +        }
   1.833 +        return params;
   1.834 +    }
   1.835 +    
   1.836 +    
   1.837 +    private CharSequence wrapPropName(
   1.838 +        ExecutableElement ee, String className, String propName, String propValue
   1.839 +    ) {
   1.840 +        TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   1.841 +        StringBuilder params = new StringBuilder();
   1.842 +        boolean first = true;
   1.843 +        for (VariableElement ve : ee.getParameters()) {
   1.844 +            if (!first) {
   1.845 +                params.append(", ");
   1.846 +            }
   1.847 +            first = false;
   1.848 +            if (ve.asType() == stringType) {
   1.849 +                if (propName != null && ve.getSimpleName().contentEquals(propName)) {
   1.850 +                    params.append('"').append(propValue).append('"');
   1.851 +                } else {
   1.852 +                    error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
   1.853 +                }
   1.854 +                continue;
   1.855 +            }
   1.856 +            String rn = fqn(ve.asType(), ee);
   1.857 +            int last = rn.lastIndexOf('.');
   1.858 +            if (last >= 0) {
   1.859 +                rn = rn.substring(last + 1);
   1.860 +            }
   1.861 +            if (rn.equals(className)) {
   1.862 +                params.append(className).append(".this");
   1.863 +                continue;
   1.864 +            }
   1.865 +            error(
   1.866 +                "@OnPrprtChange method can only accept String or " + className + " arguments",
   1.867 +                ee);
   1.868 +        }
   1.869 +        return params;
   1.870 +    }
   1.871 +    
   1.872 +    private boolean isModel(TypeMirror tm) {
   1.873 +        final Element e = processingEnv.getTypeUtils().asElement(tm);
   1.874 +        if (e == null) {
   1.875 +            return false;
   1.876 +        }
   1.877 +        for (Element ch : e.getEnclosedElements()) {
   1.878 +            if (ch.getKind() == ElementKind.METHOD) {
   1.879 +                ExecutableElement ee = (ExecutableElement)ch;
   1.880 +                if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
   1.881 +                    return true;
   1.882 +                }
   1.883 +            }
   1.884 +        }
   1.885 +        return models.values().contains(e.getSimpleName().toString());
   1.886 +    }
   1.887 +
   1.888 +    private void writeStringArray(List<String> strings, Writer w) throws IOException {
   1.889 +        w.write("new String[] {\n");
   1.890 +        String sep = "";
   1.891 +        for (String n : strings) {
   1.892 +            w.write(sep);
   1.893 +            if (n == null) {
   1.894 +                w.write("    null");
   1.895 +            } else {
   1.896 +                w.write("    \"" + n + "\"");
   1.897 +            }
   1.898 +            sep = ",\n";
   1.899 +        }
   1.900 +        w.write("\n  }");
   1.901 +    }
   1.902 +    
   1.903 +    private void writeToString(Prprt[] props, Writer w) throws IOException {
   1.904 +        w.write("  public String toString() {\n");
   1.905 +        w.write("    StringBuilder sb = new StringBuilder();\n");
   1.906 +        w.write("    sb.append('{');\n");
   1.907 +        String sep = "";
   1.908 +        for (Prprt p : props) {
   1.909 +            w.write(sep);
   1.910 +            w.append("    sb.append(\"" + p.name() + ": \");\n");
   1.911 +            w.append("    sb.append(org.apidesign.html.json.impl.JSON.toJSON(prop_");
   1.912 +            w.append(p.name()).append("));\n");
   1.913 +            sep =    "    sb.append(',');\n";
   1.914 +        }
   1.915 +        w.write("    sb.append('}');\n");
   1.916 +        w.write("    return sb.toString();\n");
   1.917 +        w.write("  }\n");
   1.918 +    }
   1.919 +    private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
   1.920 +        w.write("  public " + className + " clone() {\n");
   1.921 +        w.write("    " + className + " ret = new " + className + "();\n");
   1.922 +        for (Prprt p : props) {
   1.923 +            if (!p.array()) {
   1.924 +                boolean isModel[] = { false };
   1.925 +                boolean isEnum[] = { false };
   1.926 +                boolean isPrimitive[] = { false };
   1.927 +                checkType(p, isModel, isEnum, isPrimitive);
   1.928 +                if (!isModel[0]) {
   1.929 +                    w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
   1.930 +                    continue;
   1.931 +                }
   1.932 +                w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
   1.933 +            } else {
   1.934 +                w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
   1.935 +            }
   1.936 +        }
   1.937 +        
   1.938 +        w.write("    return ret;\n");
   1.939 +        w.write("  }\n");
   1.940 +    }
   1.941 +
   1.942 +    private String inPckName(Element e) {
   1.943 +        StringBuilder sb = new StringBuilder();
   1.944 +        while (e.getKind() != ElementKind.PACKAGE) {
   1.945 +            if (sb.length() == 0) {
   1.946 +                sb.append(e.getSimpleName());
   1.947 +            } else {
   1.948 +                sb.insert(0, '.');
   1.949 +                sb.insert(0, e.getSimpleName());
   1.950 +            }
   1.951 +            e = e.getEnclosingElement();
   1.952 +        }
   1.953 +        return sb.toString();
   1.954 +    }
   1.955 +
   1.956 +    private String fqn(TypeMirror pt, Element relative) {
   1.957 +        if (pt.getKind() == TypeKind.ERROR) {
   1.958 +            final Elements eu = processingEnv.getElementUtils();
   1.959 +            PackageElement pckg = eu.getPackageOf(relative);
   1.960 +            return pckg.getQualifiedName() + "." + pt.toString();
   1.961 +        }
   1.962 +        return pt.toString();
   1.963 +    }
   1.964 +
   1.965 +    private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
   1.966 +        TypeMirror tm;
   1.967 +        try {
   1.968 +            String ret = p.typeName(processingEnv);
   1.969 +            TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
   1.970 +            if (e == null) {
   1.971 +                isModel[0] = true;
   1.972 +                isEnum[0] = false;
   1.973 +                isPrimitive[0] = false;
   1.974 +                return ret;
   1.975 +            }
   1.976 +            tm = e.asType();
   1.977 +        } catch (MirroredTypeException ex) {
   1.978 +            tm = ex.getTypeMirror();
   1.979 +        }
   1.980 +        tm = processingEnv.getTypeUtils().erasure(tm);
   1.981 +        isPrimitive[0] = tm.getKind().isPrimitive();
   1.982 +        final Element e = processingEnv.getTypeUtils().asElement(tm);
   1.983 +        final Model m = e == null ? null : e.getAnnotation(Model.class);
   1.984 +        
   1.985 +        String ret;
   1.986 +        if (m != null) {
   1.987 +            ret = findPkgName(e) + '.' + m.className();
   1.988 +            isModel[0] = true;
   1.989 +            models.put(e, m.className());
   1.990 +        } else if (findModelForMthd(e)) {
   1.991 +            ret = ((TypeElement)e).getQualifiedName().toString();
   1.992 +            isModel[0] = true;
   1.993 +        } else {
   1.994 +            ret = tm.toString();
   1.995 +        }
   1.996 +        TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
   1.997 +        enm = processingEnv.getTypeUtils().erasure(enm);
   1.998 +        isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
   1.999 +        return ret;
  1.1000 +    }
  1.1001 +    
  1.1002 +    private static boolean findModelForMthd(Element clazz) {
  1.1003 +        if (clazz == null) {
  1.1004 +            return false;
  1.1005 +        }
  1.1006 +        for (Element e : clazz.getEnclosedElements()) {
  1.1007 +            if (e.getKind() == ElementKind.METHOD) {
  1.1008 +                ExecutableElement ee = (ExecutableElement)e;
  1.1009 +                if (
  1.1010 +                    ee.getSimpleName().contentEquals("modelFor") &&
  1.1011 +                    ee.getParameters().isEmpty()
  1.1012 +                ) {
  1.1013 +                    return true;
  1.1014 +                }
  1.1015 +            }
  1.1016 +        }
  1.1017 +        return false;
  1.1018 +    }
  1.1019 +
  1.1020 +    private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
  1.1021 +        List<String> params = new ArrayList<>();
  1.1022 +
  1.1023 +        for (int pos = 0; ;) {
  1.1024 +            int next = url.indexOf('{', pos);
  1.1025 +            if (next == -1) {
  1.1026 +                assembleURL.append('"')
  1.1027 +                    .append(url.substring(pos))
  1.1028 +                    .append('"');
  1.1029 +                return params;
  1.1030 +            }
  1.1031 +            int close = url.indexOf('}', next);
  1.1032 +            if (close == -1) {
  1.1033 +                error("Unbalanced '{' and '}' in " + url, e);
  1.1034 +                return params;
  1.1035 +            }
  1.1036 +            final String paramName = url.substring(next + 1, close);
  1.1037 +            params.add(paramName);
  1.1038 +            assembleURL.append('"')
  1.1039 +                .append(url.substring(pos, next))
  1.1040 +                .append("\" + ").append(paramName).append(" + ");
  1.1041 +            pos = close + 1;
  1.1042 +        }
  1.1043 +    }
  1.1044 +
  1.1045 +    private static Prprt findPrprt(Prprt[] properties, String propName) {
  1.1046 +        for (Prprt p : properties) {
  1.1047 +            if (propName.equals(p.name())) {
  1.1048 +                return p;
  1.1049 +            }
  1.1050 +        }
  1.1051 +        return null;
  1.1052 +    }
  1.1053 +
  1.1054 +    private boolean isPrimitive(String type) {
  1.1055 +        return 
  1.1056 +            "int".equals(type) ||
  1.1057 +            "double".equals(type) ||
  1.1058 +            "long".equals(type) ||
  1.1059 +            "short".equals(type) ||
  1.1060 +            "byte".equals(type) ||
  1.1061 +            "float".equals(type);
  1.1062 +    }
  1.1063 +
  1.1064 +    private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
  1.1065 +        Set<String> names = new HashSet<>();
  1.1066 +        for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
  1.1067 +            if (e.getValue().contains(derivedProp)) {
  1.1068 +                names.add(e.getKey());
  1.1069 +            }
  1.1070 +        }
  1.1071 +        return names;
  1.1072 +    }
  1.1073 +    
  1.1074 +    private Prprt[] createProps(Element e, Property[] arr) {
  1.1075 +        Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
  1.1076 +        Prprt[] prev = verify.put(e, ret);
  1.1077 +        if (prev != null) {
  1.1078 +            error("Two sets of properties for ", e);
  1.1079 +        }
  1.1080 +        return ret;
  1.1081 +    }
  1.1082 +    
  1.1083 +    private static class Prprt {
  1.1084 +        private final Element e;
  1.1085 +        private final AnnotationMirror tm;
  1.1086 +        private final Property p;
  1.1087 +
  1.1088 +        public Prprt(Element e, AnnotationMirror tm, Property p) {
  1.1089 +            this.e = e;
  1.1090 +            this.tm = tm;
  1.1091 +            this.p = p;
  1.1092 +        }
  1.1093 +        
  1.1094 +        String name() {
  1.1095 +            return p.name();
  1.1096 +        }
  1.1097 +        
  1.1098 +        boolean array() {
  1.1099 +            return p.array();
  1.1100 +        }
  1.1101 +
  1.1102 +        String typeName(ProcessingEnvironment env) {
  1.1103 +            try {
  1.1104 +                return p.type().getName();
  1.1105 +            } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) {
  1.1106 +                for (Object v : getAnnoValues(env)) {
  1.1107 +                    String s = v.toString().replace(" ", "");
  1.1108 +                    if (s.startsWith("type=") && s.endsWith(".class")) {
  1.1109 +                        return s.substring(5, s.length() - 6);
  1.1110 +                    }
  1.1111 +                }
  1.1112 +                throw ex;
  1.1113 +            }
  1.1114 +        }
  1.1115 +        
  1.1116 +        
  1.1117 +        static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
  1.1118 +            if (arr.length == 0) {
  1.1119 +                return new Prprt[0];
  1.1120 +            }
  1.1121 +            
  1.1122 +            if (e.getKind() != ElementKind.CLASS) {
  1.1123 +                throw new IllegalStateException("" + e.getKind());
  1.1124 +            }
  1.1125 +            TypeElement te = (TypeElement)e;
  1.1126 +            List<? extends AnnotationValue> val = null;
  1.1127 +            for (AnnotationMirror an : te.getAnnotationMirrors()) {
  1.1128 +                for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
  1.1129 +                    if (entry.getKey().getSimpleName().contentEquals("properties")) {
  1.1130 +                        val = (List)entry.getValue().getValue();
  1.1131 +                        break;
  1.1132 +                    }
  1.1133 +                }
  1.1134 +            }
  1.1135 +            if (val == null || val.size() != arr.length) {
  1.1136 +                pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
  1.1137 +                return new Prprt[0];
  1.1138 +            }
  1.1139 +            Prprt[] ret = new Prprt[arr.length];
  1.1140 +            BIG: for (int i = 0; i < ret.length; i++) {
  1.1141 +                AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
  1.1142 +                ret[i] = new Prprt(e, am, arr[i]);
  1.1143 +                
  1.1144 +            }
  1.1145 +            return ret;
  1.1146 +        }
  1.1147 +        
  1.1148 +        private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
  1.1149 +            try {
  1.1150 +                Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
  1.1151 +                Method m = trees.getMethod("instance", ProcessingEnvironment.class);
  1.1152 +                Object instance = m.invoke(null, pe);
  1.1153 +                m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
  1.1154 +                Object path = m.invoke(instance, e, tm);
  1.1155 +                m = path.getClass().getMethod("getLeaf");
  1.1156 +                Object leaf = m.invoke(path);
  1.1157 +                m = leaf.getClass().getMethod("getArguments");
  1.1158 +                return (List)m.invoke(leaf);
  1.1159 +            } catch (Exception ex) {
  1.1160 +                return Collections.emptyList();
  1.1161 +            }
  1.1162 +        }
  1.1163 +    }
  1.1164 +    
  1.1165 +}
     2.1 --- a/json/src/main/java/org/apidesign/html/json/impl/PageProcessor.java	Fri Apr 19 10:25:57 2013 +0200
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,1159 +0,0 @@
     2.4 -/**
     2.5 - * HTML via Java(tm) Language Bindings
     2.6 - * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     2.7 - *
     2.8 - * This program is free software: you can redistribute it and/or modify
     2.9 - * it under the terms of the GNU General Public License as published by
    2.10 - * the Free Software Foundation, version 2 of the License.
    2.11 - *
    2.12 - * This program is distributed in the hope that it will be useful,
    2.13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    2.14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    2.15 - * GNU General Public License for more details. apidesign.org
    2.16 - * designates this particular file as subject to the
    2.17 - * "Classpath" exception as provided by apidesign.org
    2.18 - * in the License file that accompanied this code.
    2.19 - *
    2.20 - * You should have received a copy of the GNU General Public License
    2.21 - * along with this program. Look for COPYING file in the top folder.
    2.22 - * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    2.23 - */
    2.24 -package org.apidesign.html.json.impl;
    2.25 -
    2.26 -import java.io.IOException;
    2.27 -import java.io.InputStream;
    2.28 -import java.io.OutputStreamWriter;
    2.29 -import java.io.StringWriter;
    2.30 -import java.io.Writer;
    2.31 -import java.lang.annotation.AnnotationTypeMismatchException;
    2.32 -import java.lang.annotation.IncompleteAnnotationException;
    2.33 -import java.lang.reflect.Method;
    2.34 -import java.util.ArrayList;
    2.35 -import java.util.Collection;
    2.36 -import java.util.Collections;
    2.37 -import java.util.HashMap;
    2.38 -import java.util.HashSet;
    2.39 -import java.util.LinkedHashSet;
    2.40 -import java.util.List;
    2.41 -import java.util.Map;
    2.42 -import java.util.Set;
    2.43 -import java.util.WeakHashMap;
    2.44 -import javax.annotation.processing.AbstractProcessor;
    2.45 -import javax.annotation.processing.ProcessingEnvironment;
    2.46 -import javax.annotation.processing.Processor;
    2.47 -import javax.annotation.processing.RoundEnvironment;
    2.48 -import javax.annotation.processing.SupportedAnnotationTypes;
    2.49 -import javax.lang.model.element.AnnotationMirror;
    2.50 -import javax.lang.model.element.AnnotationValue;
    2.51 -import javax.lang.model.element.Element;
    2.52 -import javax.lang.model.element.ElementKind;
    2.53 -import javax.lang.model.element.ExecutableElement;
    2.54 -import javax.lang.model.element.Modifier;
    2.55 -import javax.lang.model.element.PackageElement;
    2.56 -import javax.lang.model.element.TypeElement;
    2.57 -import javax.lang.model.element.VariableElement;
    2.58 -import javax.lang.model.type.ArrayType;
    2.59 -import javax.lang.model.type.MirroredTypeException;
    2.60 -import javax.lang.model.type.TypeKind;
    2.61 -import javax.lang.model.type.TypeMirror;
    2.62 -import javax.lang.model.util.Elements;
    2.63 -import javax.lang.model.util.Types;
    2.64 -import javax.tools.Diagnostic;
    2.65 -import javax.tools.FileObject;
    2.66 -import javax.tools.StandardLocation;
    2.67 -import net.java.html.json.ComputedProperty;
    2.68 -import net.java.html.json.Model;
    2.69 -import net.java.html.json.Function;
    2.70 -import net.java.html.json.OnPropertyChange;
    2.71 -import net.java.html.json.OnReceive;
    2.72 -import net.java.html.json.Property;
    2.73 -import org.openide.util.lookup.ServiceProvider;
    2.74 -
    2.75 -/** Annotation processor to process an XHTML page and generate appropriate 
    2.76 - * "id" file.
    2.77 - *
    2.78 - * @author Jaroslav Tulach <jtulach@netbeans.org>
    2.79 - */
    2.80 -@ServiceProvider(service=Processor.class)
    2.81 -@SupportedAnnotationTypes({
    2.82 -    "net.java.html.json.Model",
    2.83 -    "net.java.html.json.Function",
    2.84 -    "net.java.html.json.OnReceive",
    2.85 -    "net.java.html.json.OnPropertyChange",
    2.86 -    "net.java.html.json.ComputedProperty",
    2.87 -    "net.java.html.json.Property"
    2.88 -})
    2.89 -public final class PageProcessor extends AbstractProcessor {
    2.90 -    private final Map<Element,String> models = new WeakHashMap<>();
    2.91 -    private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
    2.92 -    @Override
    2.93 -    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    2.94 -        boolean ok = true;
    2.95 -        for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
    2.96 -            if (!processModel(e)) {
    2.97 -                ok = false;
    2.98 -            }
    2.99 -        }
   2.100 -        if (roundEnv.processingOver()) {
   2.101 -            models.clear();
   2.102 -            for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
   2.103 -                TypeElement te = (TypeElement)entry.getKey();
   2.104 -                String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
   2.105 -                Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
   2.106 -                if (finalElem == null) {
   2.107 -                    continue;
   2.108 -                }
   2.109 -                Prprt[] props;
   2.110 -                Model m = finalElem.getAnnotation(Model.class);
   2.111 -                props = Prprt.wrap(processingEnv, finalElem, m.properties());
   2.112 -                for (Prprt p : props) {
   2.113 -                    boolean[] isModel = { false };
   2.114 -                    boolean[] isEnum = { false };
   2.115 -                    boolean[] isPrimitive = { false };
   2.116 -                    String t = checkType(p, isModel, isEnum, isPrimitive);
   2.117 -                    if (isEnum[0]) {
   2.118 -                        continue;
   2.119 -                    }
   2.120 -                    if (isPrimitive[0]) {
   2.121 -                        continue;
   2.122 -                    }
   2.123 -                    if (isModel[0]) {
   2.124 -                        continue;
   2.125 -                    }
   2.126 -                    if ("java.lang.String".equals(t)) {
   2.127 -                        continue;
   2.128 -                    }
   2.129 -                    error("The type " + t + " should be defined by @Model annotation", entry.getKey());
   2.130 -                }
   2.131 -            }
   2.132 -            verify.clear();
   2.133 -        }
   2.134 -        return ok;
   2.135 -    }
   2.136 -
   2.137 -    private InputStream openStream(String pkg, String name) throws IOException {
   2.138 -        try {
   2.139 -            FileObject fo = processingEnv.getFiler().getResource(
   2.140 -                StandardLocation.SOURCE_PATH, pkg, name);
   2.141 -            return fo.openInputStream();
   2.142 -        } catch (IOException ex) {
   2.143 -            return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
   2.144 -        }
   2.145 -    }
   2.146 -
   2.147 -    private void error(String msg, Element e) {
   2.148 -        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
   2.149 -    }
   2.150 -    
   2.151 -    private boolean processModel(Element e) {
   2.152 -        boolean ok = true;
   2.153 -        Model m = e.getAnnotation(Model.class);
   2.154 -        if (m == null) {
   2.155 -            return true;
   2.156 -        }
   2.157 -        String pkg = findPkgName(e);
   2.158 -        Writer w;
   2.159 -        String className = m.className();
   2.160 -        models.put(e, className);
   2.161 -        try {
   2.162 -            StringWriter body = new StringWriter();
   2.163 -            List<String> propsGetSet = new ArrayList<>();
   2.164 -            List<String> functions = new ArrayList<>();
   2.165 -            Map<String, Collection<String>> propsDeps = new HashMap<>();
   2.166 -            Map<String, Collection<String>> functionDeps = new HashMap<>();
   2.167 -            Prprt[] props = createProps(e, m.properties());
   2.168 -            
   2.169 -            if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
   2.170 -                ok = false;
   2.171 -            }
   2.172 -            if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
   2.173 -                ok = false;
   2.174 -            }
   2.175 -            if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
   2.176 -                ok = false;
   2.177 -            }
   2.178 -            if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   2.179 -                ok = false;
   2.180 -            }
   2.181 -            FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   2.182 -            w = new OutputStreamWriter(java.openOutputStream());
   2.183 -            try {
   2.184 -                w.append("package " + pkg + ";\n");
   2.185 -                w.append("import net.java.html.json.*;\n");
   2.186 -                w.append("public final class ").append(className).append(" implements Cloneable {\n");
   2.187 -                w.append("  private boolean locked;\n");
   2.188 -                w.append("  private org.apidesign.html.json.impl.Bindings ko;\n");
   2.189 -                w.append(body.toString());
   2.190 -                w.append("  private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
   2.191 -                w.append("  public ").append(className).append("() {\n");
   2.192 -                w.append("  };\n");
   2.193 -                w.append("  private org.apidesign.html.json.impl.Bindings intKnckt() {\n");
   2.194 -                w.append("    if (ko != null) return ko;\n");
   2.195 -                w.append("    return ko = org.apidesign.html.json.impl.Bindings.apply(this, ");
   2.196 -                writeStringArray(propsGetSet, w);
   2.197 -                w.append(", ");
   2.198 -                writeStringArray(functions, w);
   2.199 -                w.append("    );\n");
   2.200 -                w.append("  };\n");
   2.201 -                w.append("  ").append(className).append("(Object json) {\n");
   2.202 -                int values = 0;
   2.203 -                for (int i = 0; i < propsGetSet.size(); i += 4) {
   2.204 -                    Prprt p = findPrprt(props, propsGetSet.get(i));
   2.205 -                    if (p == null) {
   2.206 -                        continue;
   2.207 -                    }
   2.208 -                    values++;
   2.209 -                }
   2.210 -                w.append("    Object[] ret = new Object[" + values + "];\n");
   2.211 -                w.append("    org.apidesign.html.json.impl.JSON.extract(json, new String[] {\n");
   2.212 -                for (int i = 0; i < propsGetSet.size(); i += 4) {
   2.213 -                    Prprt p = findPrprt(props, propsGetSet.get(i));
   2.214 -                    if (p == null) {
   2.215 -                        continue;
   2.216 -                    }
   2.217 -                    w.append("      \"").append(propsGetSet.get(i)).append("\",\n");
   2.218 -                }
   2.219 -                w.append("    }, ret);\n");
   2.220 -                for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
   2.221 -                    final String pn = propsGetSet.get(i);
   2.222 -                    Prprt p = findPrprt(props, pn);
   2.223 -                    if (p == null) {
   2.224 -                        continue;
   2.225 -                    }
   2.226 -                    boolean[] isModel = { false };
   2.227 -                    boolean[] isEnum = { false };
   2.228 -                    boolean isPrimitive[] = { false };
   2.229 -                    String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
   2.230 -                    if (p.array()) {
   2.231 -                        w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
   2.232 -                        w.append("  for (Object e : ((Object[])ret[" + cnt + "])) {\n");
   2.233 -                        if (isModel[0]) {
   2.234 -                            w.append("    this.prop_").append(pn).append(".add(new ");
   2.235 -                            w.append(type).append("(e));\n");
   2.236 -                        } else if (isEnum[0]) {
   2.237 -                            w.append("    this.prop_").append(pn);
   2.238 -                            w.append(".add(e == null ? null : ");
   2.239 -                            w.append(type).append(".valueOf((String)e));\n");
   2.240 -                        } else {
   2.241 -                            if (isPrimitive(type)) {
   2.242 -                                w.append("    this.prop_").append(pn).append(".add(((Number)e).");
   2.243 -                                w.append(type).append("Value());\n");
   2.244 -                            } else {
   2.245 -                                w.append("    this.prop_").append(pn).append(".add((");
   2.246 -                                w.append(type).append(")e);\n");
   2.247 -                            }
   2.248 -                        }
   2.249 -                        w.append("  }\n");
   2.250 -                        w.append("}\n");
   2.251 -                    } else {
   2.252 -                        if (isEnum[0]) {
   2.253 -                            w.append("    this.prop_").append(pn);
   2.254 -                            w.append(" = ret[" + cnt + "] == null ? null : ");
   2.255 -                            w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
   2.256 -                        } else if (isPrimitive(type)) {
   2.257 -                            w.append("    this.prop_").append(pn);
   2.258 -                            w.append(" = ((Number)").append("ret[" + cnt + "]).");
   2.259 -                            w.append(type).append("Value();\n");
   2.260 -                        } else {
   2.261 -                            w.append("    this.prop_").append(pn);
   2.262 -                            w.append(" = (").append(type).append(')');
   2.263 -                            w.append("ret[" + cnt + "];\n");
   2.264 -                        }
   2.265 -                    }
   2.266 -                    cnt++;
   2.267 -                }
   2.268 -                w.append("  };\n");
   2.269 -                writeToString(props, w);
   2.270 -                writeClone(className, props, w);
   2.271 -                w.append("  public Object koData() {\n");
   2.272 -                w.append("    return intKnckt().koData();\n");
   2.273 -                w.append("  }\n");
   2.274 -                w.append("}\n");
   2.275 -            } finally {
   2.276 -                w.close();
   2.277 -            }
   2.278 -        } catch (IOException ex) {
   2.279 -            error("Can't create " + className + ".java", e);
   2.280 -            return false;
   2.281 -        }
   2.282 -        return ok;
   2.283 -    }
   2.284 -    
   2.285 -    private boolean generateProperties(
   2.286 -        Element where,
   2.287 -        Writer w, Prprt[] properties,
   2.288 -        Collection<String> props, 
   2.289 -        Map<String,Collection<String>> deps,
   2.290 -        Map<String,Collection<String>> functionDeps
   2.291 -    ) throws IOException {
   2.292 -        boolean ok = true;
   2.293 -        for (Prprt p : properties) {
   2.294 -            final String tn;
   2.295 -            tn = typeName(where, p);
   2.296 -            String[] gs = toGetSet(p.name(), tn, p.array());
   2.297 -
   2.298 -            if (p.array()) {
   2.299 -                w.write("private org.apidesign.html.json.impl.JSONList<" + tn + "> prop_" + p.name() + " = new org.apidesign.html.json.impl.JSONList<" + tn + ">(\""
   2.300 -                    + p.name() + "\"");
   2.301 -                Collection<String> dependants = deps.get(p.name());
   2.302 -                if (dependants != null) {
   2.303 -                    for (String depProp : dependants) {
   2.304 -                        w.write(", ");
   2.305 -                        w.write('\"');
   2.306 -                        w.write(depProp);
   2.307 -                        w.write('\"');
   2.308 -                    }
   2.309 -                }
   2.310 -                w.write(")");
   2.311 -                
   2.312 -                dependants = functionDeps.get(p.name());
   2.313 -                if (dependants != null) {
   2.314 -                    w.write(".onChange(new Runnable() { public void run() {\n");
   2.315 -                    for (String call : dependants) {
   2.316 -                        w.append(call);
   2.317 -                    }
   2.318 -                    w.write("}})");
   2.319 -                }
   2.320 -                w.write(";\n");
   2.321 -                
   2.322 -                w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   2.323 -                w.write("  if (locked) throw new IllegalStateException();\n");
   2.324 -                w.write("  prop_" + p.name() + ".assign(ko);\n");
   2.325 -                w.write("  return prop_" + p.name() + ";\n");
   2.326 -                w.write("}\n");
   2.327 -            } else {
   2.328 -                w.write("private " + tn + " prop_" + p.name() + ";\n");
   2.329 -                w.write("public " + tn + " " + gs[0] + "() {\n");
   2.330 -                w.write("  if (locked) throw new IllegalStateException();\n");
   2.331 -                w.write("  return prop_" + p.name() + ";\n");
   2.332 -                w.write("}\n");
   2.333 -                w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   2.334 -                w.write("  if (locked) throw new IllegalStateException();\n");
   2.335 -                w.write("  prop_" + p.name() + " = v;\n");
   2.336 -                w.write("  if (ko != null) {\n");
   2.337 -                w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   2.338 -                Collection<String> dependants = deps.get(p.name());
   2.339 -                if (dependants != null) {
   2.340 -                    for (String depProp : dependants) {
   2.341 -                        w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   2.342 -                    }
   2.343 -                }
   2.344 -                w.write("  }\n");
   2.345 -                dependants = functionDeps.get(p.name());
   2.346 -                if (dependants != null) {
   2.347 -                    for (String call : dependants) {
   2.348 -                        w.append(call);
   2.349 -                    }
   2.350 -                }
   2.351 -                w.write("}\n");
   2.352 -            }
   2.353 -            
   2.354 -            props.add(p.name());
   2.355 -            props.add(gs[2]);
   2.356 -            props.add(gs[3]);
   2.357 -            props.add(gs[0]);
   2.358 -        }
   2.359 -        return ok;
   2.360 -    }
   2.361 -
   2.362 -    private boolean generateComputedProperties(
   2.363 -        Writer w, Prprt[] fixedProps,
   2.364 -        Collection<? extends Element> arr, Collection<String> props,
   2.365 -        Map<String,Collection<String>> deps
   2.366 -    ) throws IOException {
   2.367 -        boolean ok = true;
   2.368 -        for (Element e : arr) {
   2.369 -            if (e.getKind() != ElementKind.METHOD) {
   2.370 -                continue;
   2.371 -            }
   2.372 -            if (e.getAnnotation(ComputedProperty.class) == null) {
   2.373 -                continue;
   2.374 -            }
   2.375 -            ExecutableElement ee = (ExecutableElement)e;
   2.376 -            final TypeMirror rt = ee.getReturnType();
   2.377 -            final Types tu = processingEnv.getTypeUtils();
   2.378 -            TypeMirror ert = tu.erasure(rt);
   2.379 -            String tn = fqn(ert, ee);
   2.380 -            boolean array = false;
   2.381 -            if (tn.equals("java.util.List")) {
   2.382 -                array = true;
   2.383 -            }
   2.384 -            
   2.385 -            final String sn = ee.getSimpleName().toString();
   2.386 -            String[] gs = toGetSet(sn, tn, array);
   2.387 -            
   2.388 -            w.write("public " + tn + " " + gs[0] + "() {\n");
   2.389 -            w.write("  if (locked) throw new IllegalStateException();\n");
   2.390 -            int arg = 0;
   2.391 -            for (VariableElement pe : ee.getParameters()) {
   2.392 -                final String dn = pe.getSimpleName().toString();
   2.393 -                
   2.394 -                if (!verifyPropName(pe, dn, fixedProps)) {
   2.395 -                    ok = false;
   2.396 -                }
   2.397 -                
   2.398 -                final String dt = fqn(pe.asType(), ee);
   2.399 -                String[] call = toGetSet(dn, dt, false);
   2.400 -                w.write("  " + dt + " arg" + (++arg) + " = ");
   2.401 -                w.write(call[0] + "();\n");
   2.402 -                
   2.403 -                Collection<String> depends = deps.get(dn);
   2.404 -                if (depends == null) {
   2.405 -                    depends = new LinkedHashSet<>();
   2.406 -                    deps.put(dn, depends);
   2.407 -                }
   2.408 -                depends.add(sn);
   2.409 -            }
   2.410 -            w.write("  try {\n");
   2.411 -            w.write("    locked = true;\n");
   2.412 -            w.write("    return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
   2.413 -            String sep = "";
   2.414 -            for (int i = 1; i <= arg; i++) {
   2.415 -                w.write(sep);
   2.416 -                w.write("arg" + i);
   2.417 -                sep = ", ";
   2.418 -            }
   2.419 -            w.write(");\n");
   2.420 -            w.write("  } finally {\n");
   2.421 -            w.write("    locked = false;\n");
   2.422 -            w.write("  }\n");
   2.423 -            w.write("}\n");
   2.424 -
   2.425 -            props.add(e.getSimpleName().toString());
   2.426 -            props.add(gs[2]);
   2.427 -            props.add(null);
   2.428 -            props.add(gs[0]);
   2.429 -        }
   2.430 -        
   2.431 -        return ok;
   2.432 -    }
   2.433 -
   2.434 -    private static String[] toGetSet(String name, String type, boolean array) {
   2.435 -        String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
   2.436 -        String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
   2.437 -        if ("int".equals(type)) {
   2.438 -            bck2brwsrType = "I";
   2.439 -        }
   2.440 -        if ("double".equals(type)) {
   2.441 -            bck2brwsrType = "D";
   2.442 -        }
   2.443 -        String pref = "get";
   2.444 -        if ("boolean".equals(type)) {
   2.445 -            pref = "is";
   2.446 -            bck2brwsrType = "Z";
   2.447 -        }
   2.448 -        final String nu = n.replace('.', '_');
   2.449 -        if (array) {
   2.450 -            return new String[] { 
   2.451 -                "get" + n,
   2.452 -                null,
   2.453 -                "get" + nu + "__Ljava_util_List_2",
   2.454 -                null
   2.455 -            };
   2.456 -        }
   2.457 -        return new String[]{
   2.458 -            pref + n, 
   2.459 -            "set" + n, 
   2.460 -            pref + nu + "__" + bck2brwsrType,
   2.461 -            "set" + nu + "__V" + bck2brwsrType
   2.462 -        };
   2.463 -    }
   2.464 -
   2.465 -    private String typeName(Element where, Prprt p) {
   2.466 -        String ret;
   2.467 -        boolean[] isModel = { false };
   2.468 -        boolean[] isEnum = { false };
   2.469 -        boolean isPrimitive[] = { false };
   2.470 -        ret = checkType(p, isModel, isEnum, isPrimitive);
   2.471 -        if (p.array()) {
   2.472 -            String bt = findBoxedType(ret);
   2.473 -            if (bt != null) {
   2.474 -                return bt;
   2.475 -            }
   2.476 -        }
   2.477 -        return ret;
   2.478 -    }
   2.479 -    
   2.480 -    private static String findBoxedType(String ret) {
   2.481 -        if (ret.equals("boolean")) {
   2.482 -            return Boolean.class.getName();
   2.483 -        }
   2.484 -        if (ret.equals("byte")) {
   2.485 -            return Byte.class.getName();
   2.486 -        }
   2.487 -        if (ret.equals("short")) {
   2.488 -            return Short.class.getName();
   2.489 -        }
   2.490 -        if (ret.equals("char")) {
   2.491 -            return Character.class.getName();
   2.492 -        }
   2.493 -        if (ret.equals("int")) {
   2.494 -            return Integer.class.getName();
   2.495 -        }
   2.496 -        if (ret.equals("long")) {
   2.497 -            return Long.class.getName();
   2.498 -        }
   2.499 -        if (ret.equals("float")) {
   2.500 -            return Float.class.getName();
   2.501 -        }
   2.502 -        if (ret.equals("double")) {
   2.503 -            return Double.class.getName();
   2.504 -        }
   2.505 -        return null;
   2.506 -    }
   2.507 -
   2.508 -    private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
   2.509 -        StringBuilder sb = new StringBuilder();
   2.510 -        String sep = "";
   2.511 -        for (Prprt Prprt : existingProps) {
   2.512 -            if (Prprt.name().equals(propName)) {
   2.513 -                return true;
   2.514 -            }
   2.515 -            sb.append(sep);
   2.516 -            sb.append('"');
   2.517 -            sb.append(Prprt.name());
   2.518 -            sb.append('"');
   2.519 -            sep = ", ";
   2.520 -        }
   2.521 -        error(
   2.522 -            propName + " is not one of known properties: " + sb
   2.523 -            , e
   2.524 -        );
   2.525 -        return false;
   2.526 -    }
   2.527 -
   2.528 -    private static String findPkgName(Element e) {
   2.529 -        for (;;) {
   2.530 -            if (e.getKind() == ElementKind.PACKAGE) {
   2.531 -                return ((PackageElement)e).getQualifiedName().toString();
   2.532 -            }
   2.533 -            e = e.getEnclosingElement();
   2.534 -        }
   2.535 -    }
   2.536 -
   2.537 -    private boolean generateFunctions(
   2.538 -        Element clazz, StringWriter body, String className, 
   2.539 -        List<? extends Element> enclosedElements, List<String> functions
   2.540 -    ) {
   2.541 -        for (Element m : enclosedElements) {
   2.542 -            if (m.getKind() != ElementKind.METHOD) {
   2.543 -                continue;
   2.544 -            }
   2.545 -            ExecutableElement e = (ExecutableElement)m;
   2.546 -            Function onF = e.getAnnotation(Function.class);
   2.547 -            if (onF == null) {
   2.548 -                continue;
   2.549 -            }
   2.550 -            if (!e.getModifiers().contains(Modifier.STATIC)) {
   2.551 -                error("@OnFunction method needs to be static", e);
   2.552 -                return false;
   2.553 -            }
   2.554 -            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   2.555 -                error("@OnFunction method cannot be private", e);
   2.556 -                return false;
   2.557 -            }
   2.558 -            if (e.getReturnType().getKind() != TypeKind.VOID) {
   2.559 -                error("@OnFunction method should return void", e);
   2.560 -                return false;
   2.561 -            }
   2.562 -            String n = e.getSimpleName().toString();
   2.563 -            body.append("private void ").append(n).append("(Object data, Object ev) {\n");
   2.564 -            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   2.565 -            body.append(wrapParams(e, null, className, "ev", "data"));
   2.566 -            body.append(");\n");
   2.567 -            body.append("}\n");
   2.568 -            
   2.569 -            functions.add(n);
   2.570 -            functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
   2.571 -        }
   2.572 -        return true;
   2.573 -    }
   2.574 -
   2.575 -    private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
   2.576 -        Prprt[] properties, String className, 
   2.577 -        Map<String, Collection<String>> functionDeps
   2.578 -    ) {
   2.579 -        for (Element m : clazz.getEnclosedElements()) {
   2.580 -            if (m.getKind() != ElementKind.METHOD) {
   2.581 -                continue;
   2.582 -            }
   2.583 -            ExecutableElement e = (ExecutableElement) m;
   2.584 -            OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
   2.585 -            if (onPC == null) {
   2.586 -                continue;
   2.587 -            }
   2.588 -            for (String pn : onPC.value()) {
   2.589 -                if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   2.590 -                    error("No Prprt named '" + pn + "' in the model", clazz);
   2.591 -                    return false;
   2.592 -                }
   2.593 -            }
   2.594 -            if (!e.getModifiers().contains(Modifier.STATIC)) {
   2.595 -                error("@OnPrprtChange method needs to be static", e);
   2.596 -                return false;
   2.597 -            }
   2.598 -            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   2.599 -                error("@OnPrprtChange method cannot be private", e);
   2.600 -                return false;
   2.601 -            }
   2.602 -            if (e.getReturnType().getKind() != TypeKind.VOID) {
   2.603 -                error("@OnPrprtChange method should return void", e);
   2.604 -                return false;
   2.605 -            }
   2.606 -            String n = e.getSimpleName().toString();
   2.607 -            
   2.608 -            
   2.609 -            for (String pn : onPC.value()) {
   2.610 -                StringBuilder call = new StringBuilder();
   2.611 -                call.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   2.612 -                call.append(wrapPropName(e, className, "name", pn));
   2.613 -                call.append(");\n");
   2.614 -                
   2.615 -                Collection<String> change = functionDeps.get(pn);
   2.616 -                if (change == null) {
   2.617 -                    change = new ArrayList<>();
   2.618 -                    functionDeps.put(pn, change);
   2.619 -                }
   2.620 -                change.add(call.toString());
   2.621 -                for (String dpn : findDerivedFrom(propDeps, pn)) {
   2.622 -                    change = functionDeps.get(dpn);
   2.623 -                    if (change == null) {
   2.624 -                        change = new ArrayList<>();
   2.625 -                        functionDeps.put(dpn, change);
   2.626 -                    }
   2.627 -                    change.add(call.toString());
   2.628 -                }
   2.629 -            }
   2.630 -        }
   2.631 -        return true;
   2.632 -    }
   2.633 -    
   2.634 -    private boolean generateReceive(
   2.635 -        Element clazz, StringWriter body, String className, 
   2.636 -        List<? extends Element> enclosedElements, List<String> functions
   2.637 -    ) {
   2.638 -        for (Element m : enclosedElements) {
   2.639 -            if (m.getKind() != ElementKind.METHOD) {
   2.640 -                continue;
   2.641 -            }
   2.642 -            ExecutableElement e = (ExecutableElement)m;
   2.643 -            OnReceive onR = e.getAnnotation(OnReceive.class);
   2.644 -            if (onR == null) {
   2.645 -                continue;
   2.646 -            }
   2.647 -            if (!e.getModifiers().contains(Modifier.STATIC)) {
   2.648 -                error("@OnReceive method needs to be static", e);
   2.649 -                return false;
   2.650 -            }
   2.651 -            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   2.652 -                error("@OnReceive method cannot be private", e);
   2.653 -                return false;
   2.654 -            }
   2.655 -            if (e.getReturnType().getKind() != TypeKind.VOID) {
   2.656 -                error("@OnReceive method should return void", e);
   2.657 -                return false;
   2.658 -            }
   2.659 -            String modelClass = null;
   2.660 -            boolean expectsList = false;
   2.661 -            List<String> args = new ArrayList<>();
   2.662 -            {
   2.663 -                for (VariableElement ve : e.getParameters()) {
   2.664 -                    TypeMirror modelType = null;
   2.665 -                    if (ve.asType().toString().equals(className)) {
   2.666 -                        args.add(className + ".this");
   2.667 -                    } else if (isModel(ve.asType())) {
   2.668 -                        modelType = ve.asType();
   2.669 -                    } else if (ve.asType().getKind() == TypeKind.ARRAY) {
   2.670 -                        modelType = ((ArrayType)ve.asType()).getComponentType();
   2.671 -                        expectsList = true;
   2.672 -                    }
   2.673 -                    if (modelType != null) {
   2.674 -                        if (modelClass != null) {
   2.675 -                            error("There can be only one model class among arguments", e);
   2.676 -                        } else {
   2.677 -                            modelClass = modelType.toString();
   2.678 -                            if (expectsList) {
   2.679 -                                args.add("arr");
   2.680 -                            } else {
   2.681 -                                args.add("arr[0]");
   2.682 -                            }
   2.683 -                        }
   2.684 -                    }
   2.685 -                }
   2.686 -            }
   2.687 -            if (modelClass == null) {
   2.688 -                error("The method needs to have one @Model class as parameter", e);
   2.689 -            }
   2.690 -            String n = e.getSimpleName().toString();
   2.691 -            body.append("public void ").append(n).append("(");
   2.692 -            StringBuilder assembleURL = new StringBuilder();
   2.693 -            String jsonpVarName = null;
   2.694 -            {
   2.695 -                String sep = "";
   2.696 -                boolean skipJSONP = onR.jsonp().isEmpty();
   2.697 -                for (String p : findParamNames(e, onR.url(), assembleURL)) {
   2.698 -                    if (!skipJSONP && p.equals(onR.jsonp())) {
   2.699 -                        skipJSONP = true;
   2.700 -                        jsonpVarName = p;
   2.701 -                        continue;
   2.702 -                    }
   2.703 -                    body.append(sep);
   2.704 -                    body.append("String ").append(p);
   2.705 -                    sep = ", ";
   2.706 -                }
   2.707 -                if (!skipJSONP) {
   2.708 -                    error(
   2.709 -                        "Name of jsonp attribute ('" + onR.jsonp() + 
   2.710 -                        "') is not used in url attribute '" + onR.url() + "'", e
   2.711 -                    );
   2.712 -                }
   2.713 -            }
   2.714 -            body.append(") {\n");
   2.715 -            body.append("  final Object[] result = { null };\n");
   2.716 -            body.append(
   2.717 -                "  class ProcessResult implements Runnable {\n" +
   2.718 -                "    @Override\n" +
   2.719 -                "    public void run() {\n" +
   2.720 -                "      Object value = result[0];\n");
   2.721 -            body.append(
   2.722 -                "      " + modelClass + "[] arr;\n");
   2.723 -            body.append(
   2.724 -                "      if (value instanceof Object[]) {\n" +
   2.725 -                "        Object[] data = ((Object[])value);\n" +
   2.726 -                "        arr = new " + modelClass + "[data.length];\n" +
   2.727 -                "        for (int i = 0; i < data.length; i++) {\n" +
   2.728 -                "          arr[i] = new " + modelClass + "(data[i]);\n" +
   2.729 -                "        }\n" +
   2.730 -                "      } else {\n" +
   2.731 -                "        arr = new " + modelClass + "[1];\n" +
   2.732 -                "        arr[0] = new " + modelClass + "(value);\n" +
   2.733 -                "      }\n"
   2.734 -            );
   2.735 -            {
   2.736 -                body.append(clazz.getSimpleName()).append(".").append(n).append("(");
   2.737 -                String sep = "";
   2.738 -                for (String arg : args) {
   2.739 -                    body.append(sep);
   2.740 -                    body.append(arg);
   2.741 -                    sep = ", ";
   2.742 -                }
   2.743 -                body.append(");\n");
   2.744 -            }
   2.745 -            body.append(
   2.746 -                "    }\n" +
   2.747 -                "  }\n"
   2.748 -            );
   2.749 -            body.append("  ProcessResult pr = new ProcessResult();\n");
   2.750 -            if (jsonpVarName != null) {
   2.751 -                body.append("  String ").append(jsonpVarName).
   2.752 -                    append(" = org.apidesign.html.json.impl.JSON.createJSONP(result, pr);\n");
   2.753 -            }
   2.754 -            body.append("  org.apidesign.html.json.impl.JSON.loadJSON(\n      ");
   2.755 -            body.append(assembleURL);
   2.756 -            body.append(", result, pr, ").append(jsonpVarName).append("\n  );\n");
   2.757 -//            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   2.758 -//            body.append(wrapParams(e, null, className, "ev", "data"));
   2.759 -//            body.append(");\n");
   2.760 -            body.append("}\n");
   2.761 -        }
   2.762 -        return true;
   2.763 -    }
   2.764 -
   2.765 -    private CharSequence wrapParams(
   2.766 -        ExecutableElement ee, String id, String className, String evName, String dataName
   2.767 -    ) {
   2.768 -        TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   2.769 -        StringBuilder params = new StringBuilder();
   2.770 -        boolean first = true;
   2.771 -        for (VariableElement ve : ee.getParameters()) {
   2.772 -            if (!first) {
   2.773 -                params.append(", ");
   2.774 -            }
   2.775 -            first = false;
   2.776 -            String toCall = null;
   2.777 -            if (ve.asType() == stringType) {
   2.778 -                if (ve.getSimpleName().contentEquals("id")) {
   2.779 -                    params.append('"').append(id).append('"');
   2.780 -                    continue;
   2.781 -                }
   2.782 -                toCall = "org.apidesign.html.json.impl.JSON.toString(";
   2.783 -            }
   2.784 -            if (ve.asType().getKind() == TypeKind.DOUBLE) {
   2.785 -                toCall = "org.apidesign.html.json.impl.JSON.toDouble(";
   2.786 -            }
   2.787 -            if (ve.asType().getKind() == TypeKind.INT) {
   2.788 -                toCall = "org.apidesign.html.json.impl.JSON.toInt(";
   2.789 -            }
   2.790 -            if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
   2.791 -                toCall = "org.apidesign.html.json.impl.JSON.toModel(" + ve.asType() + ".class, ";
   2.792 -            }
   2.793 -
   2.794 -            if (toCall != null) {
   2.795 -                params.append(toCall);
   2.796 -                if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
   2.797 -                    params.append(dataName);
   2.798 -                    params.append(", null");
   2.799 -                } else {
   2.800 -                    if (evName == null) {
   2.801 -                        final StringBuilder sb = new StringBuilder();
   2.802 -                        sb.append("Unexpected string parameter name.");
   2.803 -                        if (dataName != null) {
   2.804 -                            sb.append(" Try \"").append(dataName).append("\"");
   2.805 -                        }
   2.806 -                        error(sb.toString(), ee);
   2.807 -                    }
   2.808 -                    params.append(evName);
   2.809 -                    params.append(", \"");
   2.810 -                    params.append(ve.getSimpleName().toString());
   2.811 -                    params.append("\"");
   2.812 -                }
   2.813 -                params.append(")");
   2.814 -                continue;
   2.815 -            }
   2.816 -            String rn = fqn(ve.asType(), ee);
   2.817 -            int last = rn.lastIndexOf('.');
   2.818 -            if (last >= 0) {
   2.819 -                rn = rn.substring(last + 1);
   2.820 -            }
   2.821 -            if (rn.equals(className)) {
   2.822 -                params.append(className).append(".this");
   2.823 -                continue;
   2.824 -            }
   2.825 -            error(
   2.826 -                "@On method can only accept String named 'id' or " + className + " arguments",
   2.827 -                ee
   2.828 -            );
   2.829 -        }
   2.830 -        return params;
   2.831 -    }
   2.832 -    
   2.833 -    
   2.834 -    private CharSequence wrapPropName(
   2.835 -        ExecutableElement ee, String className, String propName, String propValue
   2.836 -    ) {
   2.837 -        TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   2.838 -        StringBuilder params = new StringBuilder();
   2.839 -        boolean first = true;
   2.840 -        for (VariableElement ve : ee.getParameters()) {
   2.841 -            if (!first) {
   2.842 -                params.append(", ");
   2.843 -            }
   2.844 -            first = false;
   2.845 -            if (ve.asType() == stringType) {
   2.846 -                if (propName != null && ve.getSimpleName().contentEquals(propName)) {
   2.847 -                    params.append('"').append(propValue).append('"');
   2.848 -                } else {
   2.849 -                    error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
   2.850 -                }
   2.851 -                continue;
   2.852 -            }
   2.853 -            String rn = fqn(ve.asType(), ee);
   2.854 -            int last = rn.lastIndexOf('.');
   2.855 -            if (last >= 0) {
   2.856 -                rn = rn.substring(last + 1);
   2.857 -            }
   2.858 -            if (rn.equals(className)) {
   2.859 -                params.append(className).append(".this");
   2.860 -                continue;
   2.861 -            }
   2.862 -            error(
   2.863 -                "@OnPrprtChange method can only accept String or " + className + " arguments",
   2.864 -                ee);
   2.865 -        }
   2.866 -        return params;
   2.867 -    }
   2.868 -    
   2.869 -    private boolean isModel(TypeMirror tm) {
   2.870 -        final Element e = processingEnv.getTypeUtils().asElement(tm);
   2.871 -        if (e == null) {
   2.872 -            return false;
   2.873 -        }
   2.874 -        for (Element ch : e.getEnclosedElements()) {
   2.875 -            if (ch.getKind() == ElementKind.METHOD) {
   2.876 -                ExecutableElement ee = (ExecutableElement)ch;
   2.877 -                if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
   2.878 -                    return true;
   2.879 -                }
   2.880 -            }
   2.881 -        }
   2.882 -        return models.values().contains(e.getSimpleName().toString());
   2.883 -    }
   2.884 -
   2.885 -    private void writeStringArray(List<String> strings, Writer w) throws IOException {
   2.886 -        w.write("new String[] {\n");
   2.887 -        String sep = "";
   2.888 -        for (String n : strings) {
   2.889 -            w.write(sep);
   2.890 -            if (n == null) {
   2.891 -                w.write("    null");
   2.892 -            } else {
   2.893 -                w.write("    \"" + n + "\"");
   2.894 -            }
   2.895 -            sep = ",\n";
   2.896 -        }
   2.897 -        w.write("\n  }");
   2.898 -    }
   2.899 -    
   2.900 -    private void writeToString(Prprt[] props, Writer w) throws IOException {
   2.901 -        w.write("  public String toString() {\n");
   2.902 -        w.write("    StringBuilder sb = new StringBuilder();\n");
   2.903 -        w.write("    sb.append('{');\n");
   2.904 -        String sep = "";
   2.905 -        for (Prprt p : props) {
   2.906 -            w.write(sep);
   2.907 -            w.append("    sb.append(\"" + p.name() + ": \");\n");
   2.908 -            w.append("    sb.append(org.apidesign.html.json.impl.JSON.toJSON(prop_");
   2.909 -            w.append(p.name()).append("));\n");
   2.910 -            sep =    "    sb.append(',');\n";
   2.911 -        }
   2.912 -        w.write("    sb.append('}');\n");
   2.913 -        w.write("    return sb.toString();\n");
   2.914 -        w.write("  }\n");
   2.915 -    }
   2.916 -    private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
   2.917 -        w.write("  public " + className + " clone() {\n");
   2.918 -        w.write("    " + className + " ret = new " + className + "();\n");
   2.919 -        for (Prprt p : props) {
   2.920 -            if (!p.array()) {
   2.921 -                boolean isModel[] = { false };
   2.922 -                boolean isEnum[] = { false };
   2.923 -                boolean isPrimitive[] = { false };
   2.924 -                checkType(p, isModel, isEnum, isPrimitive);
   2.925 -                if (!isModel[0]) {
   2.926 -                    w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
   2.927 -                    continue;
   2.928 -                }
   2.929 -                w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
   2.930 -            } else {
   2.931 -                w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
   2.932 -            }
   2.933 -        }
   2.934 -        
   2.935 -        w.write("    return ret;\n");
   2.936 -        w.write("  }\n");
   2.937 -    }
   2.938 -
   2.939 -    private String inPckName(Element e) {
   2.940 -        StringBuilder sb = new StringBuilder();
   2.941 -        while (e.getKind() != ElementKind.PACKAGE) {
   2.942 -            if (sb.length() == 0) {
   2.943 -                sb.append(e.getSimpleName());
   2.944 -            } else {
   2.945 -                sb.insert(0, '.');
   2.946 -                sb.insert(0, e.getSimpleName());
   2.947 -            }
   2.948 -            e = e.getEnclosingElement();
   2.949 -        }
   2.950 -        return sb.toString();
   2.951 -    }
   2.952 -
   2.953 -    private String fqn(TypeMirror pt, Element relative) {
   2.954 -        if (pt.getKind() == TypeKind.ERROR) {
   2.955 -            final Elements eu = processingEnv.getElementUtils();
   2.956 -            PackageElement pckg = eu.getPackageOf(relative);
   2.957 -            return pckg.getQualifiedName() + "." + pt.toString();
   2.958 -        }
   2.959 -        return pt.toString();
   2.960 -    }
   2.961 -
   2.962 -    private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
   2.963 -        TypeMirror tm;
   2.964 -        try {
   2.965 -            String ret = p.typeName(processingEnv);
   2.966 -            TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
   2.967 -            if (e == null) {
   2.968 -                isModel[0] = true;
   2.969 -                isEnum[0] = false;
   2.970 -                isPrimitive[0] = false;
   2.971 -                return ret;
   2.972 -            }
   2.973 -            tm = e.asType();
   2.974 -        } catch (MirroredTypeException ex) {
   2.975 -            tm = ex.getTypeMirror();
   2.976 -        }
   2.977 -        tm = processingEnv.getTypeUtils().erasure(tm);
   2.978 -        isPrimitive[0] = tm.getKind().isPrimitive();
   2.979 -        final Element e = processingEnv.getTypeUtils().asElement(tm);
   2.980 -        final Model m = e == null ? null : e.getAnnotation(Model.class);
   2.981 -        
   2.982 -        String ret;
   2.983 -        if (m != null) {
   2.984 -            ret = findPkgName(e) + '.' + m.className();
   2.985 -            isModel[0] = true;
   2.986 -            models.put(e, m.className());
   2.987 -        } else if (findModelForMthd(e)) {
   2.988 -            ret = ((TypeElement)e).getQualifiedName().toString();
   2.989 -            isModel[0] = true;
   2.990 -        } else {
   2.991 -            ret = tm.toString();
   2.992 -        }
   2.993 -        TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
   2.994 -        enm = processingEnv.getTypeUtils().erasure(enm);
   2.995 -        isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
   2.996 -        return ret;
   2.997 -    }
   2.998 -    
   2.999 -    private static boolean findModelForMthd(Element clazz) {
  2.1000 -        if (clazz == null) {
  2.1001 -            return false;
  2.1002 -        }
  2.1003 -        for (Element e : clazz.getEnclosedElements()) {
  2.1004 -            if (e.getKind() == ElementKind.METHOD) {
  2.1005 -                ExecutableElement ee = (ExecutableElement)e;
  2.1006 -                if (
  2.1007 -                    ee.getSimpleName().contentEquals("modelFor") &&
  2.1008 -                    ee.getParameters().isEmpty()
  2.1009 -                ) {
  2.1010 -                    return true;
  2.1011 -                }
  2.1012 -            }
  2.1013 -        }
  2.1014 -        return false;
  2.1015 -    }
  2.1016 -
  2.1017 -    private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
  2.1018 -        List<String> params = new ArrayList<>();
  2.1019 -
  2.1020 -        for (int pos = 0; ;) {
  2.1021 -            int next = url.indexOf('{', pos);
  2.1022 -            if (next == -1) {
  2.1023 -                assembleURL.append('"')
  2.1024 -                    .append(url.substring(pos))
  2.1025 -                    .append('"');
  2.1026 -                return params;
  2.1027 -            }
  2.1028 -            int close = url.indexOf('}', next);
  2.1029 -            if (close == -1) {
  2.1030 -                error("Unbalanced '{' and '}' in " + url, e);
  2.1031 -                return params;
  2.1032 -            }
  2.1033 -            final String paramName = url.substring(next + 1, close);
  2.1034 -            params.add(paramName);
  2.1035 -            assembleURL.append('"')
  2.1036 -                .append(url.substring(pos, next))
  2.1037 -                .append("\" + ").append(paramName).append(" + ");
  2.1038 -            pos = close + 1;
  2.1039 -        }
  2.1040 -    }
  2.1041 -
  2.1042 -    private static Prprt findPrprt(Prprt[] properties, String propName) {
  2.1043 -        for (Prprt p : properties) {
  2.1044 -            if (propName.equals(p.name())) {
  2.1045 -                return p;
  2.1046 -            }
  2.1047 -        }
  2.1048 -        return null;
  2.1049 -    }
  2.1050 -
  2.1051 -    private boolean isPrimitive(String type) {
  2.1052 -        return 
  2.1053 -            "int".equals(type) ||
  2.1054 -            "double".equals(type) ||
  2.1055 -            "long".equals(type) ||
  2.1056 -            "short".equals(type) ||
  2.1057 -            "byte".equals(type) ||
  2.1058 -            "float".equals(type);
  2.1059 -    }
  2.1060 -
  2.1061 -    private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
  2.1062 -        Set<String> names = new HashSet<>();
  2.1063 -        for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
  2.1064 -            if (e.getValue().contains(derivedProp)) {
  2.1065 -                names.add(e.getKey());
  2.1066 -            }
  2.1067 -        }
  2.1068 -        return names;
  2.1069 -    }
  2.1070 -    
  2.1071 -    private Prprt[] createProps(Element e, Property[] arr) {
  2.1072 -        Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
  2.1073 -        Prprt[] prev = verify.put(e, ret);
  2.1074 -        if (prev != null) {
  2.1075 -            error("Two sets of properties for ", e);
  2.1076 -        }
  2.1077 -        return ret;
  2.1078 -    }
  2.1079 -    
  2.1080 -    private static class Prprt {
  2.1081 -        private final Element e;
  2.1082 -        private final AnnotationMirror tm;
  2.1083 -        private final Property p;
  2.1084 -
  2.1085 -        public Prprt(Element e, AnnotationMirror tm, Property p) {
  2.1086 -            this.e = e;
  2.1087 -            this.tm = tm;
  2.1088 -            this.p = p;
  2.1089 -        }
  2.1090 -        
  2.1091 -        String name() {
  2.1092 -            return p.name();
  2.1093 -        }
  2.1094 -        
  2.1095 -        boolean array() {
  2.1096 -            return p.array();
  2.1097 -        }
  2.1098 -
  2.1099 -        String typeName(ProcessingEnvironment env) {
  2.1100 -            try {
  2.1101 -                return p.type().getName();
  2.1102 -            } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) {
  2.1103 -                for (Object v : getAnnoValues(env)) {
  2.1104 -                    String s = v.toString().replace(" ", "");
  2.1105 -                    if (s.startsWith("type=") && s.endsWith(".class")) {
  2.1106 -                        return s.substring(5, s.length() - 6);
  2.1107 -                    }
  2.1108 -                }
  2.1109 -                throw ex;
  2.1110 -            }
  2.1111 -        }
  2.1112 -        
  2.1113 -        
  2.1114 -        static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
  2.1115 -            if (arr.length == 0) {
  2.1116 -                return new Prprt[0];
  2.1117 -            }
  2.1118 -            
  2.1119 -            if (e.getKind() != ElementKind.CLASS) {
  2.1120 -                throw new IllegalStateException("" + e.getKind());
  2.1121 -            }
  2.1122 -            TypeElement te = (TypeElement)e;
  2.1123 -            List<? extends AnnotationValue> val = null;
  2.1124 -            for (AnnotationMirror an : te.getAnnotationMirrors()) {
  2.1125 -                for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
  2.1126 -                    if (entry.getKey().getSimpleName().contentEquals("properties")) {
  2.1127 -                        val = (List)entry.getValue().getValue();
  2.1128 -                        break;
  2.1129 -                    }
  2.1130 -                }
  2.1131 -            }
  2.1132 -            if (val == null || val.size() != arr.length) {
  2.1133 -                pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
  2.1134 -                return new Prprt[0];
  2.1135 -            }
  2.1136 -            Prprt[] ret = new Prprt[arr.length];
  2.1137 -            BIG: for (int i = 0; i < ret.length; i++) {
  2.1138 -                AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
  2.1139 -                ret[i] = new Prprt(e, am, arr[i]);
  2.1140 -                
  2.1141 -            }
  2.1142 -            return ret;
  2.1143 -        }
  2.1144 -        
  2.1145 -        private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
  2.1146 -            try {
  2.1147 -                Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
  2.1148 -                Method m = trees.getMethod("instance", ProcessingEnvironment.class);
  2.1149 -                Object instance = m.invoke(null, pe);
  2.1150 -                m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
  2.1151 -                Object path = m.invoke(instance, e, tm);
  2.1152 -                m = path.getClass().getMethod("getLeaf");
  2.1153 -                Object leaf = m.invoke(path);
  2.1154 -                m = leaf.getClass().getMethod("getArguments");
  2.1155 -                return (List)m.invoke(leaf);
  2.1156 -            } catch (Exception ex) {
  2.1157 -                return Collections.emptyList();
  2.1158 -            }
  2.1159 -        }
  2.1160 -    }
  2.1161 -    
  2.1162 -}
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/json/src/test/java/net/java/html/json/Compile.java	Fri Apr 19 11:11:07 2013 +0200
     3.3 @@ -0,0 +1,214 @@
     3.4 +/**
     3.5 + * HTML via Java(tm) Language Bindings
     3.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     3.7 + *
     3.8 + * This program is free software: you can redistribute it and/or modify
     3.9 + * it under the terms of the GNU General Public License as published by
    3.10 + * the Free Software Foundation, version 2 of the License.
    3.11 + *
    3.12 + * This program is distributed in the hope that it will be useful,
    3.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.15 + * GNU General Public License for more details. apidesign.org
    3.16 + * designates this particular file as subject to the
    3.17 + * "Classpath" exception as provided by apidesign.org
    3.18 + * in the License file that accompanied this code.
    3.19 + *
    3.20 + * You should have received a copy of the GNU General Public License
    3.21 + * along with this program. Look for COPYING file in the top folder.
    3.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    3.23 + */
    3.24 +package net.java.html.json;
    3.25 +
    3.26 +import java.io.ByteArrayInputStream;
    3.27 +import java.io.ByteArrayOutputStream;
    3.28 +import java.io.IOException;
    3.29 +import java.io.InputStream;
    3.30 +import java.io.OutputStream;
    3.31 +import java.net.URI;
    3.32 +import java.net.URISyntaxException;
    3.33 +import java.util.ArrayList;
    3.34 +import java.util.Arrays;
    3.35 +import java.util.HashMap;
    3.36 +import java.util.List;
    3.37 +import java.util.Map;
    3.38 +import java.util.regex.Matcher;
    3.39 +import java.util.regex.Pattern;
    3.40 +import javax.tools.Diagnostic;
    3.41 +import javax.tools.DiagnosticListener;
    3.42 +import javax.tools.FileObject;
    3.43 +import javax.tools.ForwardingJavaFileManager;
    3.44 +import javax.tools.JavaFileManager;
    3.45 +import javax.tools.JavaFileObject;
    3.46 +import javax.tools.JavaFileObject.Kind;
    3.47 +import javax.tools.SimpleJavaFileObject;
    3.48 +import javax.tools.StandardJavaFileManager;
    3.49 +import javax.tools.StandardLocation;
    3.50 +import javax.tools.ToolProvider;
    3.51 +
    3.52 +/**
    3.53 + *
    3.54 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    3.55 + */
    3.56 +final class Compile implements DiagnosticListener<JavaFileObject> {
    3.57 +    private final List<Diagnostic<? extends JavaFileObject>> errors = new ArrayList<>();
    3.58 +    private final Map<String, byte[]> classes;
    3.59 +    private final String pkg;
    3.60 +    private final String cls;
    3.61 +    private final String html;
    3.62 +
    3.63 +    private Compile(String html, String code) throws IOException {
    3.64 +        this.pkg = findPkg(code);
    3.65 +        this.cls = findCls(code);
    3.66 +        this.html = html;
    3.67 +        classes = compile(html, code);
    3.68 +    }
    3.69 +
    3.70 +    /** Performs compilation of given HTML page and associated Java code
    3.71 +     */
    3.72 +    public static Compile create(String html, String code) throws IOException {
    3.73 +        return new Compile(html, code);
    3.74 +    }
    3.75 +    
    3.76 +    /** Checks for given class among compiled resources */
    3.77 +    public byte[] get(String res) {
    3.78 +        return classes.get(res);
    3.79 +    }
    3.80 +    
    3.81 +    /** Obtains errors created during compilation.
    3.82 +     */
    3.83 +    public List<Diagnostic<? extends JavaFileObject>> getErrors() {
    3.84 +        List<Diagnostic<? extends JavaFileObject>> err = new ArrayList<>();
    3.85 +        for (Diagnostic<? extends JavaFileObject> diagnostic : errors) {
    3.86 +            if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
    3.87 +                err.add(diagnostic);
    3.88 +            }
    3.89 +        }
    3.90 +        return err;
    3.91 +    }
    3.92 +    
    3.93 +    private Map<String, byte[]> compile(final String html, final String code) throws IOException {
    3.94 +        StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null);
    3.95 +
    3.96 +        final Map<String, ByteArrayOutputStream> class2BAOS = new HashMap<>();
    3.97 +
    3.98 +        JavaFileObject file = new SimpleJavaFileObject(URI.create("mem://mem"), Kind.SOURCE) {
    3.99 +            @Override
   3.100 +            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
   3.101 +                return code;
   3.102 +            }
   3.103 +        };
   3.104 +        final JavaFileObject htmlFile = new SimpleJavaFileObject(URI.create("mem://mem2"), Kind.OTHER) {
   3.105 +            @Override
   3.106 +            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
   3.107 +                return html;
   3.108 +            }
   3.109 +
   3.110 +            @Override
   3.111 +            public InputStream openInputStream() throws IOException {
   3.112 +                return new ByteArrayInputStream(html.getBytes());
   3.113 +            }
   3.114 +        };
   3.115 +        
   3.116 +        final URI scratch;
   3.117 +        try {
   3.118 +            scratch = new URI("mem://mem3");
   3.119 +        } catch (URISyntaxException ex) {
   3.120 +            throw new IOException(ex);
   3.121 +        }
   3.122 +        
   3.123 +        JavaFileManager jfm = new ForwardingJavaFileManager<JavaFileManager>(sjfm) {
   3.124 +            @Override
   3.125 +            public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
   3.126 +                if (kind  == Kind.CLASS) {
   3.127 +                    final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
   3.128 +
   3.129 +                    class2BAOS.put(className.replace('.', '/') + ".class", buffer);
   3.130 +                    return new SimpleJavaFileObject(sibling.toUri(), kind) {
   3.131 +                        @Override
   3.132 +                        public OutputStream openOutputStream() throws IOException {
   3.133 +                            return buffer;
   3.134 +                        }
   3.135 +                    };
   3.136 +                }
   3.137 +                
   3.138 +                if (kind == Kind.SOURCE) {
   3.139 +                    final String n = className.replace('.', '/') + ".java";
   3.140 +                    return new SimpleJavaFileObject(scratch/*sibling.toUri()*/, kind) {
   3.141 +                        private final ByteArrayOutputStream data = new ByteArrayOutputStream();
   3.142 +                        @Override
   3.143 +                        public OutputStream openOutputStream() throws IOException {
   3.144 +                            return data;
   3.145 +                        }
   3.146 +
   3.147 +                        @Override
   3.148 +                        public String getName() {
   3.149 +                            return n;
   3.150 +                        }
   3.151 +                        
   3.152 +                        
   3.153 +
   3.154 +                        @Override
   3.155 +                        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
   3.156 +                            data.close();
   3.157 +                            return new String(data.toByteArray());
   3.158 +                        }
   3.159 +                    };
   3.160 +                }
   3.161 +                
   3.162 +                throw new IllegalStateException();
   3.163 +            }
   3.164 +
   3.165 +            @Override
   3.166 +            public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
   3.167 +                if (location == StandardLocation.SOURCE_PATH) {
   3.168 +                    if (packageName.equals(pkg)) {
   3.169 +                        return htmlFile;
   3.170 +                    }
   3.171 +                }
   3.172 +                
   3.173 +                return null;
   3.174 +            }
   3.175 +            
   3.176 +        };
   3.177 +
   3.178 +        ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", "1.7", "-target", "1.7"), null, Arrays.asList(file)).call();
   3.179 +
   3.180 +        Map<String, byte[]> result = new HashMap<>();
   3.181 +
   3.182 +        for (Map.Entry<String, ByteArrayOutputStream> e : class2BAOS.entrySet()) {
   3.183 +            result.put(e.getKey(), e.getValue().toByteArray());
   3.184 +        }
   3.185 +
   3.186 +        return result;
   3.187 +    }
   3.188 +
   3.189 +
   3.190 +    @Override
   3.191 +    public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
   3.192 +        errors.add(diagnostic);
   3.193 +    }
   3.194 +    private static String findPkg(String java) throws IOException {
   3.195 +        Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE);
   3.196 +        Matcher m = p.matcher(java);
   3.197 +        if (!m.find()) {
   3.198 +            throw new IOException("Can't find package declaration in the java file");
   3.199 +        }
   3.200 +        String pkg = m.group(1);
   3.201 +        return pkg;
   3.202 +    }
   3.203 +    private static String findCls(String java) throws IOException {
   3.204 +        Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE);
   3.205 +        Matcher m = p.matcher(java);
   3.206 +        if (!m.find()) {
   3.207 +            throw new IOException("Can't find package declaration in the java file");
   3.208 +        }
   3.209 +        String cls = m.group(1);
   3.210 +        return cls;
   3.211 +    }
   3.212 +
   3.213 +    String getHtml() {
   3.214 +        String fqn = "'" + pkg + '.' + cls + "'";
   3.215 +        return html.replace("'${fqn}'", fqn);
   3.216 +    }
   3.217 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/json/src/test/java/net/java/html/json/ModelProcessorTest.java	Fri Apr 19 11:11:07 2013 +0200
     4.3 @@ -0,0 +1,63 @@
     4.4 +/**
     4.5 + * HTML via Java(tm) Language Bindings
     4.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4.7 + *
     4.8 + * This program is free software: you can redistribute it and/or modify
     4.9 + * it under the terms of the GNU General Public License as published by
    4.10 + * the Free Software Foundation, version 2 of the License.
    4.11 + *
    4.12 + * This program is distributed in the hope that it will be useful,
    4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.15 + * GNU General Public License for more details. apidesign.org
    4.16 + * designates this particular file as subject to the
    4.17 + * "Classpath" exception as provided by apidesign.org
    4.18 + * in the License file that accompanied this code.
    4.19 + *
    4.20 + * You should have received a copy of the GNU General Public License
    4.21 + * along with this program. Look for COPYING file in the top folder.
    4.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    4.23 + */
    4.24 +package net.java.html.json;
    4.25 +
    4.26 +import java.io.IOException;
    4.27 +import java.util.Locale;
    4.28 +import javax.tools.Diagnostic;
    4.29 +import javax.tools.JavaFileObject;
    4.30 +import static org.testng.Assert.*;
    4.31 +import org.testng.annotations.Test;
    4.32 +
    4.33 +/** Verify errors emitted by the processor.
    4.34 + *
    4.35 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    4.36 + */
    4.37 +public class ModelProcessorTest {
    4.38 +    @Test public void verifyWrongType() throws IOException {
    4.39 +        String html = "<html><body>"
    4.40 +            + "</body></html>";
    4.41 +        String code = "package x.y.z;\n"
    4.42 +            + "import net.java.html.json.Model;\n"
    4.43 +            + "import net.java.html.json.Property;\n"
    4.44 +            + "@Model(className=\"XModel\", properties={\n"
    4.45 +            + "  @Property(name=\"prop\", type=Runnable.class)\n"
    4.46 +            + "})\n"
    4.47 +            + "class X {\n"
    4.48 +            + "}\n";
    4.49 +        
    4.50 +        Compile c = Compile.create(html, code);
    4.51 +        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
    4.52 +        boolean ok = false;
    4.53 +        StringBuilder msgs = new StringBuilder();
    4.54 +        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
    4.55 +            String msg = e.getMessage(Locale.ENGLISH);
    4.56 +            if (!msg.contains("Runnable")) {
    4.57 +                ok = true;
    4.58 +            }
    4.59 +            msgs.append("\n").append(msg);
    4.60 +        }
    4.61 +        if (!ok) {
    4.62 +            fail("Should contain warning about Runnable:" + msgs);
    4.63 +        }
    4.64 +    }
    4.65 +    
    4.66 +}