1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Mon Mar 18 17:17:00 2013 +0100
1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Thu Apr 11 20:44:46 2013 +0200
1.3 @@ -20,23 +20,29 @@
1.4 import java.io.IOException;
1.5 import java.io.InputStream;
1.6 import java.io.OutputStreamWriter;
1.7 +import java.io.StringWriter;
1.8 import java.io.Writer;
1.9 +import java.lang.annotation.AnnotationTypeMismatchException;
1.10 +import java.lang.reflect.Method;
1.11 import java.util.ArrayList;
1.12 import java.util.Collection;
1.13 import java.util.Collections;
1.14 import java.util.HashMap;
1.15 +import java.util.HashSet;
1.16 import java.util.LinkedHashSet;
1.17 import java.util.List;
1.18 -import java.util.Locale;
1.19 import java.util.Map;
1.20 import java.util.Set;
1.21 +import java.util.WeakHashMap;
1.22 import javax.annotation.processing.AbstractProcessor;
1.23 import javax.annotation.processing.Completion;
1.24 import javax.annotation.processing.Completions;
1.25 +import javax.annotation.processing.ProcessingEnvironment;
1.26 import javax.annotation.processing.Processor;
1.27 import javax.annotation.processing.RoundEnvironment;
1.28 import javax.annotation.processing.SupportedAnnotationTypes;
1.29 import javax.lang.model.element.AnnotationMirror;
1.30 +import javax.lang.model.element.AnnotationValue;
1.31 import javax.lang.model.element.Element;
1.32 import javax.lang.model.element.ElementKind;
1.33 import javax.lang.model.element.ExecutableElement;
1.34 @@ -44,14 +50,21 @@
1.35 import javax.lang.model.element.PackageElement;
1.36 import javax.lang.model.element.TypeElement;
1.37 import javax.lang.model.element.VariableElement;
1.38 +import javax.lang.model.type.ArrayType;
1.39 import javax.lang.model.type.MirroredTypeException;
1.40 import javax.lang.model.type.TypeKind;
1.41 import javax.lang.model.type.TypeMirror;
1.42 +import javax.lang.model.util.Elements;
1.43 +import javax.lang.model.util.Types;
1.44 import javax.tools.Diagnostic;
1.45 import javax.tools.FileObject;
1.46 import javax.tools.StandardLocation;
1.47 import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
1.48 +import org.apidesign.bck2brwsr.htmlpage.api.Model;
1.49 import org.apidesign.bck2brwsr.htmlpage.api.On;
1.50 +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
1.51 +import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange;
1.52 +import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
1.53 import org.apidesign.bck2brwsr.htmlpage.api.Page;
1.54 import org.apidesign.bck2brwsr.htmlpage.api.Property;
1.55 import org.openide.util.lookup.ServiceProvider;
1.56 @@ -63,89 +76,70 @@
1.57 */
1.58 @ServiceProvider(service=Processor.class)
1.59 @SupportedAnnotationTypes({
1.60 + "org.apidesign.bck2brwsr.htmlpage.api.Model",
1.61 "org.apidesign.bck2brwsr.htmlpage.api.Page",
1.62 + "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
1.63 + "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
1.64 + "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange",
1.65 + "org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty",
1.66 "org.apidesign.bck2brwsr.htmlpage.api.On"
1.67 })
1.68 public final class PageProcessor extends AbstractProcessor {
1.69 + private final Map<Element,String> models = new WeakHashMap<>();
1.70 + private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
1.71 @Override
1.72 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1.73 - for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
1.74 - Page p = e.getAnnotation(Page.class);
1.75 - if (p == null) {
1.76 - continue;
1.77 - }
1.78 - PackageElement pe = (PackageElement)e.getEnclosingElement();
1.79 - String pkg = pe.getQualifiedName().toString();
1.80 -
1.81 - ProcessPage pp;
1.82 - try {
1.83 - InputStream is = openStream(pkg, p.xhtml());
1.84 - pp = ProcessPage.readPage(is);
1.85 - is.close();
1.86 - } catch (IOException iOException) {
1.87 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
1.88 - return false;
1.89 - }
1.90 - Writer w;
1.91 - String className = p.className();
1.92 - if (className.isEmpty()) {
1.93 - int indx = p.xhtml().indexOf('.');
1.94 - className = p.xhtml().substring(0, indx);
1.95 - }
1.96 - try {
1.97 - FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
1.98 - w = new OutputStreamWriter(java.openOutputStream());
1.99 - try {
1.100 - w.append("package " + pkg + ";\n");
1.101 - w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
1.102 - w.append("public final class ").append(className).append(" {\n");
1.103 - w.append(" private boolean locked;\n");
1.104 - if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
1.105 - return false;
1.106 - }
1.107 - for (String id : pp.ids()) {
1.108 - String tag = pp.tagNameForId(id);
1.109 - String type = type(tag);
1.110 - w.append(" ").append("public final ").
1.111 - append(type).append(' ').append(cnstnt(id)).append(" = new ").
1.112 - append(type).append("(\"").append(id).append("\");\n");
1.113 - }
1.114 - List<String> propsGetSet = new ArrayList<String>();
1.115 - Map<String,Collection<String>> propsDeps = new HashMap<String, Collection<String>>();
1.116 - generateComputedProperties(w, e.getEnclosedElements(), propsGetSet, propsDeps);
1.117 - generateProperties(w, p.properties(), propsGetSet, propsDeps);
1.118 - w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
1.119 - if (!propsGetSet.isEmpty()) {
1.120 - w.write("public " + className + " applyBindings() {\n");
1.121 - w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
1.122 - w.write(className + ".class, this, ");
1.123 - w.write("new String[] {\n");
1.124 - String sep = "";
1.125 - for (String n : propsGetSet) {
1.126 - w.write(sep);
1.127 - if (n == null) {
1.128 - w.write(" null");
1.129 - } else {
1.130 - w.write(" \"" + n + "\"");
1.131 - }
1.132 - sep = ",\n";
1.133 - }
1.134 - w.write("\n });\n return this;\n}\n");
1.135 -
1.136 - w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
1.137 - w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
1.138 - w.write("}\n");
1.139 - }
1.140 - w.append("}\n");
1.141 - } finally {
1.142 - w.close();
1.143 - }
1.144 - } catch (IOException ex) {
1.145 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
1.146 - return false;
1.147 + boolean ok = true;
1.148 + for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
1.149 + if (!processModel(e)) {
1.150 + ok = false;
1.151 }
1.152 }
1.153 - return true;
1.154 + for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
1.155 + if (!processPage(e)) {
1.156 + ok = false;
1.157 + }
1.158 + }
1.159 + if (roundEnv.processingOver()) {
1.160 + models.clear();
1.161 + for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
1.162 + TypeElement te = (TypeElement)entry.getKey();
1.163 + String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
1.164 + Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
1.165 + if (finalElem == null) {
1.166 + continue;
1.167 + }
1.168 + Prprt[] props;
1.169 + Model m = finalElem.getAnnotation(Model.class);
1.170 + if (m != null) {
1.171 + props = Prprt.wrap(processingEnv, finalElem, m.properties());
1.172 + } else {
1.173 + Page p = finalElem.getAnnotation(Page.class);
1.174 + props = Prprt.wrap(processingEnv, finalElem, p.properties());
1.175 + }
1.176 + for (Prprt p : props) {
1.177 + boolean[] isModel = { false };
1.178 + boolean[] isEnum = { false };
1.179 + boolean[] isPrimitive = { false };
1.180 + String t = checkType(p, isModel, isEnum, isPrimitive);
1.181 + if (isEnum[0]) {
1.182 + continue;
1.183 + }
1.184 + if (isPrimitive[0]) {
1.185 + continue;
1.186 + }
1.187 + if (isModel[0]) {
1.188 + continue;
1.189 + }
1.190 + if ("java.lang.String".equals(t)) {
1.191 + continue;
1.192 + }
1.193 + error("The type " + t + " should be defined by @Model annotation", entry.getKey());
1.194 + }
1.195 + }
1.196 + verify.clear();
1.197 + }
1.198 + return ok;
1.199 }
1.200
1.201 private InputStream openStream(String pkg, String name) throws IOException {
1.202 @@ -158,6 +152,236 @@
1.203 }
1.204 }
1.205
1.206 + private void error(String msg, Element e) {
1.207 + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
1.208 + }
1.209 +
1.210 + private boolean processModel(Element e) {
1.211 + boolean ok = true;
1.212 + Model m = e.getAnnotation(Model.class);
1.213 + if (m == null) {
1.214 + return true;
1.215 + }
1.216 + String pkg = findPkgName(e);
1.217 + Writer w;
1.218 + String className = m.className();
1.219 + models.put(e, className);
1.220 + try {
1.221 + StringWriter body = new StringWriter();
1.222 + List<String> propsGetSet = new ArrayList<>();
1.223 + List<String> functions = new ArrayList<>();
1.224 + Map<String, Collection<String>> propsDeps = new HashMap<>();
1.225 + Map<String, Collection<String>> functionDeps = new HashMap<>();
1.226 + Prprt[] props = createProps(e, m.properties());
1.227 +
1.228 + if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
1.229 + ok = false;
1.230 + }
1.231 + if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
1.232 + ok = false;
1.233 + }
1.234 + if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
1.235 + ok = false;
1.236 + }
1.237 + if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
1.238 + ok = false;
1.239 + }
1.240 + FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
1.241 + w = new OutputStreamWriter(java.openOutputStream());
1.242 + try {
1.243 + w.append("package " + pkg + ";\n");
1.244 + w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
1.245 + w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
1.246 + w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
1.247 + w.append("final class ").append(className).append(" implements Cloneable {\n");
1.248 + w.append(" private boolean locked;\n");
1.249 + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
1.250 + w.append(body.toString());
1.251 + w.append(" private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
1.252 + w.append(" public ").append(className).append("() {\n");
1.253 + w.append(" intKnckt();\n");
1.254 + w.append(" };\n");
1.255 + w.append(" private void intKnckt() {\n");
1.256 + w.append(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
1.257 + writeStringArray(propsGetSet, w);
1.258 + w.append(", ");
1.259 + writeStringArray(functions, w);
1.260 + w.append(" );\n");
1.261 + w.append(" };\n");
1.262 + w.append(" ").append(className).append("(Object json) {\n");
1.263 + int values = 0;
1.264 + for (int i = 0; i < propsGetSet.size(); i += 4) {
1.265 + Prprt p = findPrprt(props, propsGetSet.get(i));
1.266 + if (p == null) {
1.267 + continue;
1.268 + }
1.269 + values++;
1.270 + }
1.271 + w.append(" Object[] ret = new Object[" + values + "];\n");
1.272 + w.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
1.273 + for (int i = 0; i < propsGetSet.size(); i += 4) {
1.274 + Prprt p = findPrprt(props, propsGetSet.get(i));
1.275 + if (p == null) {
1.276 + continue;
1.277 + }
1.278 + w.append(" \"").append(propsGetSet.get(i)).append("\",\n");
1.279 + }
1.280 + w.append(" }, ret);\n");
1.281 + for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
1.282 + final String pn = propsGetSet.get(i);
1.283 + Prprt p = findPrprt(props, pn);
1.284 + if (p == null) {
1.285 + continue;
1.286 + }
1.287 + boolean[] isModel = { false };
1.288 + boolean[] isEnum = { false };
1.289 + boolean isPrimitive[] = { false };
1.290 + String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
1.291 + if (p.array()) {
1.292 + w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
1.293 + w.append(" for (Object e : ((Object[])ret[" + cnt + "])) {\n");
1.294 + if (isModel[0]) {
1.295 + w.append(" this.prop_").append(pn).append(".add(new ");
1.296 + w.append(type).append("(e));\n");
1.297 + } else if (isEnum[0]) {
1.298 + w.append(" this.prop_").append(pn);
1.299 + w.append(".add(");
1.300 + w.append(type).append(".valueOf((String)e));\n");
1.301 + } else {
1.302 + if (isPrimitive(type)) {
1.303 + w.append(" this.prop_").append(pn).append(".add(((Number)e).");
1.304 + w.append(type).append("Value());\n");
1.305 + } else {
1.306 + w.append(" this.prop_").append(pn).append(".add((");
1.307 + w.append(type).append(")e);\n");
1.308 + }
1.309 + }
1.310 + w.append(" }\n");
1.311 + w.append("}\n");
1.312 + } else {
1.313 + if (isEnum[0]) {
1.314 + w.append(" this.prop_").append(pn);
1.315 + w.append(" = ");
1.316 + w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
1.317 + } else if (isPrimitive(type)) {
1.318 + w.append(" this.prop_").append(pn);
1.319 + w.append(" = ((Number)").append("ret[" + cnt + "]).");
1.320 + w.append(type).append("Value();\n");
1.321 + } else {
1.322 + w.append(" this.prop_").append(pn);
1.323 + w.append(" = (").append(type).append(')');
1.324 + w.append("ret[" + cnt + "];\n");
1.325 + }
1.326 + }
1.327 + cnt++;
1.328 + }
1.329 + w.append(" intKnckt();\n");
1.330 + w.append(" };\n");
1.331 + writeToString(props, w);
1.332 + writeClone(className, props, w);
1.333 + w.append("}\n");
1.334 + } finally {
1.335 + w.close();
1.336 + }
1.337 + } catch (IOException ex) {
1.338 + error("Can't create " + className + ".java", e);
1.339 + return false;
1.340 + }
1.341 + return ok;
1.342 + }
1.343 +
1.344 + private boolean processPage(Element e) {
1.345 + boolean ok = true;
1.346 + Page p = e.getAnnotation(Page.class);
1.347 + if (p == null) {
1.348 + return true;
1.349 + }
1.350 + String pkg = findPkgName(e);
1.351 +
1.352 + ProcessPage pp;
1.353 + try (InputStream is = openStream(pkg, p.xhtml())) {
1.354 + pp = ProcessPage.readPage(is);
1.355 + is.close();
1.356 + } catch (IOException iOException) {
1.357 + error("Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
1.358 + ok = false;
1.359 + pp = null;
1.360 + }
1.361 + Writer w;
1.362 + String className = p.className();
1.363 + if (className.isEmpty()) {
1.364 + int indx = p.xhtml().indexOf('.');
1.365 + className = p.xhtml().substring(0, indx);
1.366 + }
1.367 + try {
1.368 + StringWriter body = new StringWriter();
1.369 + List<String> propsGetSet = new ArrayList<>();
1.370 + List<String> functions = new ArrayList<>();
1.371 + Map<String, Collection<String>> propsDeps = new HashMap<>();
1.372 + Map<String, Collection<String>> functionDeps = new HashMap<>();
1.373 +
1.374 + Prprt[] props = createProps(e, p.properties());
1.375 + if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
1.376 + ok = false;
1.377 + }
1.378 + if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
1.379 + ok = false;
1.380 + }
1.381 + if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
1.382 + ok = false;
1.383 + }
1.384 + if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
1.385 + ok = false;
1.386 + }
1.387 + if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) {
1.388 + ok = false;
1.389 + }
1.390 +
1.391 + FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
1.392 + w = new OutputStreamWriter(java.openOutputStream());
1.393 + try {
1.394 + w.append("package " + pkg + ";\n");
1.395 + w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
1.396 + w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
1.397 + w.append("public final class ").append(className).append(" {\n");
1.398 + w.append(" private boolean locked;\n");
1.399 + if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
1.400 + ok = false;
1.401 + } else {
1.402 + if (pp != null) for (String id : pp.ids()) {
1.403 + String tag = pp.tagNameForId(id);
1.404 + String type = type(tag);
1.405 + w.append(" ").append("public final ").
1.406 + append(type).append(' ').append(cnstnt(id)).append(" = new ").
1.407 + append(type).append("(\"").append(id).append("\");\n");
1.408 + }
1.409 + }
1.410 + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
1.411 + w.append(body.toString());
1.412 + if (!propsGetSet.isEmpty()) {
1.413 + w.write("public " + className + " applyBindings() {\n");
1.414 + w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
1.415 + w.write(className + ".class, this, ");
1.416 + writeStringArray(propsGetSet, w);
1.417 + w.append(", ");
1.418 + writeStringArray(functions, w);
1.419 + w.write(");\n return this;\n}\n");
1.420 +
1.421 + w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
1.422 + w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
1.423 + w.write("}\n");
1.424 + }
1.425 + w.append("}\n");
1.426 + } finally {
1.427 + w.close();
1.428 + }
1.429 + } catch (IOException ex) {
1.430 + error("Can't create " + className + ".java", e);
1.431 + return false;
1.432 + }
1.433 + return ok;
1.434 + }
1.435 +
1.436 private static String type(String tag) {
1.437 if (tag.equals("title")) {
1.438 return "Title";
1.439 @@ -178,12 +402,13 @@
1.440 }
1.441
1.442 private static String cnstnt(String id) {
1.443 - return id.toUpperCase(Locale.ENGLISH).replace('.', '_').replace('-', '_');
1.444 + return id.replace('.', '_').replace('-', '_');
1.445 }
1.446
1.447 private boolean initializeOnClick(
1.448 String className, TypeElement type, Writer w, ProcessPage pp
1.449 ) throws IOException {
1.450 + boolean ok = true;
1.451 TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
1.452 { //for (Element clazz : pe.getEnclosedElements()) {
1.453 // if (clazz.getKind() != ElementKind.CLASS) {
1.454 @@ -196,58 +421,27 @@
1.455 On oc = method.getAnnotation(On.class);
1.456 if (oc != null) {
1.457 for (String id : oc.id()) {
1.458 + if (pp == null) {
1.459 + error("id = " + id + " not found in HTML page.", method);
1.460 + ok = false;
1.461 + continue;
1.462 + }
1.463 if (pp.tagNameForId(id) == null) {
1.464 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
1.465 - return false;
1.466 + error("id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
1.467 + ok = false;
1.468 + continue;
1.469 }
1.470 ExecutableElement ee = (ExecutableElement)method;
1.471 - StringBuilder params = new StringBuilder();
1.472 - {
1.473 - boolean first = true;
1.474 - for (VariableElement ve : ee.getParameters()) {
1.475 - if (!first) {
1.476 - params.append(", ");
1.477 - }
1.478 - first = false;
1.479 - if (ve.asType() == stringType) {
1.480 - if (ve.getSimpleName().contentEquals("id")) {
1.481 - params.append('"').append(id).append('"');
1.482 - continue;
1.483 - }
1.484 - params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(ev, \"");
1.485 - params.append(ve.getSimpleName().toString());
1.486 - params.append("\")");
1.487 - continue;
1.488 - }
1.489 - if (processingEnv.getTypeUtils().getPrimitiveType(TypeKind.DOUBLE) == ve.asType()) {
1.490 - params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(ev, \"");
1.491 - params.append(ve.getSimpleName().toString());
1.492 - params.append("\")");
1.493 - continue;
1.494 - }
1.495 - String rn = ve.asType().toString();
1.496 - int last = rn.lastIndexOf('.');
1.497 - if (last >= 0) {
1.498 - rn = rn.substring(last + 1);
1.499 - }
1.500 - if (rn.equals(className)) {
1.501 - params.append(className).append(".this");
1.502 - continue;
1.503 - }
1.504 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
1.505 - "@On method can only accept String named 'id' or " + className + " arguments",
1.506 - ee
1.507 - );
1.508 - return false;
1.509 - }
1.510 - }
1.511 + CharSequence params = wrapParams(ee, id, className, "ev", null);
1.512 if (!ee.getModifiers().contains(Modifier.STATIC)) {
1.513 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
1.514 - return false;
1.515 + error("@On method has to be static", ee);
1.516 + ok = false;
1.517 + continue;
1.518 }
1.519 if (ee.getModifiers().contains(Modifier.PRIVATE)) {
1.520 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
1.521 - return false;
1.522 + error("@On method can't be private", ee);
1.523 + ok = false;
1.524 + continue;
1.525 }
1.526 w.append(" OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
1.527 append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
1.528 @@ -279,7 +473,7 @@
1.529
1.530
1.531 }
1.532 - return true;
1.533 + return ok;
1.534 }
1.535
1.536 @Override
1.537 @@ -293,8 +487,7 @@
1.538
1.539 Element cls = findClass(element);
1.540 Page p = cls.getAnnotation(Page.class);
1.541 - PackageElement pe = (PackageElement) cls.getEnclosingElement();
1.542 - String pkg = pe.getQualifiedName().toString();
1.543 + String pkg = findPkgName(cls);
1.544 ProcessPage pp;
1.545 try {
1.546 InputStream is = openStream(pkg, p.xhtml());
1.547 @@ -304,7 +497,7 @@
1.548 return Collections.emptyList();
1.549 }
1.550
1.551 - List<Completion> cc = new ArrayList<Completion>();
1.552 + List<Completion> cc = new ArrayList<>();
1.553 userText = userText.substring(1);
1.554 for (String id : pp.ids()) {
1.555 if (id.startsWith(userText)) {
1.556 @@ -325,44 +518,89 @@
1.557 return e.getEnclosingElement();
1.558 }
1.559
1.560 - private static void generateProperties(
1.561 - Writer w, Property[] properties, Collection<String> props,
1.562 - Map<String,Collection<String>> deps
1.563 + private boolean generateProperties(
1.564 + Element where,
1.565 + Writer w, Prprt[] properties,
1.566 + Collection<String> props,
1.567 + Map<String,Collection<String>> deps,
1.568 + Map<String,Collection<String>> functionDeps
1.569 ) throws IOException {
1.570 - for (Property p : properties) {
1.571 - final String tn = typeName(p);
1.572 - String[] gs = toGetSet(p.name(), tn);
1.573 + boolean ok = true;
1.574 + for (Prprt p : properties) {
1.575 + final String tn;
1.576 + tn = typeName(where, p);
1.577 + String[] gs = toGetSet(p.name(), tn, p.array());
1.578
1.579 - w.write("private " + tn + " prop_" + p.name() + ";\n");
1.580 - w.write("public " + tn + " " + gs[0] + "() {\n");
1.581 - w.write(" if (locked) throw new IllegalStateException();\n");
1.582 - w.write(" return prop_" + p.name() + ";\n");
1.583 - w.write("}\n");
1.584 - w.write("public void " + gs[1] + "(" + tn + " v) {\n");
1.585 - w.write(" if (locked) throw new IllegalStateException();\n");
1.586 - w.write(" prop_" + p.name() + " = v;\n");
1.587 - w.write(" if (ko != null) {\n");
1.588 - w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
1.589 - final Collection<String> dependants = deps.get(p.name());
1.590 - if (dependants != null) {
1.591 - for (String depProp : dependants) {
1.592 - w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
1.593 + if (p.array()) {
1.594 + w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
1.595 + + p.name() + "\"");
1.596 + Collection<String> dependants = deps.get(p.name());
1.597 + if (dependants != null) {
1.598 + for (String depProp : dependants) {
1.599 + w.write(", ");
1.600 + w.write('\"');
1.601 + w.write(depProp);
1.602 + w.write('\"');
1.603 + }
1.604 }
1.605 + w.write(")");
1.606 +
1.607 + dependants = functionDeps.get(p.name());
1.608 + if (dependants != null) {
1.609 + w.write(".onChange(new Runnable() { public void run() {\n");
1.610 + for (String call : dependants) {
1.611 + w.append(call);
1.612 + }
1.613 + w.write("}})");
1.614 + }
1.615 + w.write(";\n");
1.616 +
1.617 + w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
1.618 + w.write(" if (locked) throw new IllegalStateException();\n");
1.619 + w.write(" prop_" + p.name() + ".assign(ko);\n");
1.620 + w.write(" return prop_" + p.name() + ";\n");
1.621 + w.write("}\n");
1.622 + } else {
1.623 + w.write("private " + tn + " prop_" + p.name() + ";\n");
1.624 + w.write("public " + tn + " " + gs[0] + "() {\n");
1.625 + w.write(" if (locked) throw new IllegalStateException();\n");
1.626 + w.write(" return prop_" + p.name() + ";\n");
1.627 + w.write("}\n");
1.628 + w.write("public void " + gs[1] + "(" + tn + " v) {\n");
1.629 + w.write(" if (locked) throw new IllegalStateException();\n");
1.630 + w.write(" prop_" + p.name() + " = v;\n");
1.631 + w.write(" if (ko != null) {\n");
1.632 + w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
1.633 + Collection<String> dependants = deps.get(p.name());
1.634 + if (dependants != null) {
1.635 + for (String depProp : dependants) {
1.636 + w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
1.637 + }
1.638 + }
1.639 + w.write(" }\n");
1.640 + dependants = functionDeps.get(p.name());
1.641 + if (dependants != null) {
1.642 + for (String call : dependants) {
1.643 + w.append(call);
1.644 + }
1.645 + }
1.646 + w.write("}\n");
1.647 }
1.648 - w.write(" }\n");
1.649 - w.write("}\n");
1.650
1.651 props.add(p.name());
1.652 props.add(gs[2]);
1.653 props.add(gs[3]);
1.654 props.add(gs[0]);
1.655 }
1.656 + return ok;
1.657 }
1.658
1.659 private boolean generateComputedProperties(
1.660 - Writer w, Collection<? extends Element> arr, Collection<String> props,
1.661 + Writer w, Prprt[] fixedProps,
1.662 + Collection<? extends Element> arr, Collection<String> props,
1.663 Map<String,Collection<String>> deps
1.664 ) throws IOException {
1.665 + boolean ok = true;
1.666 for (Element e : arr) {
1.667 if (e.getKind() != ElementKind.METHOD) {
1.668 continue;
1.669 @@ -371,30 +609,43 @@
1.670 continue;
1.671 }
1.672 ExecutableElement ee = (ExecutableElement)e;
1.673 - final String tn = ee.getReturnType().toString();
1.674 + final TypeMirror rt = ee.getReturnType();
1.675 + final Types tu = processingEnv.getTypeUtils();
1.676 + TypeMirror ert = tu.erasure(rt);
1.677 + String tn = fqn(ert, ee);
1.678 + boolean array = false;
1.679 + if (tn.equals("java.util.List")) {
1.680 + array = true;
1.681 + }
1.682 +
1.683 final String sn = ee.getSimpleName().toString();
1.684 - String[] gs = toGetSet(sn, tn);
1.685 + String[] gs = toGetSet(sn, tn, array);
1.686
1.687 w.write("public " + tn + " " + gs[0] + "() {\n");
1.688 w.write(" if (locked) throw new IllegalStateException();\n");
1.689 int arg = 0;
1.690 for (VariableElement pe : ee.getParameters()) {
1.691 final String dn = pe.getSimpleName().toString();
1.692 - final String dt = pe.asType().toString();
1.693 - String[] call = toGetSet(dn, dt);
1.694 +
1.695 + if (!verifyPropName(pe, dn, fixedProps)) {
1.696 + ok = false;
1.697 + }
1.698 +
1.699 + final String dt = fqn(pe.asType(), ee);
1.700 + String[] call = toGetSet(dn, dt, false);
1.701 w.write(" " + dt + " arg" + (++arg) + " = ");
1.702 w.write(call[0] + "();\n");
1.703
1.704 Collection<String> depends = deps.get(dn);
1.705 if (depends == null) {
1.706 - depends = new LinkedHashSet<String>();
1.707 + depends = new LinkedHashSet<>();
1.708 deps.put(dn, depends);
1.709 }
1.710 depends.add(sn);
1.711 }
1.712 w.write(" try {\n");
1.713 w.write(" locked = true;\n");
1.714 - w.write(" return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "(");
1.715 + w.write(" return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
1.716 String sep = "";
1.717 for (int i = 1; i <= arg; i++) {
1.718 w.write(sep);
1.719 @@ -406,17 +657,17 @@
1.720 w.write(" locked = false;\n");
1.721 w.write(" }\n");
1.722 w.write("}\n");
1.723 -
1.724 +
1.725 props.add(e.getSimpleName().toString());
1.726 props.add(gs[2]);
1.727 props.add(null);
1.728 props.add(gs[0]);
1.729 }
1.730
1.731 - return true;
1.732 + return ok;
1.733 }
1.734
1.735 - private static String[] toGetSet(String name, String type) {
1.736 + private static String[] toGetSet(String name, String type, boolean array) {
1.737 String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
1.738 String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
1.739 if ("int".equals(type)) {
1.740 @@ -431,6 +682,14 @@
1.741 bck2brwsrType = "Z";
1.742 }
1.743 final String nu = n.replace('.', '_');
1.744 + if (array) {
1.745 + return new String[] {
1.746 + "get" + n,
1.747 + null,
1.748 + "get" + nu + "__Ljava_util_List_2",
1.749 + null
1.750 + };
1.751 + }
1.752 return new String[]{
1.753 pref + n,
1.754 "set" + n,
1.755 @@ -439,11 +698,701 @@
1.756 };
1.757 }
1.758
1.759 - private static String typeName(Property p) {
1.760 - try {
1.761 - return p.type().getName();
1.762 - } catch (MirroredTypeException ex) {
1.763 - return ex.getTypeMirror().toString();
1.764 + private String typeName(Element where, Prprt p) {
1.765 + String ret;
1.766 + boolean[] isModel = { false };
1.767 + boolean[] isEnum = { false };
1.768 + boolean isPrimitive[] = { false };
1.769 + ret = checkType(p, isModel, isEnum, isPrimitive);
1.770 + if (p.array()) {
1.771 + String bt = findBoxedType(ret);
1.772 + if (bt != null) {
1.773 + return bt;
1.774 + }
1.775 + }
1.776 + return ret;
1.777 + }
1.778 +
1.779 + private static String findBoxedType(String ret) {
1.780 + if (ret.equals("boolean")) {
1.781 + return Boolean.class.getName();
1.782 + }
1.783 + if (ret.equals("byte")) {
1.784 + return Byte.class.getName();
1.785 + }
1.786 + if (ret.equals("short")) {
1.787 + return Short.class.getName();
1.788 + }
1.789 + if (ret.equals("char")) {
1.790 + return Character.class.getName();
1.791 + }
1.792 + if (ret.equals("int")) {
1.793 + return Integer.class.getName();
1.794 + }
1.795 + if (ret.equals("long")) {
1.796 + return Long.class.getName();
1.797 + }
1.798 + if (ret.equals("float")) {
1.799 + return Float.class.getName();
1.800 + }
1.801 + if (ret.equals("double")) {
1.802 + return Double.class.getName();
1.803 + }
1.804 + return null;
1.805 + }
1.806 +
1.807 + private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
1.808 + StringBuilder sb = new StringBuilder();
1.809 + String sep = "";
1.810 + for (Prprt Prprt : existingProps) {
1.811 + if (Prprt.name().equals(propName)) {
1.812 + return true;
1.813 + }
1.814 + sb.append(sep);
1.815 + sb.append('"');
1.816 + sb.append(Prprt.name());
1.817 + sb.append('"');
1.818 + sep = ", ";
1.819 + }
1.820 + error(
1.821 + propName + " is not one of known properties: " + sb
1.822 + , e
1.823 + );
1.824 + return false;
1.825 + }
1.826 +
1.827 + private static String findPkgName(Element e) {
1.828 + for (;;) {
1.829 + if (e.getKind() == ElementKind.PACKAGE) {
1.830 + return ((PackageElement)e).getQualifiedName().toString();
1.831 + }
1.832 + e = e.getEnclosingElement();
1.833 }
1.834 }
1.835 +
1.836 + private boolean generateFunctions(
1.837 + Element clazz, StringWriter body, String className,
1.838 + List<? extends Element> enclosedElements, List<String> functions
1.839 + ) {
1.840 + for (Element m : enclosedElements) {
1.841 + if (m.getKind() != ElementKind.METHOD) {
1.842 + continue;
1.843 + }
1.844 + ExecutableElement e = (ExecutableElement)m;
1.845 + OnFunction onF = e.getAnnotation(OnFunction.class);
1.846 + if (onF == null) {
1.847 + continue;
1.848 + }
1.849 + if (!e.getModifiers().contains(Modifier.STATIC)) {
1.850 + error("@OnFunction method needs to be static", e);
1.851 + return false;
1.852 + }
1.853 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
1.854 + error("@OnFunction method cannot be private", e);
1.855 + return false;
1.856 + }
1.857 + if (e.getReturnType().getKind() != TypeKind.VOID) {
1.858 + error("@OnFunction method should return void", e);
1.859 + return false;
1.860 + }
1.861 + String n = e.getSimpleName().toString();
1.862 + body.append("private void ").append(n).append("(Object data, Object ev) {\n");
1.863 + body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
1.864 + body.append(wrapParams(e, null, className, "ev", "data"));
1.865 + body.append(");\n");
1.866 + body.append("}\n");
1.867 +
1.868 + functions.add(n);
1.869 + functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
1.870 + }
1.871 + return true;
1.872 + }
1.873 +
1.874 + private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
1.875 + Prprt[] properties, String className,
1.876 + Map<String, Collection<String>> functionDeps
1.877 + ) {
1.878 + for (Element m : clazz.getEnclosedElements()) {
1.879 + if (m.getKind() != ElementKind.METHOD) {
1.880 + continue;
1.881 + }
1.882 + ExecutableElement e = (ExecutableElement) m;
1.883 + OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
1.884 + if (onPC == null) {
1.885 + continue;
1.886 + }
1.887 + for (String pn : onPC.value()) {
1.888 + if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
1.889 + error("No Prprt named '" + pn + "' in the model", clazz);
1.890 + return false;
1.891 + }
1.892 + }
1.893 + if (!e.getModifiers().contains(Modifier.STATIC)) {
1.894 + error("@OnPrprtChange method needs to be static", e);
1.895 + return false;
1.896 + }
1.897 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
1.898 + error("@OnPrprtChange method cannot be private", e);
1.899 + return false;
1.900 + }
1.901 + if (e.getReturnType().getKind() != TypeKind.VOID) {
1.902 + error("@OnPrprtChange method should return void", e);
1.903 + return false;
1.904 + }
1.905 + String n = e.getSimpleName().toString();
1.906 +
1.907 +
1.908 + for (String pn : onPC.value()) {
1.909 + StringBuilder call = new StringBuilder();
1.910 + call.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
1.911 + call.append(wrapPropName(e, className, "name", pn));
1.912 + call.append(");\n");
1.913 +
1.914 + Collection<String> change = functionDeps.get(pn);
1.915 + if (change == null) {
1.916 + change = new ArrayList<>();
1.917 + functionDeps.put(pn, change);
1.918 + }
1.919 + change.add(call.toString());
1.920 + for (String dpn : findDerivedFrom(propDeps, pn)) {
1.921 + change = functionDeps.get(dpn);
1.922 + if (change == null) {
1.923 + change = new ArrayList<>();
1.924 + functionDeps.put(dpn, change);
1.925 + }
1.926 + change.add(call.toString());
1.927 + }
1.928 + }
1.929 + }
1.930 + return true;
1.931 + }
1.932 +
1.933 + private boolean generateReceive(
1.934 + Element clazz, StringWriter body, String className,
1.935 + List<? extends Element> enclosedElements, List<String> functions
1.936 + ) {
1.937 + for (Element m : enclosedElements) {
1.938 + if (m.getKind() != ElementKind.METHOD) {
1.939 + continue;
1.940 + }
1.941 + ExecutableElement e = (ExecutableElement)m;
1.942 + OnReceive onR = e.getAnnotation(OnReceive.class);
1.943 + if (onR == null) {
1.944 + continue;
1.945 + }
1.946 + if (!e.getModifiers().contains(Modifier.STATIC)) {
1.947 + error("@OnReceive method needs to be static", e);
1.948 + return false;
1.949 + }
1.950 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
1.951 + error("@OnReceive method cannot be private", e);
1.952 + return false;
1.953 + }
1.954 + if (e.getReturnType().getKind() != TypeKind.VOID) {
1.955 + error("@OnReceive method should return void", e);
1.956 + return false;
1.957 + }
1.958 + String modelClass = null;
1.959 + boolean expectsList = false;
1.960 + List<String> args = new ArrayList<>();
1.961 + {
1.962 + for (VariableElement ve : e.getParameters()) {
1.963 + TypeMirror modelType = null;
1.964 + if (ve.asType().toString().equals(className)) {
1.965 + args.add(className + ".this");
1.966 + } else if (isModel(ve.asType())) {
1.967 + modelType = ve.asType();
1.968 + } else if (ve.asType().getKind() == TypeKind.ARRAY) {
1.969 + modelType = ((ArrayType)ve.asType()).getComponentType();
1.970 + expectsList = true;
1.971 + }
1.972 + if (modelType != null) {
1.973 + if (modelClass != null) {
1.974 + error("There can be only one model class among arguments", e);
1.975 + } else {
1.976 + modelClass = modelType.toString();
1.977 + if (expectsList) {
1.978 + args.add("arr");
1.979 + } else {
1.980 + args.add("arr[0]");
1.981 + }
1.982 + }
1.983 + }
1.984 + }
1.985 + }
1.986 + if (modelClass == null) {
1.987 + error("The method needs to have one @Model class as parameter", e);
1.988 + }
1.989 + String n = e.getSimpleName().toString();
1.990 + body.append("public void ").append(n).append("(");
1.991 + StringBuilder assembleURL = new StringBuilder();
1.992 + String jsonpVarName = null;
1.993 + {
1.994 + String sep = "";
1.995 + boolean skipJSONP = onR.jsonp().isEmpty();
1.996 + for (String p : findParamNames(e, onR.url(), assembleURL)) {
1.997 + if (!skipJSONP && p.equals(onR.jsonp())) {
1.998 + skipJSONP = true;
1.999 + jsonpVarName = p;
1.1000 + continue;
1.1001 + }
1.1002 + body.append(sep);
1.1003 + body.append("String ").append(p);
1.1004 + sep = ", ";
1.1005 + }
1.1006 + if (!skipJSONP) {
1.1007 + error(
1.1008 + "Name of jsonp attribute ('" + onR.jsonp() +
1.1009 + "') is not used in url attribute '" + onR.url() + "'", e
1.1010 + );
1.1011 + }
1.1012 + }
1.1013 + body.append(") {\n");
1.1014 + body.append(" final Object[] result = { null };\n");
1.1015 + body.append(
1.1016 + " class ProcessResult implements Runnable {\n" +
1.1017 + " @Override\n" +
1.1018 + " public void run() {\n" +
1.1019 + " Object value = result[0];\n");
1.1020 + body.append(
1.1021 + " " + modelClass + "[] arr;\n");
1.1022 + body.append(
1.1023 + " if (value instanceof Object[]) {\n" +
1.1024 + " Object[] data = ((Object[])value);\n" +
1.1025 + " arr = new " + modelClass + "[data.length];\n" +
1.1026 + " for (int i = 0; i < data.length; i++) {\n" +
1.1027 + " arr[i] = new " + modelClass + "(data[i]);\n" +
1.1028 + " }\n" +
1.1029 + " } else {\n" +
1.1030 + " arr = new " + modelClass + "[1];\n" +
1.1031 + " arr[0] = new " + modelClass + "(value);\n" +
1.1032 + " }\n"
1.1033 + );
1.1034 + {
1.1035 + body.append(clazz.getSimpleName()).append(".").append(n).append("(");
1.1036 + String sep = "";
1.1037 + for (String arg : args) {
1.1038 + body.append(sep);
1.1039 + body.append(arg);
1.1040 + sep = ", ";
1.1041 + }
1.1042 + body.append(");\n");
1.1043 + }
1.1044 + body.append(
1.1045 + " }\n" +
1.1046 + " }\n"
1.1047 + );
1.1048 + body.append(" ProcessResult pr = new ProcessResult();\n");
1.1049 + if (jsonpVarName != null) {
1.1050 + body.append(" String ").append(jsonpVarName).
1.1051 + append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n");
1.1052 + }
1.1053 + body.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n ");
1.1054 + body.append(assembleURL);
1.1055 + body.append(", result, pr, ").append(jsonpVarName).append("\n );\n");
1.1056 +// body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
1.1057 +// body.append(wrapParams(e, null, className, "ev", "data"));
1.1058 +// body.append(");\n");
1.1059 + body.append("}\n");
1.1060 + }
1.1061 + return true;
1.1062 + }
1.1063 +
1.1064 + private CharSequence wrapParams(
1.1065 + ExecutableElement ee, String id, String className, String evName, String dataName
1.1066 + ) {
1.1067 + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
1.1068 + StringBuilder params = new StringBuilder();
1.1069 + boolean first = true;
1.1070 + for (VariableElement ve : ee.getParameters()) {
1.1071 + if (!first) {
1.1072 + params.append(", ");
1.1073 + }
1.1074 + first = false;
1.1075 + String toCall = null;
1.1076 + if (ve.asType() == stringType) {
1.1077 + if (ve.getSimpleName().contentEquals("id")) {
1.1078 + params.append('"').append(id).append('"');
1.1079 + continue;
1.1080 + }
1.1081 + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
1.1082 + }
1.1083 + if (ve.asType().getKind() == TypeKind.DOUBLE) {
1.1084 + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
1.1085 + }
1.1086 + if (ve.asType().getKind() == TypeKind.INT) {
1.1087 + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
1.1088 + }
1.1089 + if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
1.1090 + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
1.1091 + }
1.1092 +
1.1093 + if (toCall != null) {
1.1094 + params.append(toCall);
1.1095 + if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
1.1096 + params.append(dataName);
1.1097 + params.append(", null");
1.1098 + } else {
1.1099 + if (evName == null) {
1.1100 + final StringBuilder sb = new StringBuilder();
1.1101 + sb.append("Unexpected string parameter name.");
1.1102 + if (dataName != null) {
1.1103 + sb.append(" Try \"").append(dataName).append("\"");
1.1104 + }
1.1105 + error(sb.toString(), ee);
1.1106 + }
1.1107 + params.append(evName);
1.1108 + params.append(", \"");
1.1109 + params.append(ve.getSimpleName().toString());
1.1110 + params.append("\"");
1.1111 + }
1.1112 + params.append(")");
1.1113 + continue;
1.1114 + }
1.1115 + String rn = fqn(ve.asType(), ee);
1.1116 + int last = rn.lastIndexOf('.');
1.1117 + if (last >= 0) {
1.1118 + rn = rn.substring(last + 1);
1.1119 + }
1.1120 + if (rn.equals(className)) {
1.1121 + params.append(className).append(".this");
1.1122 + continue;
1.1123 + }
1.1124 + error(
1.1125 + "@On method can only accept String named 'id' or " + className + " arguments",
1.1126 + ee
1.1127 + );
1.1128 + }
1.1129 + return params;
1.1130 + }
1.1131 +
1.1132 +
1.1133 + private CharSequence wrapPropName(
1.1134 + ExecutableElement ee, String className, String propName, String propValue
1.1135 + ) {
1.1136 + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
1.1137 + StringBuilder params = new StringBuilder();
1.1138 + boolean first = true;
1.1139 + for (VariableElement ve : ee.getParameters()) {
1.1140 + if (!first) {
1.1141 + params.append(", ");
1.1142 + }
1.1143 + first = false;
1.1144 + if (ve.asType() == stringType) {
1.1145 + if (propName != null && ve.getSimpleName().contentEquals(propName)) {
1.1146 + params.append('"').append(propValue).append('"');
1.1147 + } else {
1.1148 + error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
1.1149 + }
1.1150 + continue;
1.1151 + }
1.1152 + String rn = fqn(ve.asType(), ee);
1.1153 + int last = rn.lastIndexOf('.');
1.1154 + if (last >= 0) {
1.1155 + rn = rn.substring(last + 1);
1.1156 + }
1.1157 + if (rn.equals(className)) {
1.1158 + params.append(className).append(".this");
1.1159 + continue;
1.1160 + }
1.1161 + error(
1.1162 + "@OnPrprtChange method can only accept String or " + className + " arguments",
1.1163 + ee);
1.1164 + }
1.1165 + return params;
1.1166 + }
1.1167 +
1.1168 + private boolean isModel(TypeMirror tm) {
1.1169 + final Element e = processingEnv.getTypeUtils().asElement(tm);
1.1170 + if (e == null) {
1.1171 + return false;
1.1172 + }
1.1173 + for (Element ch : e.getEnclosedElements()) {
1.1174 + if (ch.getKind() == ElementKind.METHOD) {
1.1175 + ExecutableElement ee = (ExecutableElement)ch;
1.1176 + if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
1.1177 + return true;
1.1178 + }
1.1179 + }
1.1180 + }
1.1181 + return models.values().contains(e.getSimpleName().toString());
1.1182 + }
1.1183 +
1.1184 + private void writeStringArray(List<String> strings, Writer w) throws IOException {
1.1185 + w.write("new String[] {\n");
1.1186 + String sep = "";
1.1187 + for (String n : strings) {
1.1188 + w.write(sep);
1.1189 + if (n == null) {
1.1190 + w.write(" null");
1.1191 + } else {
1.1192 + w.write(" \"" + n + "\"");
1.1193 + }
1.1194 + sep = ",\n";
1.1195 + }
1.1196 + w.write("\n }");
1.1197 + }
1.1198 +
1.1199 + private void writeToString(Prprt[] props, Writer w) throws IOException {
1.1200 + w.write(" public String toString() {\n");
1.1201 + w.write(" StringBuilder sb = new StringBuilder();\n");
1.1202 + w.write(" sb.append('{');\n");
1.1203 + String sep = "";
1.1204 + for (Prprt p : props) {
1.1205 + w.write(sep);
1.1206 + w.append(" sb.append(\"" + p.name() + ": \");\n");
1.1207 + w.append(" sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
1.1208 + w.append(p.name()).append("));\n");
1.1209 + sep = " sb.append(',');\n";
1.1210 + }
1.1211 + w.write(" sb.append('}');\n");
1.1212 + w.write(" return sb.toString();\n");
1.1213 + w.write(" }\n");
1.1214 + }
1.1215 + private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
1.1216 + w.write(" public " + className + " clone() {\n");
1.1217 + w.write(" " + className + " ret = new " + className + "();\n");
1.1218 + for (Prprt p : props) {
1.1219 + if (!p.array()) {
1.1220 + boolean isModel[] = { false };
1.1221 + boolean isEnum[] = { false };
1.1222 + boolean isPrimitive[] = { false };
1.1223 + checkType(p, isModel, isEnum, isPrimitive);
1.1224 + if (!isModel[0]) {
1.1225 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
1.1226 + continue;
1.1227 + }
1.1228 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
1.1229 + } else {
1.1230 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
1.1231 + }
1.1232 + }
1.1233 +
1.1234 + w.write(" return ret;\n");
1.1235 + w.write(" }\n");
1.1236 + }
1.1237 +
1.1238 + private String inPckName(Element e) {
1.1239 + StringBuilder sb = new StringBuilder();
1.1240 + while (e.getKind() != ElementKind.PACKAGE) {
1.1241 + if (sb.length() == 0) {
1.1242 + sb.append(e.getSimpleName());
1.1243 + } else {
1.1244 + sb.insert(0, '.');
1.1245 + sb.insert(0, e.getSimpleName());
1.1246 + }
1.1247 + e = e.getEnclosingElement();
1.1248 + }
1.1249 + return sb.toString();
1.1250 + }
1.1251 +
1.1252 + private String fqn(TypeMirror pt, Element relative) {
1.1253 + if (pt.getKind() == TypeKind.ERROR) {
1.1254 + final Elements eu = processingEnv.getElementUtils();
1.1255 + PackageElement pckg = eu.getPackageOf(relative);
1.1256 + return pckg.getQualifiedName() + "." + pt.toString();
1.1257 + }
1.1258 + return pt.toString();
1.1259 + }
1.1260 +
1.1261 + private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
1.1262 + TypeMirror tm;
1.1263 + try {
1.1264 + String ret = p.typeName(processingEnv);
1.1265 + TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
1.1266 + if (e == null) {
1.1267 + isModel[0] = true;
1.1268 + isEnum[0] = false;
1.1269 + isPrimitive[0] = false;
1.1270 + return ret;
1.1271 + }
1.1272 + tm = e.asType();
1.1273 + } catch (MirroredTypeException ex) {
1.1274 + tm = ex.getTypeMirror();
1.1275 + }
1.1276 + tm = processingEnv.getTypeUtils().erasure(tm);
1.1277 + isPrimitive[0] = tm.getKind().isPrimitive();
1.1278 + final Element e = processingEnv.getTypeUtils().asElement(tm);
1.1279 + final Model m = e == null ? null : e.getAnnotation(Model.class);
1.1280 +
1.1281 + String ret;
1.1282 + if (m != null) {
1.1283 + ret = findPkgName(e) + '.' + m.className();
1.1284 + isModel[0] = true;
1.1285 + models.put(e, m.className());
1.1286 + } else if (findModelForMthd(e)) {
1.1287 + ret = ((TypeElement)e).getQualifiedName().toString();
1.1288 + isModel[0] = true;
1.1289 + } else {
1.1290 + ret = tm.toString();
1.1291 + }
1.1292 + TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
1.1293 + enm = processingEnv.getTypeUtils().erasure(enm);
1.1294 + isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
1.1295 + return ret;
1.1296 + }
1.1297 +
1.1298 + private static boolean findModelForMthd(Element clazz) {
1.1299 + if (clazz == null) {
1.1300 + return false;
1.1301 + }
1.1302 + for (Element e : clazz.getEnclosedElements()) {
1.1303 + if (e.getKind() == ElementKind.METHOD) {
1.1304 + ExecutableElement ee = (ExecutableElement)e;
1.1305 + if (
1.1306 + ee.getSimpleName().contentEquals("modelFor") &&
1.1307 + ee.getParameters().isEmpty()
1.1308 + ) {
1.1309 + return true;
1.1310 + }
1.1311 + }
1.1312 + }
1.1313 + return false;
1.1314 + }
1.1315 +
1.1316 + private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
1.1317 + List<String> params = new ArrayList<>();
1.1318 +
1.1319 + for (int pos = 0; ;) {
1.1320 + int next = url.indexOf('{', pos);
1.1321 + if (next == -1) {
1.1322 + assembleURL.append('"')
1.1323 + .append(url.substring(pos))
1.1324 + .append('"');
1.1325 + return params;
1.1326 + }
1.1327 + int close = url.indexOf('}', next);
1.1328 + if (close == -1) {
1.1329 + error("Unbalanced '{' and '}' in " + url, e);
1.1330 + return params;
1.1331 + }
1.1332 + final String paramName = url.substring(next + 1, close);
1.1333 + params.add(paramName);
1.1334 + assembleURL.append('"')
1.1335 + .append(url.substring(pos, next))
1.1336 + .append("\" + ").append(paramName).append(" + ");
1.1337 + pos = close + 1;
1.1338 + }
1.1339 + }
1.1340 +
1.1341 + private static Prprt findPrprt(Prprt[] properties, String propName) {
1.1342 + for (Prprt p : properties) {
1.1343 + if (propName.equals(p.name())) {
1.1344 + return p;
1.1345 + }
1.1346 + }
1.1347 + return null;
1.1348 + }
1.1349 +
1.1350 + private boolean isPrimitive(String type) {
1.1351 + return
1.1352 + "int".equals(type) ||
1.1353 + "double".equals(type) ||
1.1354 + "long".equals(type) ||
1.1355 + "short".equals(type) ||
1.1356 + "byte".equals(type) ||
1.1357 + "float".equals(type);
1.1358 + }
1.1359 +
1.1360 + private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
1.1361 + Set<String> names = new HashSet<>();
1.1362 + for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
1.1363 + if (e.getValue().contains(derivedProp)) {
1.1364 + names.add(e.getKey());
1.1365 + }
1.1366 + }
1.1367 + return names;
1.1368 + }
1.1369 +
1.1370 + private Prprt[] createProps(Element e, Property[] arr) {
1.1371 + Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
1.1372 + Prprt[] prev = verify.put(e, ret);
1.1373 + if (prev != null) {
1.1374 + error("Two sets of properties for ", e);
1.1375 + }
1.1376 + return ret;
1.1377 + }
1.1378 +
1.1379 + private static class Prprt {
1.1380 + private final Element e;
1.1381 + private final AnnotationMirror tm;
1.1382 + private final Property p;
1.1383 +
1.1384 + public Prprt(Element e, AnnotationMirror tm, Property p) {
1.1385 + this.e = e;
1.1386 + this.tm = tm;
1.1387 + this.p = p;
1.1388 + }
1.1389 +
1.1390 + String name() {
1.1391 + return p.name();
1.1392 + }
1.1393 +
1.1394 + boolean array() {
1.1395 + return p.array();
1.1396 + }
1.1397 +
1.1398 + String typeName(ProcessingEnvironment env) {
1.1399 + try {
1.1400 + return p.type().getName();
1.1401 + } catch (AnnotationTypeMismatchException ex) {
1.1402 + for (Object v : getAnnoValues(env)) {
1.1403 + String s = v.toString().replace(" ", "");
1.1404 + if (s.startsWith("type=") && s.endsWith(".class")) {
1.1405 + return s.substring(5, s.length() - 6);
1.1406 + }
1.1407 + }
1.1408 + throw ex;
1.1409 + }
1.1410 + }
1.1411 +
1.1412 +
1.1413 + static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
1.1414 + if (arr.length == 0) {
1.1415 + return new Prprt[0];
1.1416 + }
1.1417 +
1.1418 + if (e.getKind() != ElementKind.CLASS) {
1.1419 + throw new IllegalStateException("" + e.getKind());
1.1420 + }
1.1421 + TypeElement te = (TypeElement)e;
1.1422 + List<? extends AnnotationValue> val = null;
1.1423 + for (AnnotationMirror an : te.getAnnotationMirrors()) {
1.1424 + for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
1.1425 + if (entry.getKey().getSimpleName().contentEquals("properties")) {
1.1426 + val = (List)entry.getValue().getValue();
1.1427 + break;
1.1428 + }
1.1429 + }
1.1430 + }
1.1431 + if (val == null || val.size() != arr.length) {
1.1432 + pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
1.1433 + return new Prprt[0];
1.1434 + }
1.1435 + Prprt[] ret = new Prprt[arr.length];
1.1436 + BIG: for (int i = 0; i < ret.length; i++) {
1.1437 + AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
1.1438 + ret[i] = new Prprt(e, am, arr[i]);
1.1439 +
1.1440 + }
1.1441 + return ret;
1.1442 + }
1.1443 +
1.1444 + private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
1.1445 + try {
1.1446 + Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
1.1447 + Method m = trees.getMethod("instance", ProcessingEnvironment.class);
1.1448 + Object instance = m.invoke(null, pe);
1.1449 + m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
1.1450 + Object path = m.invoke(instance, e, tm);
1.1451 + m = path.getClass().getMethod("getLeaf");
1.1452 + Object leaf = m.invoke(path);
1.1453 + m = leaf.getClass().getMethod("getArguments");
1.1454 + return (List)m.invoke(leaf);
1.1455 + } catch (Exception ex) {
1.1456 + return Collections.emptyList();
1.1457 + }
1.1458 + }
1.1459 + }
1.1460 +
1.1461 }