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 }