1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Sun Apr 07 22:32:41 2013 +0200
1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Mon Apr 08 12:12:42 2013 +0200
1.3 @@ -26,6 +26,7 @@
1.4 import java.util.Collection;
1.5 import java.util.Collections;
1.6 import java.util.HashMap;
1.7 +import java.util.HashSet;
1.8 import java.util.LinkedHashSet;
1.9 import java.util.List;
1.10 import java.util.Map;
1.11 @@ -60,6 +61,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.OnPropertyChange;
1.16 import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
1.17 import org.apidesign.bck2brwsr.htmlpage.api.Page;
1.18 import org.apidesign.bck2brwsr.htmlpage.api.Property;
1.19 @@ -76,6 +78,7 @@
1.20 "org.apidesign.bck2brwsr.htmlpage.api.Page",
1.21 "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
1.22 "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
1.23 + "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange",
1.24 "org.apidesign.bck2brwsr.htmlpage.api.On"
1.25 })
1.26 public final class PageProcessor extends AbstractProcessor {
1.27 @@ -128,10 +131,14 @@
1.28 List<String> propsGetSet = new ArrayList<>();
1.29 List<String> functions = new ArrayList<>();
1.30 Map<String, Collection<String>> propsDeps = new HashMap<>();
1.31 + Map<String, Collection<String>> functionDeps = new HashMap<>();
1.32 if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
1.33 ok = false;
1.34 }
1.35 - if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps)) {
1.36 + if (!generateOnChange(e, propsDeps, m.properties(), className, functionDeps)) {
1.37 + ok = false;
1.38 + }
1.39 + if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps, functionDeps)) {
1.40 ok = false;
1.41 }
1.42 if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
1.43 @@ -265,10 +272,14 @@
1.44 List<String> propsGetSet = new ArrayList<>();
1.45 List<String> functions = new ArrayList<>();
1.46 Map<String, Collection<String>> propsDeps = new HashMap<>();
1.47 + Map<String, Collection<String>> functionDeps = new HashMap<>();
1.48 if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
1.49 ok = false;
1.50 }
1.51 - if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps)) {
1.52 + if (!generateOnChange(e, propsDeps, p.properties(), className, functionDeps)) {
1.53 + ok = false;
1.54 + }
1.55 + if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps, functionDeps)) {
1.56 ok = false;
1.57 }
1.58 if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
1.59 @@ -461,7 +472,9 @@
1.60 private boolean generateProperties(
1.61 Element where,
1.62 Writer w, Property[] properties,
1.63 - Collection<String> props, Map<String,Collection<String>> deps
1.64 + Collection<String> props,
1.65 + Map<String,Collection<String>> deps,
1.66 + Map<String,Collection<String>> functionDeps
1.67 ) throws IOException {
1.68 boolean ok = true;
1.69 for (Property p : properties) {
1.70 @@ -498,13 +511,19 @@
1.71 w.write(" prop_" + p.name() + " = v;\n");
1.72 w.write(" if (ko != null) {\n");
1.73 w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
1.74 - final Collection<String> dependants = deps.get(p.name());
1.75 + Collection<String> dependants = deps.get(p.name());
1.76 if (dependants != null) {
1.77 for (String depProp : dependants) {
1.78 w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
1.79 }
1.80 }
1.81 w.write(" }\n");
1.82 + dependants = functionDeps.get(p.name());
1.83 + if (dependants != null) {
1.84 + for (String call : dependants) {
1.85 + w.append(call);
1.86 + }
1.87 + }
1.88 w.write("}\n");
1.89 }
1.90
1.91 @@ -744,6 +763,68 @@
1.92 return true;
1.93 }
1.94
1.95 + private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
1.96 + Property[] properties, String className,
1.97 + Map<String, Collection<String>> functionDeps
1.98 + ) {
1.99 + for (Element m : clazz.getEnclosedElements()) {
1.100 + if (m.getKind() != ElementKind.METHOD) {
1.101 + continue;
1.102 + }
1.103 + ExecutableElement e = (ExecutableElement) m;
1.104 + OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
1.105 + if (onPC == null) {
1.106 + continue;
1.107 + }
1.108 + for (String pn : onPC.value()) {
1.109 + if (findProperty(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
1.110 + err().printMessage(Diagnostic.Kind.ERROR, "No property named '" + pn + "' in the model");
1.111 + return false;
1.112 + }
1.113 + }
1.114 + if (!e.getModifiers().contains(Modifier.STATIC)) {
1.115 + err().printMessage(
1.116 + Diagnostic.Kind.ERROR, "@OnPropertyChange method needs to be static", e);
1.117 + return false;
1.118 + }
1.119 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
1.120 + err().printMessage(
1.121 + Diagnostic.Kind.ERROR, "@OnPropertyChange method cannot be private", e);
1.122 + return false;
1.123 + }
1.124 + if (e.getReturnType().getKind() != TypeKind.VOID) {
1.125 + err().printMessage(
1.126 + Diagnostic.Kind.ERROR, "@OnPropertyChange method should return void", e);
1.127 + return false;
1.128 + }
1.129 + String n = e.getSimpleName().toString();
1.130 +
1.131 +
1.132 + for (String pn : onPC.value()) {
1.133 + StringBuilder call = new StringBuilder();
1.134 + call.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
1.135 + call.append(wrapPropName(e, className, "name", pn));
1.136 + call.append(");\n");
1.137 +
1.138 + Collection<String> change = functionDeps.get(pn);
1.139 + if (change == null) {
1.140 + change = new ArrayList<>();
1.141 + functionDeps.put(pn, change);
1.142 + }
1.143 + change.add(call.toString());
1.144 + for (String dpn : findDerivedFrom(propDeps, pn)) {
1.145 + change = functionDeps.get(dpn);
1.146 + if (change == null) {
1.147 + change = new ArrayList<>();
1.148 + functionDeps.put(dpn, change);
1.149 + }
1.150 + change.add(call.toString());
1.151 + }
1.152 + }
1.153 + }
1.154 + return true;
1.155 + }
1.156 +
1.157 private boolean generateReceive(
1.158 Element clazz, StringWriter body, String className,
1.159 List<? extends Element> enclosedElements, List<String> functions
1.160 @@ -898,6 +979,14 @@
1.161 params.append(dataName);
1.162 params.append(", null");
1.163 } else {
1.164 + if (evName == null) {
1.165 + final StringBuilder sb = new StringBuilder();
1.166 + sb.append("Unexpected string parameter name.");
1.167 + if (dataName != null) {
1.168 + sb.append(" Try \"").append(dataName).append("\"");
1.169 + }
1.170 + err().printMessage(Diagnostic.Kind.ERROR, sb.toString(), ee);
1.171 + }
1.172 params.append(evName);
1.173 params.append(", \"");
1.174 params.append(ve.getSimpleName().toString());
1.175 @@ -923,6 +1012,42 @@
1.176 return params;
1.177 }
1.178
1.179 +
1.180 + private CharSequence wrapPropName(
1.181 + ExecutableElement ee, String className, String propName, String propValue
1.182 + ) {
1.183 + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
1.184 + StringBuilder params = new StringBuilder();
1.185 + boolean first = true;
1.186 + for (VariableElement ve : ee.getParameters()) {
1.187 + if (!first) {
1.188 + params.append(", ");
1.189 + }
1.190 + first = false;
1.191 + if (ve.asType() == stringType) {
1.192 + if (propName != null && ve.getSimpleName().contentEquals(propName)) {
1.193 + params.append('"').append(propValue).append('"');
1.194 + } else {
1.195 + err().printMessage(Diagnostic.Kind.ERROR, "Unexpected string parameter name. Try \"" + propName + "\".");
1.196 + }
1.197 + continue;
1.198 + }
1.199 + String rn = fqn(ve.asType(), ee);
1.200 + int last = rn.lastIndexOf('.');
1.201 + if (last >= 0) {
1.202 + rn = rn.substring(last + 1);
1.203 + }
1.204 + if (rn.equals(className)) {
1.205 + params.append(className).append(".this");
1.206 + continue;
1.207 + }
1.208 + err().printMessage(Diagnostic.Kind.ERROR,
1.209 + "@OnPropertyChange method can only accept String or " + className + " arguments",
1.210 + ee);
1.211 + }
1.212 + return params;
1.213 + }
1.214 +
1.215 private boolean isModel(TypeMirror tm) {
1.216 final Element e = processingEnv.getTypeUtils().asElement(tm);
1.217 if (e == null) {
1.218 @@ -1078,4 +1203,14 @@
1.219 "byte".equals(type) ||
1.220 "float".equals(type);
1.221 }
1.222 +
1.223 + private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
1.224 + Set<String> names = new HashSet<>();
1.225 + for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
1.226 + if (e.getValue().contains(derivedProp)) {
1.227 + names.add(e.getKey());
1.228 + }
1.229 + }
1.230 + return names;
1.231 + }
1.232 }