Allow usage of generated classes in @Model and @Page annotations model
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 10 Apr 2013 09:55:26 +0200
branchmodel
changeset 9613cdaee10e72b
parent 960 4887e22cb810
child 962 787578f33c21
Allow usage of generated classes in @Model and @Page annotations
javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageTest.java
javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java
javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java
     1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Tue Apr 09 10:06:19 2013 +0200
     1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Wed Apr 10 09:55:26 2013 +0200
     1.3 @@ -22,6 +22,8 @@
     1.4  import java.io.OutputStreamWriter;
     1.5  import java.io.StringWriter;
     1.6  import java.io.Writer;
     1.7 +import java.lang.annotation.AnnotationTypeMismatchException;
     1.8 +import java.lang.reflect.Method;
     1.9  import java.util.ArrayList;
    1.10  import java.util.Collection;
    1.11  import java.util.Collections;
    1.12 @@ -35,11 +37,12 @@
    1.13  import javax.annotation.processing.AbstractProcessor;
    1.14  import javax.annotation.processing.Completion;
    1.15  import javax.annotation.processing.Completions;
    1.16 -import javax.annotation.processing.Messager;
    1.17 +import javax.annotation.processing.ProcessingEnvironment;
    1.18  import javax.annotation.processing.Processor;
    1.19  import javax.annotation.processing.RoundEnvironment;
    1.20  import javax.annotation.processing.SupportedAnnotationTypes;
    1.21  import javax.lang.model.element.AnnotationMirror;
    1.22 +import javax.lang.model.element.AnnotationValue;
    1.23  import javax.lang.model.element.Element;
    1.24  import javax.lang.model.element.ElementKind;
    1.25  import javax.lang.model.element.ExecutableElement;
    1.26 @@ -78,10 +81,12 @@
    1.27      "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
    1.28      "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
    1.29      "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange",
    1.30 +    "org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty",
    1.31      "org.apidesign.bck2brwsr.htmlpage.api.On"
    1.32  })
    1.33  public final class PageProcessor extends AbstractProcessor {
    1.34      private final Map<Element,String> models = new WeakHashMap<>();
    1.35 +    private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
    1.36      @Override
    1.37      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    1.38          boolean ok = true;
    1.39 @@ -97,6 +102,42 @@
    1.40          }
    1.41          if (roundEnv.processingOver()) {
    1.42              models.clear();
    1.43 +            for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
    1.44 +                TypeElement te = (TypeElement)entry.getKey();
    1.45 +                String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
    1.46 +                Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
    1.47 +                if (finalElem == null) {
    1.48 +                    continue;
    1.49 +                }
    1.50 +                Prprt[] props;
    1.51 +                Model m = finalElem.getAnnotation(Model.class);
    1.52 +                if (m != null) {
    1.53 +                    props = Prprt.wrap(processingEnv, finalElem, m.properties());
    1.54 +                } else {
    1.55 +                    Page p = finalElem.getAnnotation(Page.class);
    1.56 +                    props = Prprt.wrap(processingEnv, finalElem, p.properties());
    1.57 +                }
    1.58 +                for (Prprt p : props) {
    1.59 +                    boolean[] isModel = { false };
    1.60 +                    boolean[] isEnum = { false };
    1.61 +                    boolean[] isPrimitive = { false };
    1.62 +                    String t = checkType(p, isModel, isEnum, isPrimitive);
    1.63 +                    if (isEnum[0]) {
    1.64 +                        continue;
    1.65 +                    }
    1.66 +                    if (isPrimitive[0]) {
    1.67 +                        continue;
    1.68 +                    }
    1.69 +                    if (isModel[0]) {
    1.70 +                        continue;
    1.71 +                    }
    1.72 +                    if ("java.lang.String".equals(t)) {
    1.73 +                        continue;
    1.74 +                    }
    1.75 +                    error("The type " + t + " should be defined by @Model annotation", entry.getKey());
    1.76 +                }
    1.77 +            }
    1.78 +            verify.clear();
    1.79          }
    1.80          return ok;
    1.81      }
    1.82 @@ -111,8 +152,8 @@
    1.83          }
    1.84      }
    1.85  
    1.86 -    private  Messager err() {
    1.87 -        return processingEnv.getMessager();
    1.88 +    private void error(String msg, Element e) {
    1.89 +        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
    1.90      }
    1.91      
    1.92      private boolean processModel(Element e) {
    1.93 @@ -131,13 +172,15 @@
    1.94              List<String> functions = new ArrayList<>();
    1.95              Map<String, Collection<String>> propsDeps = new HashMap<>();
    1.96              Map<String, Collection<String>> functionDeps = new HashMap<>();
    1.97 -            if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
    1.98 +            Prprt[] props = createProps(e, m.properties());
    1.99 +            
   1.100 +            if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
   1.101                  ok = false;
   1.102              }
   1.103 -            if (!generateOnChange(e, propsDeps, m.properties(), className, functionDeps)) {
   1.104 +            if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
   1.105                  ok = false;
   1.106              }
   1.107 -            if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps, functionDeps)) {
   1.108 +            if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
   1.109                  ok = false;
   1.110              }
   1.111              if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   1.112 @@ -168,7 +211,7 @@
   1.113                  w.append("  ").append(className).append("(Object json) {\n");
   1.114                  int values = 0;
   1.115                  for (int i = 0; i < propsGetSet.size(); i += 4) {
   1.116 -                    Property p = findProperty(m.properties(), propsGetSet.get(i));
   1.117 +                    Prprt p = findPrprt(props, propsGetSet.get(i));
   1.118                      if (p == null) {
   1.119                          continue;
   1.120                      }
   1.121 @@ -177,7 +220,7 @@
   1.122                  w.append("    Object[] ret = new Object[" + values + "];\n");
   1.123                  w.append("    org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
   1.124                  for (int i = 0; i < propsGetSet.size(); i += 4) {
   1.125 -                    Property p = findProperty(m.properties(), propsGetSet.get(i));
   1.126 +                    Prprt p = findPrprt(props, propsGetSet.get(i));
   1.127                      if (p == null) {
   1.128                          continue;
   1.129                      }
   1.130 @@ -186,13 +229,14 @@
   1.131                  w.append("    }, ret);\n");
   1.132                  for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
   1.133                      final String pn = propsGetSet.get(i);
   1.134 -                    Property p = findProperty(m.properties(), pn);
   1.135 +                    Prprt p = findPrprt(props, pn);
   1.136                      if (p == null) {
   1.137                          continue;
   1.138                      }
   1.139                      boolean[] isModel = { false };
   1.140                      boolean[] isEnum = { false };
   1.141 -                    String type = checkType(m.properties()[prop++], isModel, isEnum);
   1.142 +                    boolean isPrimitive[] = { false };
   1.143 +                    String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
   1.144                      if (p.array()) {
   1.145                          w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
   1.146                          w.append("  for (Object e : ((Object[])ret[" + cnt + "])) {\n");
   1.147 @@ -233,14 +277,14 @@
   1.148                  }
   1.149                  w.append("    intKnckt();\n");
   1.150                  w.append("  };\n");
   1.151 -                writeToString(m.properties(), w);
   1.152 -                writeClone(className, m.properties(), w);
   1.153 +                writeToString(props, w);
   1.154 +                writeClone(className, props, w);
   1.155                  w.append("}\n");
   1.156              } finally {
   1.157                  w.close();
   1.158              }
   1.159          } catch (IOException ex) {
   1.160 -            err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   1.161 +            error("Can't create " + className + ".java", e);
   1.162              return false;
   1.163          }
   1.164          return ok;
   1.165 @@ -259,7 +303,7 @@
   1.166              pp = ProcessPage.readPage(is);
   1.167              is.close();
   1.168          } catch (IOException iOException) {
   1.169 -            err().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
   1.170 +            error("Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
   1.171              ok = false;
   1.172              pp = null;
   1.173          }
   1.174 @@ -275,13 +319,15 @@
   1.175              List<String> functions = new ArrayList<>();
   1.176              Map<String, Collection<String>> propsDeps = new HashMap<>();
   1.177              Map<String, Collection<String>> functionDeps = new HashMap<>();
   1.178 -            if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   1.179 +            
   1.180 +            Prprt[] props = createProps(e, p.properties());
   1.181 +            if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
   1.182                  ok = false;
   1.183              }
   1.184 -            if (!generateOnChange(e, propsDeps, p.properties(), className, functionDeps)) {
   1.185 +            if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
   1.186                  ok = false;
   1.187              }
   1.188 -            if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps, functionDeps)) {
   1.189 +            if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
   1.190                  ok = false;
   1.191              }
   1.192              if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   1.193 @@ -330,7 +376,7 @@
   1.194                  w.close();
   1.195              }
   1.196          } catch (IOException ex) {
   1.197 -            err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   1.198 +            error("Can't create " + className + ".java", e);
   1.199              return false;
   1.200          }
   1.201          return ok;
   1.202 @@ -376,24 +422,24 @@
   1.203                  if (oc != null) {
   1.204                      for (String id : oc.id()) {
   1.205                          if (pp == null) {
   1.206 -                            err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   1.207 +                            error("id = " + id + " not found in HTML page.", method);
   1.208                              ok = false;
   1.209                              continue;
   1.210                          }
   1.211                          if (pp.tagNameForId(id) == null) {
   1.212 -                            err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   1.213 +                            error("id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   1.214                              ok = false;
   1.215                              continue;
   1.216                          }
   1.217                          ExecutableElement ee = (ExecutableElement)method;
   1.218                          CharSequence params = wrapParams(ee, id, className, "ev", null);
   1.219                          if (!ee.getModifiers().contains(Modifier.STATIC)) {
   1.220 -                            err().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   1.221 +                            error("@On method has to be static", ee);
   1.222                              ok = false;
   1.223                              continue;
   1.224                          }
   1.225                          if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   1.226 -                            err().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   1.227 +                            error("@On method can't be private", ee);
   1.228                              ok = false;
   1.229                              continue;
   1.230                          }
   1.231 @@ -473,13 +519,13 @@
   1.232  
   1.233      private boolean generateProperties(
   1.234          Element where,
   1.235 -        Writer w, Property[] properties,
   1.236 +        Writer w, Prprt[] properties,
   1.237          Collection<String> props, 
   1.238          Map<String,Collection<String>> deps,
   1.239          Map<String,Collection<String>> functionDeps
   1.240      ) throws IOException {
   1.241          boolean ok = true;
   1.242 -        for (Property p : properties) {
   1.243 +        for (Prprt p : properties) {
   1.244              final String tn;
   1.245              tn = typeName(where, p);
   1.246              String[] gs = toGetSet(p.name(), tn, p.array());
   1.247 @@ -549,7 +595,7 @@
   1.248      }
   1.249  
   1.250      private boolean generateComputedProperties(
   1.251 -        Writer w, Property[] fixedProps,
   1.252 +        Writer w, Prprt[] fixedProps,
   1.253          Collection<? extends Element> arr, Collection<String> props,
   1.254          Map<String,Collection<String>> deps
   1.255      ) throws IOException {
   1.256 @@ -651,27 +697,18 @@
   1.257          };
   1.258      }
   1.259  
   1.260 -    private String typeName(Element where, Property p) {
   1.261 +    private String typeName(Element where, Prprt p) {
   1.262          String ret;
   1.263          boolean[] isModel = { false };
   1.264          boolean[] isEnum = { false };
   1.265 -        ret = checkType(p, isModel, isEnum);
   1.266 +        boolean isPrimitive[] = { false };
   1.267 +        ret = checkType(p, isModel, isEnum, isPrimitive);
   1.268          if (p.array()) {
   1.269              String bt = findBoxedType(ret);
   1.270              if (bt != null) {
   1.271                  return bt;
   1.272              }
   1.273          }
   1.274 -        if (!isModel[0] && !"java.lang.String".equals(ret) && !isEnum[0]) {
   1.275 -            String bt = findBoxedType(ret);
   1.276 -            if (bt == null) {
   1.277 -                err().printMessage(
   1.278 -                    Diagnostic.Kind.ERROR, 
   1.279 -                    "Only primitive types supported in the mapping. Not " + ret,
   1.280 -                    where
   1.281 -                );
   1.282 -            }
   1.283 -        }
   1.284          return ret;
   1.285      }
   1.286      
   1.287 @@ -703,20 +740,20 @@
   1.288          return null;
   1.289      }
   1.290  
   1.291 -    private boolean verifyPropName(Element e, String propName, Property[] existingProps) {
   1.292 +    private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
   1.293          StringBuilder sb = new StringBuilder();
   1.294          String sep = "";
   1.295 -        for (Property property : existingProps) {
   1.296 -            if (property.name().equals(propName)) {
   1.297 +        for (Prprt Prprt : existingProps) {
   1.298 +            if (Prprt.name().equals(propName)) {
   1.299                  return true;
   1.300              }
   1.301              sb.append(sep);
   1.302              sb.append('"');
   1.303 -            sb.append(property.name());
   1.304 +            sb.append(Prprt.name());
   1.305              sb.append('"');
   1.306              sep = ", ";
   1.307          }
   1.308 -        err().printMessage(Diagnostic.Kind.ERROR,
   1.309 +        error(
   1.310              propName + " is not one of known properties: " + sb
   1.311              , e
   1.312          );
   1.313 @@ -746,21 +783,15 @@
   1.314                  continue;
   1.315              }
   1.316              if (!e.getModifiers().contains(Modifier.STATIC)) {
   1.317 -                err().printMessage(
   1.318 -                    Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e
   1.319 -                );
   1.320 +                error("@OnFunction method needs to be static", e);
   1.321                  return false;
   1.322              }
   1.323              if (e.getModifiers().contains(Modifier.PRIVATE)) {
   1.324 -                err().printMessage(
   1.325 -                    Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e
   1.326 -                );
   1.327 +                error("@OnFunction method cannot be private", e);
   1.328                  return false;
   1.329              }
   1.330              if (e.getReturnType().getKind() != TypeKind.VOID) {
   1.331 -                err().printMessage(
   1.332 -                    Diagnostic.Kind.ERROR, "@OnFunction method should return void", e
   1.333 -                );
   1.334 +                error("@OnFunction method should return void", e);
   1.335                  return false;
   1.336              }
   1.337              String n = e.getSimpleName().toString();
   1.338 @@ -777,7 +808,7 @@
   1.339      }
   1.340  
   1.341      private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
   1.342 -        Property[] properties, String className, 
   1.343 +        Prprt[] properties, String className, 
   1.344          Map<String, Collection<String>> functionDeps
   1.345      ) {
   1.346          for (Element m : clazz.getEnclosedElements()) {
   1.347 @@ -790,24 +821,21 @@
   1.348                  continue;
   1.349              }
   1.350              for (String pn : onPC.value()) {
   1.351 -                if (findProperty(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   1.352 -                    err().printMessage(Diagnostic.Kind.ERROR, "No property named '" + pn + "' in the model");
   1.353 +                if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   1.354 +                    error("No Prprt named '" + pn + "' in the model", clazz);
   1.355                      return false;
   1.356                  }
   1.357              }
   1.358              if (!e.getModifiers().contains(Modifier.STATIC)) {
   1.359 -                err().printMessage(
   1.360 -                    Diagnostic.Kind.ERROR, "@OnPropertyChange method needs to be static", e);
   1.361 +                error("@OnPrprtChange method needs to be static", e);
   1.362                  return false;
   1.363              }
   1.364              if (e.getModifiers().contains(Modifier.PRIVATE)) {
   1.365 -                err().printMessage(
   1.366 -                    Diagnostic.Kind.ERROR, "@OnPropertyChange method cannot be private", e);
   1.367 +                error("@OnPrprtChange method cannot be private", e);
   1.368                  return false;
   1.369              }
   1.370              if (e.getReturnType().getKind() != TypeKind.VOID) {
   1.371 -                err().printMessage(
   1.372 -                    Diagnostic.Kind.ERROR, "@OnPropertyChange method should return void", e);
   1.373 +                error("@OnPrprtChange method should return void", e);
   1.374                  return false;
   1.375              }
   1.376              String n = e.getSimpleName().toString();
   1.377 @@ -852,21 +880,15 @@
   1.378                  continue;
   1.379              }
   1.380              if (!e.getModifiers().contains(Modifier.STATIC)) {
   1.381 -                err().printMessage(
   1.382 -                    Diagnostic.Kind.ERROR, "@OnReceive method needs to be static", e
   1.383 -                );
   1.384 +                error("@OnReceive method needs to be static", e);
   1.385                  return false;
   1.386              }
   1.387              if (e.getModifiers().contains(Modifier.PRIVATE)) {
   1.388 -                err().printMessage(
   1.389 -                    Diagnostic.Kind.ERROR, "@OnReceive method cannot be private", e
   1.390 -                );
   1.391 +                error("@OnReceive method cannot be private", e);
   1.392                  return false;
   1.393              }
   1.394              if (e.getReturnType().getKind() != TypeKind.VOID) {
   1.395 -                err().printMessage(
   1.396 -                    Diagnostic.Kind.ERROR, "@OnReceive method should return void", e
   1.397 -                );
   1.398 +                error("@OnReceive method should return void", e);
   1.399                  return false;
   1.400              }
   1.401              String modelClass = null;
   1.402 @@ -885,7 +907,7 @@
   1.403                      }
   1.404                      if (modelType != null) {
   1.405                          if (modelClass != null) {
   1.406 -                            err().printMessage(Diagnostic.Kind.ERROR, "There can be only one model class among arguments", e);
   1.407 +                            error("There can be only one model class among arguments", e);
   1.408                          } else {
   1.409                              modelClass = modelType.toString();
   1.410                              if (expectsList) {
   1.411 @@ -898,7 +920,7 @@
   1.412                  }
   1.413              }
   1.414              if (modelClass == null) {
   1.415 -                err().printMessage(Diagnostic.Kind.ERROR, "The method needs to have one @Model class as parameter", e);
   1.416 +                error("The method needs to have one @Model class as parameter", e);
   1.417              }
   1.418              String n = e.getSimpleName().toString();
   1.419              body.append("public void ").append(n).append("(");
   1.420 @@ -918,9 +940,9 @@
   1.421                      sep = ", ";
   1.422                  }
   1.423                  if (!skipJSONP) {
   1.424 -                    err().printMessage(Diagnostic.Kind.ERROR, 
   1.425 +                    error(
   1.426                          "Name of jsonp attribute ('" + onR.jsonp() + 
   1.427 -                        "') is not used in url attribute '" + onR.url() + "'"
   1.428 +                        "') is not used in url attribute '" + onR.url() + "'", e
   1.429                      );
   1.430                  }
   1.431              }
   1.432 @@ -1016,7 +1038,7 @@
   1.433                          if (dataName != null) {
   1.434                              sb.append(" Try \"").append(dataName).append("\"");
   1.435                          }
   1.436 -                        err().printMessage(Diagnostic.Kind.ERROR, sb.toString(), ee);
   1.437 +                        error(sb.toString(), ee);
   1.438                      }
   1.439                      params.append(evName);
   1.440                      params.append(", \"");
   1.441 @@ -1035,7 +1057,7 @@
   1.442                  params.append(className).append(".this");
   1.443                  continue;
   1.444              }
   1.445 -            err().printMessage(Diagnostic.Kind.ERROR, 
   1.446 +            error(
   1.447                  "@On method can only accept String named 'id' or " + className + " arguments",
   1.448                  ee
   1.449              );
   1.450 @@ -1059,7 +1081,7 @@
   1.451                  if (propName != null && ve.getSimpleName().contentEquals(propName)) {
   1.452                      params.append('"').append(propValue).append('"');
   1.453                  } else {
   1.454 -                    err().printMessage(Diagnostic.Kind.ERROR, "Unexpected string parameter name. Try \"" + propName + "\".");
   1.455 +                    error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
   1.456                  }
   1.457                  continue;
   1.458              }
   1.459 @@ -1072,8 +1094,8 @@
   1.460                  params.append(className).append(".this");
   1.461                  continue;
   1.462              }
   1.463 -            err().printMessage(Diagnostic.Kind.ERROR,
   1.464 -                "@OnPropertyChange method can only accept String or " + className + " arguments",
   1.465 +            error(
   1.466 +                "@OnPrprtChange method can only accept String or " + className + " arguments",
   1.467                  ee);
   1.468          }
   1.469          return params;
   1.470 @@ -1110,12 +1132,12 @@
   1.471          w.write("\n  }");
   1.472      }
   1.473      
   1.474 -    private void writeToString(Property[] props, Writer w) throws IOException {
   1.475 +    private void writeToString(Prprt[] props, Writer w) throws IOException {
   1.476          w.write("  public String toString() {\n");
   1.477          w.write("    StringBuilder sb = new StringBuilder();\n");
   1.478          w.write("    sb.append('{');\n");
   1.479          String sep = "";
   1.480 -        for (Property p : props) {
   1.481 +        for (Prprt p : props) {
   1.482              w.write(sep);
   1.483              w.append("    sb.append(\"" + p.name() + ": \");\n");
   1.484              w.append("    sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
   1.485 @@ -1126,14 +1148,15 @@
   1.486          w.write("    return sb.toString();\n");
   1.487          w.write("  }\n");
   1.488      }
   1.489 -    private void writeClone(String className, Property[] props, Writer w) throws IOException {
   1.490 +    private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
   1.491          w.write("  public " + className + " clone() {\n");
   1.492          w.write("    " + className + " ret = new " + className + "();\n");
   1.493 -        for (Property p : props) {
   1.494 +        for (Prprt p : props) {
   1.495              if (!p.array()) {
   1.496                  boolean isModel[] = { false };
   1.497                  boolean isEnum[] = { false };
   1.498 -                checkType(p, isModel, isEnum);
   1.499 +                boolean isPrimitive[] = { false };
   1.500 +                checkType(p, isModel, isEnum, isPrimitive);
   1.501                  if (!isModel[0]) {
   1.502                      w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
   1.503                      continue;
   1.504 @@ -1171,26 +1194,59 @@
   1.505          return pt.toString();
   1.506      }
   1.507  
   1.508 -    private String checkType(Property p, boolean[] isModel, boolean[] isEnum) {
   1.509 +    private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
   1.510 +        TypeMirror tm;
   1.511 +        try {
   1.512 +            String ret = p.typeName(processingEnv);
   1.513 +            TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
   1.514 +            if (e == null) {
   1.515 +                isModel[0] = true;
   1.516 +                isEnum[0] = false;
   1.517 +                isPrimitive[0] = false;
   1.518 +                return ret;
   1.519 +            }
   1.520 +            tm = e.asType();
   1.521 +        } catch (MirroredTypeException ex) {
   1.522 +            tm = ex.getTypeMirror();
   1.523 +        }
   1.524 +        tm = processingEnv.getTypeUtils().erasure(tm);
   1.525 +        isPrimitive[0] = tm.getKind().isPrimitive();
   1.526 +        final Element e = processingEnv.getTypeUtils().asElement(tm);
   1.527 +        final Model m = e == null ? null : e.getAnnotation(Model.class);
   1.528 +        
   1.529          String ret;
   1.530 -        try {
   1.531 -            ret = p.type().getName();
   1.532 -        } catch (MirroredTypeException ex) {
   1.533 -            TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror());
   1.534 -            final Element e = processingEnv.getTypeUtils().asElement(tm);
   1.535 -            final Model m = e == null ? null : e.getAnnotation(Model.class);
   1.536 -            if (m != null) {
   1.537 -                ret = findPkgName(e) + '.' + m.className();
   1.538 -                isModel[0] = true;
   1.539 -                models.put(e, m.className());
   1.540 -            } else {
   1.541 -                ret = tm.toString();
   1.542 +        if (m != null) {
   1.543 +            ret = findPkgName(e) + '.' + m.className();
   1.544 +            isModel[0] = true;
   1.545 +            models.put(e, m.className());
   1.546 +        } else if (findModelForMthd(e)) {
   1.547 +            ret = ((TypeElement)e).getQualifiedName().toString();
   1.548 +            isModel[0] = true;
   1.549 +        } else {
   1.550 +            ret = tm.toString();
   1.551 +        }
   1.552 +        TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
   1.553 +        enm = processingEnv.getTypeUtils().erasure(enm);
   1.554 +        isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
   1.555 +        return ret;
   1.556 +    }
   1.557 +    
   1.558 +    private static boolean findModelForMthd(Element clazz) {
   1.559 +        if (clazz == null) {
   1.560 +            return false;
   1.561 +        }
   1.562 +        for (Element e : clazz.getEnclosedElements()) {
   1.563 +            if (e.getKind() == ElementKind.METHOD) {
   1.564 +                ExecutableElement ee = (ExecutableElement)e;
   1.565 +                if (
   1.566 +                    ee.getSimpleName().contentEquals("modelFor") &&
   1.567 +                    ee.getParameters().isEmpty()
   1.568 +                ) {
   1.569 +                    return true;
   1.570 +                }
   1.571              }
   1.572 -            TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
   1.573 -            enm = processingEnv.getTypeUtils().erasure(enm);
   1.574 -            isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
   1.575          }
   1.576 -        return ret;
   1.577 +        return false;
   1.578      }
   1.579  
   1.580      private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
   1.581 @@ -1206,7 +1262,7 @@
   1.582              }
   1.583              int close = url.indexOf('}', next);
   1.584              if (close == -1) {
   1.585 -                err().printMessage(Diagnostic.Kind.ERROR, "Unbalanced '{' and '}' in " + url, e);
   1.586 +                error("Unbalanced '{' and '}' in " + url, e);
   1.587                  return params;
   1.588              }
   1.589              final String paramName = url.substring(next + 1, close);
   1.590 @@ -1218,8 +1274,8 @@
   1.591          }
   1.592      }
   1.593  
   1.594 -    private static Property findProperty(Property[] properties, String propName) {
   1.595 -        for (Property p : properties) {
   1.596 +    private static Prprt findPrprt(Prprt[] properties, String propName) {
   1.597 +        for (Prprt p : properties) {
   1.598              if (propName.equals(p.name())) {
   1.599                  return p;
   1.600              }
   1.601 @@ -1246,4 +1302,96 @@
   1.602          }
   1.603          return names;
   1.604      }
   1.605 +    
   1.606 +    private Prprt[] createProps(Element e, Property[] arr) {
   1.607 +        Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
   1.608 +        Prprt[] prev = verify.put(e, ret);
   1.609 +        if (prev != null) {
   1.610 +            error("Two sets of properties for ", e);
   1.611 +        }
   1.612 +        return ret;
   1.613 +    }
   1.614 +    
   1.615 +    private static class Prprt {
   1.616 +        private final Element e;
   1.617 +        private final AnnotationMirror tm;
   1.618 +        private final Property p;
   1.619 +
   1.620 +        public Prprt(Element e, AnnotationMirror tm, Property p) {
   1.621 +            this.e = e;
   1.622 +            this.tm = tm;
   1.623 +            this.p = p;
   1.624 +        }
   1.625 +        
   1.626 +        String name() {
   1.627 +            return p.name();
   1.628 +        }
   1.629 +        
   1.630 +        boolean array() {
   1.631 +            return p.array();
   1.632 +        }
   1.633 +
   1.634 +        String typeName(ProcessingEnvironment env) {
   1.635 +            try {
   1.636 +                return p.type().getName();
   1.637 +            } catch (AnnotationTypeMismatchException ex) {
   1.638 +                for (Object v : getAnnoValues(env)) {
   1.639 +                    String s = v.toString().replace(" ", "");
   1.640 +                    if (s.startsWith("type=") && s.endsWith(".class")) {
   1.641 +                        return s.substring(5, s.length() - 6);
   1.642 +                    }
   1.643 +                }
   1.644 +                throw ex;
   1.645 +            }
   1.646 +        }
   1.647 +        
   1.648 +        
   1.649 +        static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
   1.650 +            if (arr.length == 0) {
   1.651 +                return new Prprt[0];
   1.652 +            }
   1.653 +            
   1.654 +            if (e.getKind() != ElementKind.CLASS) {
   1.655 +                throw new IllegalStateException("" + e.getKind());
   1.656 +            }
   1.657 +            TypeElement te = (TypeElement)e;
   1.658 +            List<? extends AnnotationValue> val = null;
   1.659 +            for (AnnotationMirror an : te.getAnnotationMirrors()) {
   1.660 +                for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
   1.661 +                    if (entry.getKey().getSimpleName().contentEquals("properties")) {
   1.662 +                        val = (List)entry.getValue().getValue();
   1.663 +                        break;
   1.664 +                    }
   1.665 +                }
   1.666 +            }
   1.667 +            if (val == null || val.size() != arr.length) {
   1.668 +                pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
   1.669 +                return new Prprt[0];
   1.670 +            }
   1.671 +            Prprt[] ret = new Prprt[arr.length];
   1.672 +            BIG: for (int i = 0; i < ret.length; i++) {
   1.673 +                AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
   1.674 +                ret[i] = new Prprt(e, am, arr[i]);
   1.675 +                
   1.676 +            }
   1.677 +            return ret;
   1.678 +        }
   1.679 +        
   1.680 +        private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
   1.681 +            try {
   1.682 +                Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
   1.683 +                Method m = trees.getMethod("instance", ProcessingEnvironment.class);
   1.684 +                Object instance = m.invoke(null, pe);
   1.685 +                m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
   1.686 +                Object path = m.invoke(instance, e, tm);
   1.687 +                m = path.getClass().getMethod("getLeaf");
   1.688 +                Object leaf = m.invoke(path);
   1.689 +                m = leaf.getClass().getMethod("getArguments");
   1.690 +                return (List)m.invoke(leaf);
   1.691 +            } catch (Exception ex) {
   1.692 +                return Collections.emptyList();
   1.693 +            }
   1.694 +        }
   1.695 +    }
   1.696 +    
   1.697  }
     2.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageTest.java	Tue Apr 09 10:06:19 2013 +0200
     2.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageTest.java	Wed Apr 10 09:55:26 2013 +0200
     2.3 @@ -19,6 +19,8 @@
     2.4  
     2.5  import java.io.IOException;
     2.6  import java.util.Locale;
     2.7 +import javax.tools.Diagnostic;
     2.8 +import javax.tools.JavaFileObject;
     2.9  import static org.testng.Assert.*;
    2.10  import org.testng.annotations.Test;
    2.11  
    2.12 @@ -39,11 +41,12 @@
    2.13              + "}\n";
    2.14          
    2.15          Compile c = Compile.create(html, code);
    2.16 -        assertEquals(c.getErrors().size(), 1, "One error: " + c.getErrors());
    2.17 -        
    2.18 -        String msg = c.getErrors().get(0).getMessage(Locale.ENGLISH);
    2.19 -        if (!msg.contains("Runnable")) {
    2.20 -            fail("Should contain warning about Runnable: " + msg);
    2.21 +        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
    2.22 +        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
    2.23 +            String msg = e.getMessage(Locale.ENGLISH);
    2.24 +            if (!msg.contains("Runnable")) {
    2.25 +                fail("Should contain warning about Runnable: " + msg);
    2.26 +            }
    2.27          }
    2.28      }
    2.29      
     3.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java	Tue Apr 09 10:06:19 2013 +0200
     3.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java	Wed Apr 10 09:55:26 2013 +0200
     3.3 @@ -52,7 +52,7 @@
     3.4      }
     3.5      
     3.6      @Model(className = "People", properties = {
     3.7 -        @Property(array = true, name = "info", type = PersonImpl.class),
     3.8 +        @Property(array = true, name = "info", type = Person.class),
     3.9          @Property(array = true, name = "nicknames", type = String.class),
    3.10          @Property(array = true, name = "age", type = int.class),
    3.11          @Property(array = true, name = "sex", type = Sex.class)
     4.1 --- a/javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java	Tue Apr 09 10:06:19 2013 +0200
     4.2 +++ b/javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java	Wed Apr 10 09:55:26 2013 +0200
     4.3 @@ -29,11 +29,11 @@
     4.4   * @author Jaroslav Tulach
     4.5   */
     4.6  @Page(xhtml="index.html", className="TwitterModel", properties={
     4.7 -    @Property(name="savedLists", type=TwitterClient.Twttrs.class, array = true),
     4.8 +    @Property(name="savedLists", type=Tweeters.class, array = true),
     4.9      @Property(name="activeTweetersName", type=String.class),
    4.10      @Property(name="activeTweeters", type=String.class, array = true),
    4.11      @Property(name="userNameToAdd", type=String.class),
    4.12 -    @Property(name="currentTweets", type=TwitterClient.Twt.class, array = true)
    4.13 +    @Property(name="currentTweets", type=Tweet.class, array = true)
    4.14  })
    4.15  public class TwitterClient {
    4.16      @Model(className = "Tweeters", properties = {