javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
branchmodel
changeset 934 19b4ddc302a6
parent 930 e8916518b38d
child 935 2cd6f67472c4
     1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Thu Apr 04 13:08:26 2013 +0200
     1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Fri Apr 05 12:43:17 2013 +0200
     1.3 @@ -34,6 +34,7 @@
     1.4  import javax.annotation.processing.AbstractProcessor;
     1.5  import javax.annotation.processing.Completion;
     1.6  import javax.annotation.processing.Completions;
     1.7 +import javax.annotation.processing.Messager;
     1.8  import javax.annotation.processing.Processor;
     1.9  import javax.annotation.processing.RoundEnvironment;
    1.10  import javax.annotation.processing.SupportedAnnotationTypes;
    1.11 @@ -57,6 +58,7 @@
    1.12  import org.apidesign.bck2brwsr.htmlpage.api.Model;
    1.13  import org.apidesign.bck2brwsr.htmlpage.api.On;
    1.14  import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
    1.15 +import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
    1.16  import org.apidesign.bck2brwsr.htmlpage.api.Page;
    1.17  import org.apidesign.bck2brwsr.htmlpage.api.Property;
    1.18  import org.openide.util.lookup.ServiceProvider;
    1.19 @@ -71,6 +73,7 @@
    1.20      "org.apidesign.bck2brwsr.htmlpage.api.Model",
    1.21      "org.apidesign.bck2brwsr.htmlpage.api.Page",
    1.22      "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
    1.23 +    "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
    1.24      "org.apidesign.bck2brwsr.htmlpage.api.On"
    1.25  })
    1.26  public final class PageProcessor extends AbstractProcessor {
    1.27 @@ -103,6 +106,10 @@
    1.28              return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
    1.29          }
    1.30      }
    1.31 +
    1.32 +    private  Messager err() {
    1.33 +        return processingEnv.getMessager();
    1.34 +    }
    1.35      
    1.36      private boolean processModel(Element e) {
    1.37          boolean ok = true;
    1.38 @@ -135,18 +142,64 @@
    1.39                  w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
    1.40                  w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
    1.41                  w.append("final class ").append(className).append(" implements Cloneable {\n");
    1.42 -                w.append("  private Object json;\n");
    1.43                  w.append("  private boolean locked;\n");
    1.44                  w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
    1.45                  w.append(body.toString());
    1.46                  w.append("  private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
    1.47                  w.append("  public ").append(className).append("() {\n");
    1.48 +                w.append("    intKnckt();\n");
    1.49 +                w.append("  };\n");
    1.50 +                w.append("  private void intKnckt() {\n");
    1.51                  w.append("    ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
    1.52                  writeStringArray(propsGetSet, w);
    1.53                  w.append(", ");
    1.54                  writeStringArray(functions, w);
    1.55                  w.append("    );\n");
    1.56                  w.append("  };\n");
    1.57 +                w.append("  ").append(className).append("(Object json) {\n");
    1.58 +                int values = 0;
    1.59 +                for (int i = 0; i < propsGetSet.size(); i += 4) {
    1.60 +                    if (propsGetSet.get(i + 2) == null) {
    1.61 +                        continue;
    1.62 +                    }
    1.63 +                    values++;
    1.64 +                }
    1.65 +                w.append("    Object[] ret = new Object[" + values + "];\n");
    1.66 +                w.append("    org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
    1.67 +                for (int i = 0; i < propsGetSet.size(); i += 4) {
    1.68 +                    if (propsGetSet.get(i + 2) == null) {
    1.69 +                        continue;
    1.70 +                    }
    1.71 +                    w.append("      \"").append(propsGetSet.get(i)).append("\",\n");
    1.72 +                }
    1.73 +                w.append("    }, ret);\n");
    1.74 +                for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
    1.75 +                    if (propsGetSet.get(i + 2) == null) {
    1.76 +                        continue;
    1.77 +                    }
    1.78 +                    boolean[] isModel = { false };
    1.79 +                    boolean[] isEnum = { false };
    1.80 +                    String type = checkType(m.properties()[prop++], isModel, isEnum);
    1.81 +                    w.append("    this.prop_").append(propsGetSet.get(i)).append(" = ");
    1.82 +                    boolean close = false;
    1.83 +                    if (isEnum[0]) {
    1.84 +//                        w.append(type).append(".valueOf((String)");
    1.85 +//                        close = true;
    1.86 +                        w.append("null;\n");
    1.87 +                        continue;
    1.88 +                    } else {
    1.89 +                        w.append('(').append(type).append(')');
    1.90 +                    }
    1.91 +                    w.append("ret[" + cnt++ + "]");
    1.92 +                    if (close) {
    1.93 +                        w.append(");\n");
    1.94 +                    } else {
    1.95 +                        w.append(";\n");
    1.96 +                    }
    1.97 +                    
    1.98 +                }
    1.99 +                w.append("    intKnckt();\n");
   1.100 +                w.append("  };\n");
   1.101                  writeToString(m.properties(), w);
   1.102                  writeClone(className, m.properties(), w);
   1.103                  w.append("}\n");
   1.104 @@ -154,7 +207,7 @@
   1.105                  w.close();
   1.106              }
   1.107          } catch (IOException ex) {
   1.108 -            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   1.109 +            err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   1.110              return false;
   1.111          }
   1.112          return ok;
   1.113 @@ -173,7 +226,7 @@
   1.114              pp = ProcessPage.readPage(is);
   1.115              is.close();
   1.116          } catch (IOException iOException) {
   1.117 -            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
   1.118 +            err().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
   1.119              ok = false;
   1.120              pp = null;
   1.121          }
   1.122 @@ -197,6 +250,9 @@
   1.123              if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   1.124                  ok = false;
   1.125              }
   1.126 +            if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) {
   1.127 +                ok = false;
   1.128 +            }
   1.129              
   1.130              FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   1.131              w = new OutputStreamWriter(java.openOutputStream());
   1.132 @@ -237,7 +293,7 @@
   1.133                  w.close();
   1.134              }
   1.135          } catch (IOException ex) {
   1.136 -            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   1.137 +            err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   1.138              return false;
   1.139          }
   1.140          return ok;
   1.141 @@ -283,24 +339,24 @@
   1.142                  if (oc != null) {
   1.143                      for (String id : oc.id()) {
   1.144                          if (pp == null) {
   1.145 -                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   1.146 +                            err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   1.147                              ok = false;
   1.148                              continue;
   1.149                          }
   1.150                          if (pp.tagNameForId(id) == null) {
   1.151 -                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   1.152 +                            err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   1.153                              ok = false;
   1.154                              continue;
   1.155                          }
   1.156                          ExecutableElement ee = (ExecutableElement)method;
   1.157                          CharSequence params = wrapParams(ee, id, className, "ev", null);
   1.158                          if (!ee.getModifiers().contains(Modifier.STATIC)) {
   1.159 -                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   1.160 +                            err().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   1.161                              ok = false;
   1.162                              continue;
   1.163                          }
   1.164                          if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   1.165 -                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   1.166 +                            err().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   1.167                              ok = false;
   1.168                              continue;
   1.169                          }
   1.170 @@ -553,7 +609,7 @@
   1.171          if (!isModel[0] && !"java.lang.String".equals(ret) && !isEnum[0]) {
   1.172              String bt = findBoxedType(ret);
   1.173              if (bt == null) {
   1.174 -                processingEnv.getMessager().printMessage(
   1.175 +                err().printMessage(
   1.176                      Diagnostic.Kind.ERROR, 
   1.177                      "Only primitive types supported in the mapping. Not " + ret,
   1.178                      where
   1.179 @@ -604,7 +660,7 @@
   1.180              sb.append('"');
   1.181              sep = ", ";
   1.182          }
   1.183 -        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
   1.184 +        err().printMessage(Diagnostic.Kind.ERROR,
   1.185              propName + " is not one of known properties: " + sb
   1.186              , e
   1.187          );
   1.188 @@ -634,19 +690,19 @@
   1.189                  continue;
   1.190              }
   1.191              if (!e.getModifiers().contains(Modifier.STATIC)) {
   1.192 -                processingEnv.getMessager().printMessage(
   1.193 +                err().printMessage(
   1.194                      Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e
   1.195                  );
   1.196                  return false;
   1.197              }
   1.198              if (e.getModifiers().contains(Modifier.PRIVATE)) {
   1.199 -                processingEnv.getMessager().printMessage(
   1.200 +                err().printMessage(
   1.201                      Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e
   1.202                  );
   1.203                  return false;
   1.204              }
   1.205              if (e.getReturnType().getKind() != TypeKind.VOID) {
   1.206 -                processingEnv.getMessager().printMessage(
   1.207 +                err().printMessage(
   1.208                      Diagnostic.Kind.ERROR, "@OnFunction method should return void", e
   1.209                  );
   1.210                  return false;
   1.211 @@ -664,6 +720,100 @@
   1.212          return true;
   1.213      }
   1.214  
   1.215 +    private boolean generateReceive(
   1.216 +        Element clazz, StringWriter body, String className, 
   1.217 +        List<? extends Element> enclosedElements, List<String> functions
   1.218 +    ) {
   1.219 +        for (Element m : enclosedElements) {
   1.220 +            if (m.getKind() != ElementKind.METHOD) {
   1.221 +                continue;
   1.222 +            }
   1.223 +            ExecutableElement e = (ExecutableElement)m;
   1.224 +            OnReceive onR = e.getAnnotation(OnReceive.class);
   1.225 +            if (onR == null) {
   1.226 +                continue;
   1.227 +            }
   1.228 +            if (!e.getModifiers().contains(Modifier.STATIC)) {
   1.229 +                err().printMessage(
   1.230 +                    Diagnostic.Kind.ERROR, "@OnReceive method needs to be static", e
   1.231 +                );
   1.232 +                return false;
   1.233 +            }
   1.234 +            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   1.235 +                err().printMessage(
   1.236 +                    Diagnostic.Kind.ERROR, "@OnReceive method cannot be private", e
   1.237 +                );
   1.238 +                return false;
   1.239 +            }
   1.240 +            if (e.getReturnType().getKind() != TypeKind.VOID) {
   1.241 +                err().printMessage(
   1.242 +                    Diagnostic.Kind.ERROR, "@OnReceive method should return void", e
   1.243 +                );
   1.244 +                return false;
   1.245 +            }
   1.246 +            String modelClass = null;
   1.247 +            List<String> args = new ArrayList<>();
   1.248 +            {
   1.249 +                for (VariableElement ve : e.getParameters()) {
   1.250 +                    if (ve.asType().toString().equals(className)) {
   1.251 +                        args.add(className + ".this");
   1.252 +                    } else if (isModel(ve.asType())) {
   1.253 +                        if (modelClass != null) {
   1.254 +                            err().printMessage(Diagnostic.Kind.ERROR, "There can be only one model class among arguments", e);
   1.255 +                        } else {
   1.256 +                            modelClass = ve.asType().toString();
   1.257 +                            args.add("new " + modelClass + "(value)");
   1.258 +                        }
   1.259 +                    }
   1.260 +                }
   1.261 +            }
   1.262 +            String n = e.getSimpleName().toString();
   1.263 +            body.append("public void ").append(n).append("(");
   1.264 +            StringBuilder assembleURL = new StringBuilder();
   1.265 +            {
   1.266 +                String sep = "";
   1.267 +                for (String p : findParamNames(e, onR.url(), assembleURL)) {
   1.268 +                    body.append(sep);
   1.269 +                    body.append("String ").append(p);
   1.270 +                    sep = ", ";
   1.271 +                }
   1.272 +            }
   1.273 +            body.append(") {\n");
   1.274 +            body.append("  final Object[] result = { null };\n");
   1.275 +            body.append(
   1.276 +                "  class ProcessResult implements Runnable {\n" +
   1.277 +                "    @Override\n" +
   1.278 +                "    public void run() {\n" +
   1.279 +                "      Object value = result[0];\n" +
   1.280 +                "      if (value instanceof Object[]) {\n" +
   1.281 +                "        throw new IllegalStateException(\"Array value: \" + value);\n" +
   1.282 +                "      } else {\n        ");
   1.283 +            {
   1.284 +                body.append(clazz.getSimpleName()).append(".").append(n).append("(");
   1.285 +                String sep = "";
   1.286 +                for (String arg : args) {
   1.287 +                    body.append(sep);
   1.288 +                    body.append(arg);
   1.289 +                    sep = ", ";
   1.290 +                }
   1.291 +                body.append(");\n");
   1.292 +            }
   1.293 +            body.append(
   1.294 +                "      }\n" +
   1.295 +                "    }\n" +
   1.296 +                "  }\n"
   1.297 +            );
   1.298 +            body.append("  org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n      ");
   1.299 +            body.append(assembleURL);
   1.300 +            body.append(", result, new ProcessResult()\n  );\n");
   1.301 +//            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   1.302 +//            body.append(wrapParams(e, null, className, "ev", "data"));
   1.303 +//            body.append(");\n");
   1.304 +            body.append("}\n");
   1.305 +        }
   1.306 +        return true;
   1.307 +    }
   1.308 +
   1.309      private CharSequence wrapParams(
   1.310          ExecutableElement ee, String id, String className, String evName, String dataName
   1.311      ) {
   1.312 @@ -716,7 +866,7 @@
   1.313                  params.append(className).append(".this");
   1.314                  continue;
   1.315              }
   1.316 -            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   1.317 +            err().printMessage(Diagnostic.Kind.ERROR, 
   1.318                  "@On method can only accept String named 'id' or " + className + " arguments",
   1.319                  ee
   1.320              );
   1.321 @@ -835,4 +985,29 @@
   1.322          }
   1.323          return ret;
   1.324      }
   1.325 +
   1.326 +    private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
   1.327 +        List<String> params = new ArrayList<>();
   1.328 +
   1.329 +        for (int pos = 0; ;) {
   1.330 +            int next = url.indexOf('{', pos);
   1.331 +            if (next == -1) {
   1.332 +                assembleURL.append('"')
   1.333 +                    .append(url.substring(pos))
   1.334 +                    .append('"');
   1.335 +                return params;
   1.336 +            }
   1.337 +            int close = url.indexOf('}', next);
   1.338 +            if (close == -1) {
   1.339 +                err().printMessage(Diagnostic.Kind.ERROR, "Unbalanced '{' and '}' in " + url, e);
   1.340 +                return params;
   1.341 +            }
   1.342 +            final String paramName = url.substring(next + 1, close);
   1.343 +            params.add(paramName);
   1.344 +            assembleURL.append('"')
   1.345 +                .append(url.substring(pos, next))
   1.346 +                .append("\" + ").append(paramName).append(" + ");
   1.347 +            pos = close + 1;
   1.348 +        }
   1.349 +    }
   1.350  }