1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java Fri Apr 19 11:11:07 2013 +0200
1.3 @@ -0,0 +1,1162 @@
1.4 +/**
1.5 + * HTML via Java(tm) Language Bindings
1.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
1.7 + *
1.8 + * This program is free software: you can redistribute it and/or modify
1.9 + * it under the terms of the GNU General Public License as published by
1.10 + * the Free Software Foundation, version 2 of the License.
1.11 + *
1.12 + * This program is distributed in the hope that it will be useful,
1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.15 + * GNU General Public License for more details. apidesign.org
1.16 + * designates this particular file as subject to the
1.17 + * "Classpath" exception as provided by apidesign.org
1.18 + * in the License file that accompanied this code.
1.19 + *
1.20 + * You should have received a copy of the GNU General Public License
1.21 + * along with this program. Look for COPYING file in the top folder.
1.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
1.23 + */
1.24 +package org.apidesign.html.json.impl;
1.25 +
1.26 +import java.io.IOException;
1.27 +import java.io.InputStream;
1.28 +import java.io.OutputStreamWriter;
1.29 +import java.io.StringWriter;
1.30 +import java.io.Writer;
1.31 +import java.lang.annotation.AnnotationTypeMismatchException;
1.32 +import java.lang.annotation.IncompleteAnnotationException;
1.33 +import java.lang.reflect.Method;
1.34 +import java.util.ArrayList;
1.35 +import java.util.Collection;
1.36 +import java.util.Collections;
1.37 +import java.util.HashMap;
1.38 +import java.util.HashSet;
1.39 +import java.util.LinkedHashSet;
1.40 +import java.util.List;
1.41 +import java.util.Map;
1.42 +import java.util.Set;
1.43 +import java.util.WeakHashMap;
1.44 +import javax.annotation.processing.AbstractProcessor;
1.45 +import javax.annotation.processing.ProcessingEnvironment;
1.46 +import javax.annotation.processing.Processor;
1.47 +import javax.annotation.processing.RoundEnvironment;
1.48 +import javax.annotation.processing.SupportedAnnotationTypes;
1.49 +import javax.lang.model.element.AnnotationMirror;
1.50 +import javax.lang.model.element.AnnotationValue;
1.51 +import javax.lang.model.element.Element;
1.52 +import javax.lang.model.element.ElementKind;
1.53 +import javax.lang.model.element.ExecutableElement;
1.54 +import javax.lang.model.element.Modifier;
1.55 +import javax.lang.model.element.PackageElement;
1.56 +import javax.lang.model.element.TypeElement;
1.57 +import javax.lang.model.element.VariableElement;
1.58 +import javax.lang.model.type.ArrayType;
1.59 +import javax.lang.model.type.MirroredTypeException;
1.60 +import javax.lang.model.type.TypeKind;
1.61 +import javax.lang.model.type.TypeMirror;
1.62 +import javax.lang.model.util.Elements;
1.63 +import javax.lang.model.util.Types;
1.64 +import javax.tools.Diagnostic;
1.65 +import javax.tools.FileObject;
1.66 +import javax.tools.StandardLocation;
1.67 +import net.java.html.json.ComputedProperty;
1.68 +import net.java.html.json.Model;
1.69 +import net.java.html.json.Function;
1.70 +import net.java.html.json.OnPropertyChange;
1.71 +import net.java.html.json.OnReceive;
1.72 +import net.java.html.json.Property;
1.73 +import org.openide.util.lookup.ServiceProvider;
1.74 +
1.75 +/** Annotation processor to process an XHTML page and generate appropriate
1.76 + * "id" file.
1.77 + *
1.78 + * @author Jaroslav Tulach <jtulach@netbeans.org>
1.79 + */
1.80 +@ServiceProvider(service=Processor.class)
1.81 +@SupportedAnnotationTypes({
1.82 + "net.java.html.json.Model",
1.83 + "net.java.html.json.Function",
1.84 + "net.java.html.json.OnReceive",
1.85 + "net.java.html.json.OnPropertyChange",
1.86 + "net.java.html.json.ComputedProperty",
1.87 + "net.java.html.json.Property"
1.88 +})
1.89 +public final class ModelProcessor extends AbstractProcessor {
1.90 + private final Map<Element,String> models = new WeakHashMap<>();
1.91 + private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
1.92 + @Override
1.93 + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1.94 + boolean ok = true;
1.95 + for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
1.96 + if (!processModel(e)) {
1.97 + ok = false;
1.98 + }
1.99 + }
1.100 + if (roundEnv.processingOver()) {
1.101 + models.clear();
1.102 + for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
1.103 + TypeElement te = (TypeElement)entry.getKey();
1.104 + String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
1.105 + Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
1.106 + if (finalElem == null) {
1.107 + continue;
1.108 + }
1.109 + Prprt[] props;
1.110 + Model m = finalElem.getAnnotation(Model.class);
1.111 + if (m == null) {
1.112 + continue;
1.113 + }
1.114 + props = Prprt.wrap(processingEnv, finalElem, m.properties());
1.115 + for (Prprt p : props) {
1.116 + boolean[] isModel = { false };
1.117 + boolean[] isEnum = { false };
1.118 + boolean[] isPrimitive = { false };
1.119 + String t = checkType(p, isModel, isEnum, isPrimitive);
1.120 + if (isEnum[0]) {
1.121 + continue;
1.122 + }
1.123 + if (isPrimitive[0]) {
1.124 + continue;
1.125 + }
1.126 + if (isModel[0]) {
1.127 + continue;
1.128 + }
1.129 + if ("java.lang.String".equals(t)) {
1.130 + continue;
1.131 + }
1.132 + error("The type " + t + " should be defined by @Model annotation", entry.getKey());
1.133 + }
1.134 + }
1.135 + verify.clear();
1.136 + }
1.137 + return ok;
1.138 + }
1.139 +
1.140 + private InputStream openStream(String pkg, String name) throws IOException {
1.141 + try {
1.142 + FileObject fo = processingEnv.getFiler().getResource(
1.143 + StandardLocation.SOURCE_PATH, pkg, name);
1.144 + return fo.openInputStream();
1.145 + } catch (IOException ex) {
1.146 + return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
1.147 + }
1.148 + }
1.149 +
1.150 + private void error(String msg, Element e) {
1.151 + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
1.152 + }
1.153 +
1.154 + private boolean processModel(Element e) {
1.155 + boolean ok = true;
1.156 + Model m = e.getAnnotation(Model.class);
1.157 + if (m == null) {
1.158 + return true;
1.159 + }
1.160 + String pkg = findPkgName(e);
1.161 + Writer w;
1.162 + String className = m.className();
1.163 + models.put(e, className);
1.164 + try {
1.165 + StringWriter body = new StringWriter();
1.166 + List<String> propsGetSet = new ArrayList<>();
1.167 + List<String> functions = new ArrayList<>();
1.168 + Map<String, Collection<String>> propsDeps = new HashMap<>();
1.169 + Map<String, Collection<String>> functionDeps = new HashMap<>();
1.170 + Prprt[] props = createProps(e, m.properties());
1.171 +
1.172 + if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
1.173 + ok = false;
1.174 + }
1.175 + if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
1.176 + ok = false;
1.177 + }
1.178 + if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
1.179 + ok = false;
1.180 + }
1.181 + if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
1.182 + ok = false;
1.183 + }
1.184 + FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
1.185 + w = new OutputStreamWriter(java.openOutputStream());
1.186 + try {
1.187 + w.append("package " + pkg + ";\n");
1.188 + w.append("import net.java.html.json.*;\n");
1.189 + w.append("public final class ").append(className).append(" implements Cloneable {\n");
1.190 + w.append(" private boolean locked;\n");
1.191 + w.append(" private org.apidesign.html.json.impl.Bindings ko;\n");
1.192 + w.append(body.toString());
1.193 + w.append(" private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
1.194 + w.append(" public ").append(className).append("() {\n");
1.195 + w.append(" };\n");
1.196 + w.append(" private org.apidesign.html.json.impl.Bindings intKnckt() {\n");
1.197 + w.append(" if (ko != null) return ko;\n");
1.198 + w.append(" return ko = org.apidesign.html.json.impl.Bindings.apply(this, ");
1.199 + writeStringArray(propsGetSet, w);
1.200 + w.append(", ");
1.201 + writeStringArray(functions, w);
1.202 + w.append(" );\n");
1.203 + w.append(" };\n");
1.204 + w.append(" ").append(className).append("(Object json) {\n");
1.205 + int values = 0;
1.206 + for (int i = 0; i < propsGetSet.size(); i += 4) {
1.207 + Prprt p = findPrprt(props, propsGetSet.get(i));
1.208 + if (p == null) {
1.209 + continue;
1.210 + }
1.211 + values++;
1.212 + }
1.213 + w.append(" Object[] ret = new Object[" + values + "];\n");
1.214 + w.append(" org.apidesign.html.json.impl.JSON.extract(json, new String[] {\n");
1.215 + for (int i = 0; i < propsGetSet.size(); i += 4) {
1.216 + Prprt p = findPrprt(props, propsGetSet.get(i));
1.217 + if (p == null) {
1.218 + continue;
1.219 + }
1.220 + w.append(" \"").append(propsGetSet.get(i)).append("\",\n");
1.221 + }
1.222 + w.append(" }, ret);\n");
1.223 + for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
1.224 + final String pn = propsGetSet.get(i);
1.225 + Prprt p = findPrprt(props, pn);
1.226 + if (p == null) {
1.227 + continue;
1.228 + }
1.229 + boolean[] isModel = { false };
1.230 + boolean[] isEnum = { false };
1.231 + boolean isPrimitive[] = { false };
1.232 + String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
1.233 + if (p.array()) {
1.234 + w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
1.235 + w.append(" for (Object e : ((Object[])ret[" + cnt + "])) {\n");
1.236 + if (isModel[0]) {
1.237 + w.append(" this.prop_").append(pn).append(".add(new ");
1.238 + w.append(type).append("(e));\n");
1.239 + } else if (isEnum[0]) {
1.240 + w.append(" this.prop_").append(pn);
1.241 + w.append(".add(e == null ? null : ");
1.242 + w.append(type).append(".valueOf((String)e));\n");
1.243 + } else {
1.244 + if (isPrimitive(type)) {
1.245 + w.append(" this.prop_").append(pn).append(".add(((Number)e).");
1.246 + w.append(type).append("Value());\n");
1.247 + } else {
1.248 + w.append(" this.prop_").append(pn).append(".add((");
1.249 + w.append(type).append(")e);\n");
1.250 + }
1.251 + }
1.252 + w.append(" }\n");
1.253 + w.append("}\n");
1.254 + } else {
1.255 + if (isEnum[0]) {
1.256 + w.append(" this.prop_").append(pn);
1.257 + w.append(" = ret[" + cnt + "] == null ? null : ");
1.258 + w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
1.259 + } else if (isPrimitive(type)) {
1.260 + w.append(" this.prop_").append(pn);
1.261 + w.append(" = ((Number)").append("ret[" + cnt + "]).");
1.262 + w.append(type).append("Value();\n");
1.263 + } else {
1.264 + w.append(" this.prop_").append(pn);
1.265 + w.append(" = (").append(type).append(')');
1.266 + w.append("ret[" + cnt + "];\n");
1.267 + }
1.268 + }
1.269 + cnt++;
1.270 + }
1.271 + w.append(" };\n");
1.272 + writeToString(props, w);
1.273 + writeClone(className, props, w);
1.274 + w.append(" public Object koData() {\n");
1.275 + w.append(" return intKnckt().koData();\n");
1.276 + w.append(" }\n");
1.277 + w.append("}\n");
1.278 + } finally {
1.279 + w.close();
1.280 + }
1.281 + } catch (IOException ex) {
1.282 + error("Can't create " + className + ".java", e);
1.283 + return false;
1.284 + }
1.285 + return ok;
1.286 + }
1.287 +
1.288 + private boolean generateProperties(
1.289 + Element where,
1.290 + Writer w, Prprt[] properties,
1.291 + Collection<String> props,
1.292 + Map<String,Collection<String>> deps,
1.293 + Map<String,Collection<String>> functionDeps
1.294 + ) throws IOException {
1.295 + boolean ok = true;
1.296 + for (Prprt p : properties) {
1.297 + final String tn;
1.298 + tn = typeName(where, p);
1.299 + String[] gs = toGetSet(p.name(), tn, p.array());
1.300 +
1.301 + if (p.array()) {
1.302 + w.write("private org.apidesign.html.json.impl.JSONList<" + tn + "> prop_" + p.name() + " = new org.apidesign.html.json.impl.JSONList<" + tn + ">(\""
1.303 + + p.name() + "\"");
1.304 + Collection<String> dependants = deps.get(p.name());
1.305 + if (dependants != null) {
1.306 + for (String depProp : dependants) {
1.307 + w.write(", ");
1.308 + w.write('\"');
1.309 + w.write(depProp);
1.310 + w.write('\"');
1.311 + }
1.312 + }
1.313 + w.write(")");
1.314 +
1.315 + dependants = functionDeps.get(p.name());
1.316 + if (dependants != null) {
1.317 + w.write(".onChange(new Runnable() { public void run() {\n");
1.318 + for (String call : dependants) {
1.319 + w.append(call);
1.320 + }
1.321 + w.write("}})");
1.322 + }
1.323 + w.write(";\n");
1.324 +
1.325 + w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
1.326 + w.write(" if (locked) throw new IllegalStateException();\n");
1.327 + w.write(" prop_" + p.name() + ".assign(ko);\n");
1.328 + w.write(" return prop_" + p.name() + ";\n");
1.329 + w.write("}\n");
1.330 + } else {
1.331 + w.write("private " + tn + " prop_" + p.name() + ";\n");
1.332 + w.write("public " + tn + " " + gs[0] + "() {\n");
1.333 + w.write(" if (locked) throw new IllegalStateException();\n");
1.334 + w.write(" return prop_" + p.name() + ";\n");
1.335 + w.write("}\n");
1.336 + w.write("public void " + gs[1] + "(" + tn + " v) {\n");
1.337 + w.write(" if (locked) throw new IllegalStateException();\n");
1.338 + w.write(" prop_" + p.name() + " = v;\n");
1.339 + w.write(" if (ko != null) {\n");
1.340 + w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
1.341 + Collection<String> dependants = deps.get(p.name());
1.342 + if (dependants != null) {
1.343 + for (String depProp : dependants) {
1.344 + w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
1.345 + }
1.346 + }
1.347 + w.write(" }\n");
1.348 + dependants = functionDeps.get(p.name());
1.349 + if (dependants != null) {
1.350 + for (String call : dependants) {
1.351 + w.append(call);
1.352 + }
1.353 + }
1.354 + w.write("}\n");
1.355 + }
1.356 +
1.357 + props.add(p.name());
1.358 + props.add(gs[2]);
1.359 + props.add(gs[3]);
1.360 + props.add(gs[0]);
1.361 + }
1.362 + return ok;
1.363 + }
1.364 +
1.365 + private boolean generateComputedProperties(
1.366 + Writer w, Prprt[] fixedProps,
1.367 + Collection<? extends Element> arr, Collection<String> props,
1.368 + Map<String,Collection<String>> deps
1.369 + ) throws IOException {
1.370 + boolean ok = true;
1.371 + for (Element e : arr) {
1.372 + if (e.getKind() != ElementKind.METHOD) {
1.373 + continue;
1.374 + }
1.375 + if (e.getAnnotation(ComputedProperty.class) == null) {
1.376 + continue;
1.377 + }
1.378 + ExecutableElement ee = (ExecutableElement)e;
1.379 + final TypeMirror rt = ee.getReturnType();
1.380 + final Types tu = processingEnv.getTypeUtils();
1.381 + TypeMirror ert = tu.erasure(rt);
1.382 + String tn = fqn(ert, ee);
1.383 + boolean array = false;
1.384 + if (tn.equals("java.util.List")) {
1.385 + array = true;
1.386 + }
1.387 +
1.388 + final String sn = ee.getSimpleName().toString();
1.389 + String[] gs = toGetSet(sn, tn, array);
1.390 +
1.391 + w.write("public " + tn + " " + gs[0] + "() {\n");
1.392 + w.write(" if (locked) throw new IllegalStateException();\n");
1.393 + int arg = 0;
1.394 + for (VariableElement pe : ee.getParameters()) {
1.395 + final String dn = pe.getSimpleName().toString();
1.396 +
1.397 + if (!verifyPropName(pe, dn, fixedProps)) {
1.398 + ok = false;
1.399 + }
1.400 +
1.401 + final String dt = fqn(pe.asType(), ee);
1.402 + String[] call = toGetSet(dn, dt, false);
1.403 + w.write(" " + dt + " arg" + (++arg) + " = ");
1.404 + w.write(call[0] + "();\n");
1.405 +
1.406 + Collection<String> depends = deps.get(dn);
1.407 + if (depends == null) {
1.408 + depends = new LinkedHashSet<>();
1.409 + deps.put(dn, depends);
1.410 + }
1.411 + depends.add(sn);
1.412 + }
1.413 + w.write(" try {\n");
1.414 + w.write(" locked = true;\n");
1.415 + w.write(" return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
1.416 + String sep = "";
1.417 + for (int i = 1; i <= arg; i++) {
1.418 + w.write(sep);
1.419 + w.write("arg" + i);
1.420 + sep = ", ";
1.421 + }
1.422 + w.write(");\n");
1.423 + w.write(" } finally {\n");
1.424 + w.write(" locked = false;\n");
1.425 + w.write(" }\n");
1.426 + w.write("}\n");
1.427 +
1.428 + props.add(e.getSimpleName().toString());
1.429 + props.add(gs[2]);
1.430 + props.add(null);
1.431 + props.add(gs[0]);
1.432 + }
1.433 +
1.434 + return ok;
1.435 + }
1.436 +
1.437 + private static String[] toGetSet(String name, String type, boolean array) {
1.438 + String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
1.439 + String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
1.440 + if ("int".equals(type)) {
1.441 + bck2brwsrType = "I";
1.442 + }
1.443 + if ("double".equals(type)) {
1.444 + bck2brwsrType = "D";
1.445 + }
1.446 + String pref = "get";
1.447 + if ("boolean".equals(type)) {
1.448 + pref = "is";
1.449 + bck2brwsrType = "Z";
1.450 + }
1.451 + final String nu = n.replace('.', '_');
1.452 + if (array) {
1.453 + return new String[] {
1.454 + "get" + n,
1.455 + null,
1.456 + "get" + nu + "__Ljava_util_List_2",
1.457 + null
1.458 + };
1.459 + }
1.460 + return new String[]{
1.461 + pref + n,
1.462 + "set" + n,
1.463 + pref + nu + "__" + bck2brwsrType,
1.464 + "set" + nu + "__V" + bck2brwsrType
1.465 + };
1.466 + }
1.467 +
1.468 + private String typeName(Element where, Prprt p) {
1.469 + String ret;
1.470 + boolean[] isModel = { false };
1.471 + boolean[] isEnum = { false };
1.472 + boolean isPrimitive[] = { false };
1.473 + ret = checkType(p, isModel, isEnum, isPrimitive);
1.474 + if (p.array()) {
1.475 + String bt = findBoxedType(ret);
1.476 + if (bt != null) {
1.477 + return bt;
1.478 + }
1.479 + }
1.480 + return ret;
1.481 + }
1.482 +
1.483 + private static String findBoxedType(String ret) {
1.484 + if (ret.equals("boolean")) {
1.485 + return Boolean.class.getName();
1.486 + }
1.487 + if (ret.equals("byte")) {
1.488 + return Byte.class.getName();
1.489 + }
1.490 + if (ret.equals("short")) {
1.491 + return Short.class.getName();
1.492 + }
1.493 + if (ret.equals("char")) {
1.494 + return Character.class.getName();
1.495 + }
1.496 + if (ret.equals("int")) {
1.497 + return Integer.class.getName();
1.498 + }
1.499 + if (ret.equals("long")) {
1.500 + return Long.class.getName();
1.501 + }
1.502 + if (ret.equals("float")) {
1.503 + return Float.class.getName();
1.504 + }
1.505 + if (ret.equals("double")) {
1.506 + return Double.class.getName();
1.507 + }
1.508 + return null;
1.509 + }
1.510 +
1.511 + private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
1.512 + StringBuilder sb = new StringBuilder();
1.513 + String sep = "";
1.514 + for (Prprt Prprt : existingProps) {
1.515 + if (Prprt.name().equals(propName)) {
1.516 + return true;
1.517 + }
1.518 + sb.append(sep);
1.519 + sb.append('"');
1.520 + sb.append(Prprt.name());
1.521 + sb.append('"');
1.522 + sep = ", ";
1.523 + }
1.524 + error(
1.525 + propName + " is not one of known properties: " + sb
1.526 + , e
1.527 + );
1.528 + return false;
1.529 + }
1.530 +
1.531 + private static String findPkgName(Element e) {
1.532 + for (;;) {
1.533 + if (e.getKind() == ElementKind.PACKAGE) {
1.534 + return ((PackageElement)e).getQualifiedName().toString();
1.535 + }
1.536 + e = e.getEnclosingElement();
1.537 + }
1.538 + }
1.539 +
1.540 + private boolean generateFunctions(
1.541 + Element clazz, StringWriter body, String className,
1.542 + List<? extends Element> enclosedElements, List<String> functions
1.543 + ) {
1.544 + for (Element m : enclosedElements) {
1.545 + if (m.getKind() != ElementKind.METHOD) {
1.546 + continue;
1.547 + }
1.548 + ExecutableElement e = (ExecutableElement)m;
1.549 + Function onF = e.getAnnotation(Function.class);
1.550 + if (onF == null) {
1.551 + continue;
1.552 + }
1.553 + if (!e.getModifiers().contains(Modifier.STATIC)) {
1.554 + error("@OnFunction method needs to be static", e);
1.555 + return false;
1.556 + }
1.557 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
1.558 + error("@OnFunction method cannot be private", e);
1.559 + return false;
1.560 + }
1.561 + if (e.getReturnType().getKind() != TypeKind.VOID) {
1.562 + error("@OnFunction method should return void", e);
1.563 + return false;
1.564 + }
1.565 + String n = e.getSimpleName().toString();
1.566 + body.append("private void ").append(n).append("(Object data, Object ev) {\n");
1.567 + body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
1.568 + body.append(wrapParams(e, null, className, "ev", "data"));
1.569 + body.append(");\n");
1.570 + body.append("}\n");
1.571 +
1.572 + functions.add(n);
1.573 + functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
1.574 + }
1.575 + return true;
1.576 + }
1.577 +
1.578 + private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
1.579 + Prprt[] properties, String className,
1.580 + Map<String, Collection<String>> functionDeps
1.581 + ) {
1.582 + for (Element m : clazz.getEnclosedElements()) {
1.583 + if (m.getKind() != ElementKind.METHOD) {
1.584 + continue;
1.585 + }
1.586 + ExecutableElement e = (ExecutableElement) m;
1.587 + OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
1.588 + if (onPC == null) {
1.589 + continue;
1.590 + }
1.591 + for (String pn : onPC.value()) {
1.592 + if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
1.593 + error("No Prprt named '" + pn + "' in the model", clazz);
1.594 + return false;
1.595 + }
1.596 + }
1.597 + if (!e.getModifiers().contains(Modifier.STATIC)) {
1.598 + error("@OnPrprtChange method needs to be static", e);
1.599 + return false;
1.600 + }
1.601 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
1.602 + error("@OnPrprtChange method cannot be private", e);
1.603 + return false;
1.604 + }
1.605 + if (e.getReturnType().getKind() != TypeKind.VOID) {
1.606 + error("@OnPrprtChange method should return void", e);
1.607 + return false;
1.608 + }
1.609 + String n = e.getSimpleName().toString();
1.610 +
1.611 +
1.612 + for (String pn : onPC.value()) {
1.613 + StringBuilder call = new StringBuilder();
1.614 + call.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
1.615 + call.append(wrapPropName(e, className, "name", pn));
1.616 + call.append(");\n");
1.617 +
1.618 + Collection<String> change = functionDeps.get(pn);
1.619 + if (change == null) {
1.620 + change = new ArrayList<>();
1.621 + functionDeps.put(pn, change);
1.622 + }
1.623 + change.add(call.toString());
1.624 + for (String dpn : findDerivedFrom(propDeps, pn)) {
1.625 + change = functionDeps.get(dpn);
1.626 + if (change == null) {
1.627 + change = new ArrayList<>();
1.628 + functionDeps.put(dpn, change);
1.629 + }
1.630 + change.add(call.toString());
1.631 + }
1.632 + }
1.633 + }
1.634 + return true;
1.635 + }
1.636 +
1.637 + private boolean generateReceive(
1.638 + Element clazz, StringWriter body, String className,
1.639 + List<? extends Element> enclosedElements, List<String> functions
1.640 + ) {
1.641 + for (Element m : enclosedElements) {
1.642 + if (m.getKind() != ElementKind.METHOD) {
1.643 + continue;
1.644 + }
1.645 + ExecutableElement e = (ExecutableElement)m;
1.646 + OnReceive onR = e.getAnnotation(OnReceive.class);
1.647 + if (onR == null) {
1.648 + continue;
1.649 + }
1.650 + if (!e.getModifiers().contains(Modifier.STATIC)) {
1.651 + error("@OnReceive method needs to be static", e);
1.652 + return false;
1.653 + }
1.654 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
1.655 + error("@OnReceive method cannot be private", e);
1.656 + return false;
1.657 + }
1.658 + if (e.getReturnType().getKind() != TypeKind.VOID) {
1.659 + error("@OnReceive method should return void", e);
1.660 + return false;
1.661 + }
1.662 + String modelClass = null;
1.663 + boolean expectsList = false;
1.664 + List<String> args = new ArrayList<>();
1.665 + {
1.666 + for (VariableElement ve : e.getParameters()) {
1.667 + TypeMirror modelType = null;
1.668 + if (ve.asType().toString().equals(className)) {
1.669 + args.add(className + ".this");
1.670 + } else if (isModel(ve.asType())) {
1.671 + modelType = ve.asType();
1.672 + } else if (ve.asType().getKind() == TypeKind.ARRAY) {
1.673 + modelType = ((ArrayType)ve.asType()).getComponentType();
1.674 + expectsList = true;
1.675 + }
1.676 + if (modelType != null) {
1.677 + if (modelClass != null) {
1.678 + error("There can be only one model class among arguments", e);
1.679 + } else {
1.680 + modelClass = modelType.toString();
1.681 + if (expectsList) {
1.682 + args.add("arr");
1.683 + } else {
1.684 + args.add("arr[0]");
1.685 + }
1.686 + }
1.687 + }
1.688 + }
1.689 + }
1.690 + if (modelClass == null) {
1.691 + error("The method needs to have one @Model class as parameter", e);
1.692 + }
1.693 + String n = e.getSimpleName().toString();
1.694 + body.append("public void ").append(n).append("(");
1.695 + StringBuilder assembleURL = new StringBuilder();
1.696 + String jsonpVarName = null;
1.697 + {
1.698 + String sep = "";
1.699 + boolean skipJSONP = onR.jsonp().isEmpty();
1.700 + for (String p : findParamNames(e, onR.url(), assembleURL)) {
1.701 + if (!skipJSONP && p.equals(onR.jsonp())) {
1.702 + skipJSONP = true;
1.703 + jsonpVarName = p;
1.704 + continue;
1.705 + }
1.706 + body.append(sep);
1.707 + body.append("String ").append(p);
1.708 + sep = ", ";
1.709 + }
1.710 + if (!skipJSONP) {
1.711 + error(
1.712 + "Name of jsonp attribute ('" + onR.jsonp() +
1.713 + "') is not used in url attribute '" + onR.url() + "'", e
1.714 + );
1.715 + }
1.716 + }
1.717 + body.append(") {\n");
1.718 + body.append(" final Object[] result = { null };\n");
1.719 + body.append(
1.720 + " class ProcessResult implements Runnable {\n" +
1.721 + " @Override\n" +
1.722 + " public void run() {\n" +
1.723 + " Object value = result[0];\n");
1.724 + body.append(
1.725 + " " + modelClass + "[] arr;\n");
1.726 + body.append(
1.727 + " if (value instanceof Object[]) {\n" +
1.728 + " Object[] data = ((Object[])value);\n" +
1.729 + " arr = new " + modelClass + "[data.length];\n" +
1.730 + " for (int i = 0; i < data.length; i++) {\n" +
1.731 + " arr[i] = new " + modelClass + "(data[i]);\n" +
1.732 + " }\n" +
1.733 + " } else {\n" +
1.734 + " arr = new " + modelClass + "[1];\n" +
1.735 + " arr[0] = new " + modelClass + "(value);\n" +
1.736 + " }\n"
1.737 + );
1.738 + {
1.739 + body.append(clazz.getSimpleName()).append(".").append(n).append("(");
1.740 + String sep = "";
1.741 + for (String arg : args) {
1.742 + body.append(sep);
1.743 + body.append(arg);
1.744 + sep = ", ";
1.745 + }
1.746 + body.append(");\n");
1.747 + }
1.748 + body.append(
1.749 + " }\n" +
1.750 + " }\n"
1.751 + );
1.752 + body.append(" ProcessResult pr = new ProcessResult();\n");
1.753 + if (jsonpVarName != null) {
1.754 + body.append(" String ").append(jsonpVarName).
1.755 + append(" = org.apidesign.html.json.impl.JSON.createJSONP(result, pr);\n");
1.756 + }
1.757 + body.append(" org.apidesign.html.json.impl.JSON.loadJSON(\n ");
1.758 + body.append(assembleURL);
1.759 + body.append(", result, pr, ").append(jsonpVarName).append("\n );\n");
1.760 +// body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
1.761 +// body.append(wrapParams(e, null, className, "ev", "data"));
1.762 +// body.append(");\n");
1.763 + body.append("}\n");
1.764 + }
1.765 + return true;
1.766 + }
1.767 +
1.768 + private CharSequence wrapParams(
1.769 + ExecutableElement ee, String id, String className, String evName, String dataName
1.770 + ) {
1.771 + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
1.772 + StringBuilder params = new StringBuilder();
1.773 + boolean first = true;
1.774 + for (VariableElement ve : ee.getParameters()) {
1.775 + if (!first) {
1.776 + params.append(", ");
1.777 + }
1.778 + first = false;
1.779 + String toCall = null;
1.780 + if (ve.asType() == stringType) {
1.781 + if (ve.getSimpleName().contentEquals("id")) {
1.782 + params.append('"').append(id).append('"');
1.783 + continue;
1.784 + }
1.785 + toCall = "org.apidesign.html.json.impl.JSON.toString(";
1.786 + }
1.787 + if (ve.asType().getKind() == TypeKind.DOUBLE) {
1.788 + toCall = "org.apidesign.html.json.impl.JSON.toDouble(";
1.789 + }
1.790 + if (ve.asType().getKind() == TypeKind.INT) {
1.791 + toCall = "org.apidesign.html.json.impl.JSON.toInt(";
1.792 + }
1.793 + if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
1.794 + toCall = "org.apidesign.html.json.impl.JSON.toModel(" + ve.asType() + ".class, ";
1.795 + }
1.796 +
1.797 + if (toCall != null) {
1.798 + params.append(toCall);
1.799 + if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
1.800 + params.append(dataName);
1.801 + params.append(", null");
1.802 + } else {
1.803 + if (evName == null) {
1.804 + final StringBuilder sb = new StringBuilder();
1.805 + sb.append("Unexpected string parameter name.");
1.806 + if (dataName != null) {
1.807 + sb.append(" Try \"").append(dataName).append("\"");
1.808 + }
1.809 + error(sb.toString(), ee);
1.810 + }
1.811 + params.append(evName);
1.812 + params.append(", \"");
1.813 + params.append(ve.getSimpleName().toString());
1.814 + params.append("\"");
1.815 + }
1.816 + params.append(")");
1.817 + continue;
1.818 + }
1.819 + String rn = fqn(ve.asType(), ee);
1.820 + int last = rn.lastIndexOf('.');
1.821 + if (last >= 0) {
1.822 + rn = rn.substring(last + 1);
1.823 + }
1.824 + if (rn.equals(className)) {
1.825 + params.append(className).append(".this");
1.826 + continue;
1.827 + }
1.828 + error(
1.829 + "@On method can only accept String named 'id' or " + className + " arguments",
1.830 + ee
1.831 + );
1.832 + }
1.833 + return params;
1.834 + }
1.835 +
1.836 +
1.837 + private CharSequence wrapPropName(
1.838 + ExecutableElement ee, String className, String propName, String propValue
1.839 + ) {
1.840 + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
1.841 + StringBuilder params = new StringBuilder();
1.842 + boolean first = true;
1.843 + for (VariableElement ve : ee.getParameters()) {
1.844 + if (!first) {
1.845 + params.append(", ");
1.846 + }
1.847 + first = false;
1.848 + if (ve.asType() == stringType) {
1.849 + if (propName != null && ve.getSimpleName().contentEquals(propName)) {
1.850 + params.append('"').append(propValue).append('"');
1.851 + } else {
1.852 + error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
1.853 + }
1.854 + continue;
1.855 + }
1.856 + String rn = fqn(ve.asType(), ee);
1.857 + int last = rn.lastIndexOf('.');
1.858 + if (last >= 0) {
1.859 + rn = rn.substring(last + 1);
1.860 + }
1.861 + if (rn.equals(className)) {
1.862 + params.append(className).append(".this");
1.863 + continue;
1.864 + }
1.865 + error(
1.866 + "@OnPrprtChange method can only accept String or " + className + " arguments",
1.867 + ee);
1.868 + }
1.869 + return params;
1.870 + }
1.871 +
1.872 + private boolean isModel(TypeMirror tm) {
1.873 + final Element e = processingEnv.getTypeUtils().asElement(tm);
1.874 + if (e == null) {
1.875 + return false;
1.876 + }
1.877 + for (Element ch : e.getEnclosedElements()) {
1.878 + if (ch.getKind() == ElementKind.METHOD) {
1.879 + ExecutableElement ee = (ExecutableElement)ch;
1.880 + if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
1.881 + return true;
1.882 + }
1.883 + }
1.884 + }
1.885 + return models.values().contains(e.getSimpleName().toString());
1.886 + }
1.887 +
1.888 + private void writeStringArray(List<String> strings, Writer w) throws IOException {
1.889 + w.write("new String[] {\n");
1.890 + String sep = "";
1.891 + for (String n : strings) {
1.892 + w.write(sep);
1.893 + if (n == null) {
1.894 + w.write(" null");
1.895 + } else {
1.896 + w.write(" \"" + n + "\"");
1.897 + }
1.898 + sep = ",\n";
1.899 + }
1.900 + w.write("\n }");
1.901 + }
1.902 +
1.903 + private void writeToString(Prprt[] props, Writer w) throws IOException {
1.904 + w.write(" public String toString() {\n");
1.905 + w.write(" StringBuilder sb = new StringBuilder();\n");
1.906 + w.write(" sb.append('{');\n");
1.907 + String sep = "";
1.908 + for (Prprt p : props) {
1.909 + w.write(sep);
1.910 + w.append(" sb.append(\"" + p.name() + ": \");\n");
1.911 + w.append(" sb.append(org.apidesign.html.json.impl.JSON.toJSON(prop_");
1.912 + w.append(p.name()).append("));\n");
1.913 + sep = " sb.append(',');\n";
1.914 + }
1.915 + w.write(" sb.append('}');\n");
1.916 + w.write(" return sb.toString();\n");
1.917 + w.write(" }\n");
1.918 + }
1.919 + private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
1.920 + w.write(" public " + className + " clone() {\n");
1.921 + w.write(" " + className + " ret = new " + className + "();\n");
1.922 + for (Prprt p : props) {
1.923 + if (!p.array()) {
1.924 + boolean isModel[] = { false };
1.925 + boolean isEnum[] = { false };
1.926 + boolean isPrimitive[] = { false };
1.927 + checkType(p, isModel, isEnum, isPrimitive);
1.928 + if (!isModel[0]) {
1.929 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
1.930 + continue;
1.931 + }
1.932 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
1.933 + } else {
1.934 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
1.935 + }
1.936 + }
1.937 +
1.938 + w.write(" return ret;\n");
1.939 + w.write(" }\n");
1.940 + }
1.941 +
1.942 + private String inPckName(Element e) {
1.943 + StringBuilder sb = new StringBuilder();
1.944 + while (e.getKind() != ElementKind.PACKAGE) {
1.945 + if (sb.length() == 0) {
1.946 + sb.append(e.getSimpleName());
1.947 + } else {
1.948 + sb.insert(0, '.');
1.949 + sb.insert(0, e.getSimpleName());
1.950 + }
1.951 + e = e.getEnclosingElement();
1.952 + }
1.953 + return sb.toString();
1.954 + }
1.955 +
1.956 + private String fqn(TypeMirror pt, Element relative) {
1.957 + if (pt.getKind() == TypeKind.ERROR) {
1.958 + final Elements eu = processingEnv.getElementUtils();
1.959 + PackageElement pckg = eu.getPackageOf(relative);
1.960 + return pckg.getQualifiedName() + "." + pt.toString();
1.961 + }
1.962 + return pt.toString();
1.963 + }
1.964 +
1.965 + private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
1.966 + TypeMirror tm;
1.967 + try {
1.968 + String ret = p.typeName(processingEnv);
1.969 + TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
1.970 + if (e == null) {
1.971 + isModel[0] = true;
1.972 + isEnum[0] = false;
1.973 + isPrimitive[0] = false;
1.974 + return ret;
1.975 + }
1.976 + tm = e.asType();
1.977 + } catch (MirroredTypeException ex) {
1.978 + tm = ex.getTypeMirror();
1.979 + }
1.980 + tm = processingEnv.getTypeUtils().erasure(tm);
1.981 + isPrimitive[0] = tm.getKind().isPrimitive();
1.982 + final Element e = processingEnv.getTypeUtils().asElement(tm);
1.983 + final Model m = e == null ? null : e.getAnnotation(Model.class);
1.984 +
1.985 + String ret;
1.986 + if (m != null) {
1.987 + ret = findPkgName(e) + '.' + m.className();
1.988 + isModel[0] = true;
1.989 + models.put(e, m.className());
1.990 + } else if (findModelForMthd(e)) {
1.991 + ret = ((TypeElement)e).getQualifiedName().toString();
1.992 + isModel[0] = true;
1.993 + } else {
1.994 + ret = tm.toString();
1.995 + }
1.996 + TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
1.997 + enm = processingEnv.getTypeUtils().erasure(enm);
1.998 + isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
1.999 + return ret;
1.1000 + }
1.1001 +
1.1002 + private static boolean findModelForMthd(Element clazz) {
1.1003 + if (clazz == null) {
1.1004 + return false;
1.1005 + }
1.1006 + for (Element e : clazz.getEnclosedElements()) {
1.1007 + if (e.getKind() == ElementKind.METHOD) {
1.1008 + ExecutableElement ee = (ExecutableElement)e;
1.1009 + if (
1.1010 + ee.getSimpleName().contentEquals("modelFor") &&
1.1011 + ee.getParameters().isEmpty()
1.1012 + ) {
1.1013 + return true;
1.1014 + }
1.1015 + }
1.1016 + }
1.1017 + return false;
1.1018 + }
1.1019 +
1.1020 + private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
1.1021 + List<String> params = new ArrayList<>();
1.1022 +
1.1023 + for (int pos = 0; ;) {
1.1024 + int next = url.indexOf('{', pos);
1.1025 + if (next == -1) {
1.1026 + assembleURL.append('"')
1.1027 + .append(url.substring(pos))
1.1028 + .append('"');
1.1029 + return params;
1.1030 + }
1.1031 + int close = url.indexOf('}', next);
1.1032 + if (close == -1) {
1.1033 + error("Unbalanced '{' and '}' in " + url, e);
1.1034 + return params;
1.1035 + }
1.1036 + final String paramName = url.substring(next + 1, close);
1.1037 + params.add(paramName);
1.1038 + assembleURL.append('"')
1.1039 + .append(url.substring(pos, next))
1.1040 + .append("\" + ").append(paramName).append(" + ");
1.1041 + pos = close + 1;
1.1042 + }
1.1043 + }
1.1044 +
1.1045 + private static Prprt findPrprt(Prprt[] properties, String propName) {
1.1046 + for (Prprt p : properties) {
1.1047 + if (propName.equals(p.name())) {
1.1048 + return p;
1.1049 + }
1.1050 + }
1.1051 + return null;
1.1052 + }
1.1053 +
1.1054 + private boolean isPrimitive(String type) {
1.1055 + return
1.1056 + "int".equals(type) ||
1.1057 + "double".equals(type) ||
1.1058 + "long".equals(type) ||
1.1059 + "short".equals(type) ||
1.1060 + "byte".equals(type) ||
1.1061 + "float".equals(type);
1.1062 + }
1.1063 +
1.1064 + private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
1.1065 + Set<String> names = new HashSet<>();
1.1066 + for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
1.1067 + if (e.getValue().contains(derivedProp)) {
1.1068 + names.add(e.getKey());
1.1069 + }
1.1070 + }
1.1071 + return names;
1.1072 + }
1.1073 +
1.1074 + private Prprt[] createProps(Element e, Property[] arr) {
1.1075 + Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
1.1076 + Prprt[] prev = verify.put(e, ret);
1.1077 + if (prev != null) {
1.1078 + error("Two sets of properties for ", e);
1.1079 + }
1.1080 + return ret;
1.1081 + }
1.1082 +
1.1083 + private static class Prprt {
1.1084 + private final Element e;
1.1085 + private final AnnotationMirror tm;
1.1086 + private final Property p;
1.1087 +
1.1088 + public Prprt(Element e, AnnotationMirror tm, Property p) {
1.1089 + this.e = e;
1.1090 + this.tm = tm;
1.1091 + this.p = p;
1.1092 + }
1.1093 +
1.1094 + String name() {
1.1095 + return p.name();
1.1096 + }
1.1097 +
1.1098 + boolean array() {
1.1099 + return p.array();
1.1100 + }
1.1101 +
1.1102 + String typeName(ProcessingEnvironment env) {
1.1103 + try {
1.1104 + return p.type().getName();
1.1105 + } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) {
1.1106 + for (Object v : getAnnoValues(env)) {
1.1107 + String s = v.toString().replace(" ", "");
1.1108 + if (s.startsWith("type=") && s.endsWith(".class")) {
1.1109 + return s.substring(5, s.length() - 6);
1.1110 + }
1.1111 + }
1.1112 + throw ex;
1.1113 + }
1.1114 + }
1.1115 +
1.1116 +
1.1117 + static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
1.1118 + if (arr.length == 0) {
1.1119 + return new Prprt[0];
1.1120 + }
1.1121 +
1.1122 + if (e.getKind() != ElementKind.CLASS) {
1.1123 + throw new IllegalStateException("" + e.getKind());
1.1124 + }
1.1125 + TypeElement te = (TypeElement)e;
1.1126 + List<? extends AnnotationValue> val = null;
1.1127 + for (AnnotationMirror an : te.getAnnotationMirrors()) {
1.1128 + for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
1.1129 + if (entry.getKey().getSimpleName().contentEquals("properties")) {
1.1130 + val = (List)entry.getValue().getValue();
1.1131 + break;
1.1132 + }
1.1133 + }
1.1134 + }
1.1135 + if (val == null || val.size() != arr.length) {
1.1136 + pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
1.1137 + return new Prprt[0];
1.1138 + }
1.1139 + Prprt[] ret = new Prprt[arr.length];
1.1140 + BIG: for (int i = 0; i < ret.length; i++) {
1.1141 + AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
1.1142 + ret[i] = new Prprt(e, am, arr[i]);
1.1143 +
1.1144 + }
1.1145 + return ret;
1.1146 + }
1.1147 +
1.1148 + private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
1.1149 + try {
1.1150 + Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
1.1151 + Method m = trees.getMethod("instance", ProcessingEnvironment.class);
1.1152 + Object instance = m.invoke(null, pe);
1.1153 + m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
1.1154 + Object path = m.invoke(instance, e, tm);
1.1155 + m = path.getClass().getMethod("getLeaf");
1.1156 + Object leaf = m.invoke(path);
1.1157 + m = leaf.getClass().getMethod("getArguments");
1.1158 + return (List)m.invoke(leaf);
1.1159 + } catch (Exception ex) {
1.1160 + return Collections.emptyList();
1.1161 + }
1.1162 + }
1.1163 + }
1.1164 +
1.1165 +}
2.1 --- a/json/src/main/java/org/apidesign/html/json/impl/PageProcessor.java Fri Apr 19 10:25:57 2013 +0200
2.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2.3 @@ -1,1159 +0,0 @@
2.4 -/**
2.5 - * HTML via Java(tm) Language Bindings
2.6 - * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
2.7 - *
2.8 - * This program is free software: you can redistribute it and/or modify
2.9 - * it under the terms of the GNU General Public License as published by
2.10 - * the Free Software Foundation, version 2 of the License.
2.11 - *
2.12 - * This program is distributed in the hope that it will be useful,
2.13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
2.14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2.15 - * GNU General Public License for more details. apidesign.org
2.16 - * designates this particular file as subject to the
2.17 - * "Classpath" exception as provided by apidesign.org
2.18 - * in the License file that accompanied this code.
2.19 - *
2.20 - * You should have received a copy of the GNU General Public License
2.21 - * along with this program. Look for COPYING file in the top folder.
2.22 - * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
2.23 - */
2.24 -package org.apidesign.html.json.impl;
2.25 -
2.26 -import java.io.IOException;
2.27 -import java.io.InputStream;
2.28 -import java.io.OutputStreamWriter;
2.29 -import java.io.StringWriter;
2.30 -import java.io.Writer;
2.31 -import java.lang.annotation.AnnotationTypeMismatchException;
2.32 -import java.lang.annotation.IncompleteAnnotationException;
2.33 -import java.lang.reflect.Method;
2.34 -import java.util.ArrayList;
2.35 -import java.util.Collection;
2.36 -import java.util.Collections;
2.37 -import java.util.HashMap;
2.38 -import java.util.HashSet;
2.39 -import java.util.LinkedHashSet;
2.40 -import java.util.List;
2.41 -import java.util.Map;
2.42 -import java.util.Set;
2.43 -import java.util.WeakHashMap;
2.44 -import javax.annotation.processing.AbstractProcessor;
2.45 -import javax.annotation.processing.ProcessingEnvironment;
2.46 -import javax.annotation.processing.Processor;
2.47 -import javax.annotation.processing.RoundEnvironment;
2.48 -import javax.annotation.processing.SupportedAnnotationTypes;
2.49 -import javax.lang.model.element.AnnotationMirror;
2.50 -import javax.lang.model.element.AnnotationValue;
2.51 -import javax.lang.model.element.Element;
2.52 -import javax.lang.model.element.ElementKind;
2.53 -import javax.lang.model.element.ExecutableElement;
2.54 -import javax.lang.model.element.Modifier;
2.55 -import javax.lang.model.element.PackageElement;
2.56 -import javax.lang.model.element.TypeElement;
2.57 -import javax.lang.model.element.VariableElement;
2.58 -import javax.lang.model.type.ArrayType;
2.59 -import javax.lang.model.type.MirroredTypeException;
2.60 -import javax.lang.model.type.TypeKind;
2.61 -import javax.lang.model.type.TypeMirror;
2.62 -import javax.lang.model.util.Elements;
2.63 -import javax.lang.model.util.Types;
2.64 -import javax.tools.Diagnostic;
2.65 -import javax.tools.FileObject;
2.66 -import javax.tools.StandardLocation;
2.67 -import net.java.html.json.ComputedProperty;
2.68 -import net.java.html.json.Model;
2.69 -import net.java.html.json.Function;
2.70 -import net.java.html.json.OnPropertyChange;
2.71 -import net.java.html.json.OnReceive;
2.72 -import net.java.html.json.Property;
2.73 -import org.openide.util.lookup.ServiceProvider;
2.74 -
2.75 -/** Annotation processor to process an XHTML page and generate appropriate
2.76 - * "id" file.
2.77 - *
2.78 - * @author Jaroslav Tulach <jtulach@netbeans.org>
2.79 - */
2.80 -@ServiceProvider(service=Processor.class)
2.81 -@SupportedAnnotationTypes({
2.82 - "net.java.html.json.Model",
2.83 - "net.java.html.json.Function",
2.84 - "net.java.html.json.OnReceive",
2.85 - "net.java.html.json.OnPropertyChange",
2.86 - "net.java.html.json.ComputedProperty",
2.87 - "net.java.html.json.Property"
2.88 -})
2.89 -public final class PageProcessor extends AbstractProcessor {
2.90 - private final Map<Element,String> models = new WeakHashMap<>();
2.91 - private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
2.92 - @Override
2.93 - public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
2.94 - boolean ok = true;
2.95 - for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
2.96 - if (!processModel(e)) {
2.97 - ok = false;
2.98 - }
2.99 - }
2.100 - if (roundEnv.processingOver()) {
2.101 - models.clear();
2.102 - for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
2.103 - TypeElement te = (TypeElement)entry.getKey();
2.104 - String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
2.105 - Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
2.106 - if (finalElem == null) {
2.107 - continue;
2.108 - }
2.109 - Prprt[] props;
2.110 - Model m = finalElem.getAnnotation(Model.class);
2.111 - props = Prprt.wrap(processingEnv, finalElem, m.properties());
2.112 - for (Prprt p : props) {
2.113 - boolean[] isModel = { false };
2.114 - boolean[] isEnum = { false };
2.115 - boolean[] isPrimitive = { false };
2.116 - String t = checkType(p, isModel, isEnum, isPrimitive);
2.117 - if (isEnum[0]) {
2.118 - continue;
2.119 - }
2.120 - if (isPrimitive[0]) {
2.121 - continue;
2.122 - }
2.123 - if (isModel[0]) {
2.124 - continue;
2.125 - }
2.126 - if ("java.lang.String".equals(t)) {
2.127 - continue;
2.128 - }
2.129 - error("The type " + t + " should be defined by @Model annotation", entry.getKey());
2.130 - }
2.131 - }
2.132 - verify.clear();
2.133 - }
2.134 - return ok;
2.135 - }
2.136 -
2.137 - private InputStream openStream(String pkg, String name) throws IOException {
2.138 - try {
2.139 - FileObject fo = processingEnv.getFiler().getResource(
2.140 - StandardLocation.SOURCE_PATH, pkg, name);
2.141 - return fo.openInputStream();
2.142 - } catch (IOException ex) {
2.143 - return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
2.144 - }
2.145 - }
2.146 -
2.147 - private void error(String msg, Element e) {
2.148 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
2.149 - }
2.150 -
2.151 - private boolean processModel(Element e) {
2.152 - boolean ok = true;
2.153 - Model m = e.getAnnotation(Model.class);
2.154 - if (m == null) {
2.155 - return true;
2.156 - }
2.157 - String pkg = findPkgName(e);
2.158 - Writer w;
2.159 - String className = m.className();
2.160 - models.put(e, className);
2.161 - try {
2.162 - StringWriter body = new StringWriter();
2.163 - List<String> propsGetSet = new ArrayList<>();
2.164 - List<String> functions = new ArrayList<>();
2.165 - Map<String, Collection<String>> propsDeps = new HashMap<>();
2.166 - Map<String, Collection<String>> functionDeps = new HashMap<>();
2.167 - Prprt[] props = createProps(e, m.properties());
2.168 -
2.169 - if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
2.170 - ok = false;
2.171 - }
2.172 - if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
2.173 - ok = false;
2.174 - }
2.175 - if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
2.176 - ok = false;
2.177 - }
2.178 - if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
2.179 - ok = false;
2.180 - }
2.181 - FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
2.182 - w = new OutputStreamWriter(java.openOutputStream());
2.183 - try {
2.184 - w.append("package " + pkg + ";\n");
2.185 - w.append("import net.java.html.json.*;\n");
2.186 - w.append("public final class ").append(className).append(" implements Cloneable {\n");
2.187 - w.append(" private boolean locked;\n");
2.188 - w.append(" private org.apidesign.html.json.impl.Bindings ko;\n");
2.189 - w.append(body.toString());
2.190 - w.append(" private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
2.191 - w.append(" public ").append(className).append("() {\n");
2.192 - w.append(" };\n");
2.193 - w.append(" private org.apidesign.html.json.impl.Bindings intKnckt() {\n");
2.194 - w.append(" if (ko != null) return ko;\n");
2.195 - w.append(" return ko = org.apidesign.html.json.impl.Bindings.apply(this, ");
2.196 - writeStringArray(propsGetSet, w);
2.197 - w.append(", ");
2.198 - writeStringArray(functions, w);
2.199 - w.append(" );\n");
2.200 - w.append(" };\n");
2.201 - w.append(" ").append(className).append("(Object json) {\n");
2.202 - int values = 0;
2.203 - for (int i = 0; i < propsGetSet.size(); i += 4) {
2.204 - Prprt p = findPrprt(props, propsGetSet.get(i));
2.205 - if (p == null) {
2.206 - continue;
2.207 - }
2.208 - values++;
2.209 - }
2.210 - w.append(" Object[] ret = new Object[" + values + "];\n");
2.211 - w.append(" org.apidesign.html.json.impl.JSON.extract(json, new String[] {\n");
2.212 - for (int i = 0; i < propsGetSet.size(); i += 4) {
2.213 - Prprt p = findPrprt(props, propsGetSet.get(i));
2.214 - if (p == null) {
2.215 - continue;
2.216 - }
2.217 - w.append(" \"").append(propsGetSet.get(i)).append("\",\n");
2.218 - }
2.219 - w.append(" }, ret);\n");
2.220 - for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
2.221 - final String pn = propsGetSet.get(i);
2.222 - Prprt p = findPrprt(props, pn);
2.223 - if (p == null) {
2.224 - continue;
2.225 - }
2.226 - boolean[] isModel = { false };
2.227 - boolean[] isEnum = { false };
2.228 - boolean isPrimitive[] = { false };
2.229 - String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
2.230 - if (p.array()) {
2.231 - w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
2.232 - w.append(" for (Object e : ((Object[])ret[" + cnt + "])) {\n");
2.233 - if (isModel[0]) {
2.234 - w.append(" this.prop_").append(pn).append(".add(new ");
2.235 - w.append(type).append("(e));\n");
2.236 - } else if (isEnum[0]) {
2.237 - w.append(" this.prop_").append(pn);
2.238 - w.append(".add(e == null ? null : ");
2.239 - w.append(type).append(".valueOf((String)e));\n");
2.240 - } else {
2.241 - if (isPrimitive(type)) {
2.242 - w.append(" this.prop_").append(pn).append(".add(((Number)e).");
2.243 - w.append(type).append("Value());\n");
2.244 - } else {
2.245 - w.append(" this.prop_").append(pn).append(".add((");
2.246 - w.append(type).append(")e);\n");
2.247 - }
2.248 - }
2.249 - w.append(" }\n");
2.250 - w.append("}\n");
2.251 - } else {
2.252 - if (isEnum[0]) {
2.253 - w.append(" this.prop_").append(pn);
2.254 - w.append(" = ret[" + cnt + "] == null ? null : ");
2.255 - w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
2.256 - } else if (isPrimitive(type)) {
2.257 - w.append(" this.prop_").append(pn);
2.258 - w.append(" = ((Number)").append("ret[" + cnt + "]).");
2.259 - w.append(type).append("Value();\n");
2.260 - } else {
2.261 - w.append(" this.prop_").append(pn);
2.262 - w.append(" = (").append(type).append(')');
2.263 - w.append("ret[" + cnt + "];\n");
2.264 - }
2.265 - }
2.266 - cnt++;
2.267 - }
2.268 - w.append(" };\n");
2.269 - writeToString(props, w);
2.270 - writeClone(className, props, w);
2.271 - w.append(" public Object koData() {\n");
2.272 - w.append(" return intKnckt().koData();\n");
2.273 - w.append(" }\n");
2.274 - w.append("}\n");
2.275 - } finally {
2.276 - w.close();
2.277 - }
2.278 - } catch (IOException ex) {
2.279 - error("Can't create " + className + ".java", e);
2.280 - return false;
2.281 - }
2.282 - return ok;
2.283 - }
2.284 -
2.285 - private boolean generateProperties(
2.286 - Element where,
2.287 - Writer w, Prprt[] properties,
2.288 - Collection<String> props,
2.289 - Map<String,Collection<String>> deps,
2.290 - Map<String,Collection<String>> functionDeps
2.291 - ) throws IOException {
2.292 - boolean ok = true;
2.293 - for (Prprt p : properties) {
2.294 - final String tn;
2.295 - tn = typeName(where, p);
2.296 - String[] gs = toGetSet(p.name(), tn, p.array());
2.297 -
2.298 - if (p.array()) {
2.299 - w.write("private org.apidesign.html.json.impl.JSONList<" + tn + "> prop_" + p.name() + " = new org.apidesign.html.json.impl.JSONList<" + tn + ">(\""
2.300 - + p.name() + "\"");
2.301 - Collection<String> dependants = deps.get(p.name());
2.302 - if (dependants != null) {
2.303 - for (String depProp : dependants) {
2.304 - w.write(", ");
2.305 - w.write('\"');
2.306 - w.write(depProp);
2.307 - w.write('\"');
2.308 - }
2.309 - }
2.310 - w.write(")");
2.311 -
2.312 - dependants = functionDeps.get(p.name());
2.313 - if (dependants != null) {
2.314 - w.write(".onChange(new Runnable() { public void run() {\n");
2.315 - for (String call : dependants) {
2.316 - w.append(call);
2.317 - }
2.318 - w.write("}})");
2.319 - }
2.320 - w.write(";\n");
2.321 -
2.322 - w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
2.323 - w.write(" if (locked) throw new IllegalStateException();\n");
2.324 - w.write(" prop_" + p.name() + ".assign(ko);\n");
2.325 - w.write(" return prop_" + p.name() + ";\n");
2.326 - w.write("}\n");
2.327 - } else {
2.328 - w.write("private " + tn + " prop_" + p.name() + ";\n");
2.329 - w.write("public " + tn + " " + gs[0] + "() {\n");
2.330 - w.write(" if (locked) throw new IllegalStateException();\n");
2.331 - w.write(" return prop_" + p.name() + ";\n");
2.332 - w.write("}\n");
2.333 - w.write("public void " + gs[1] + "(" + tn + " v) {\n");
2.334 - w.write(" if (locked) throw new IllegalStateException();\n");
2.335 - w.write(" prop_" + p.name() + " = v;\n");
2.336 - w.write(" if (ko != null) {\n");
2.337 - w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
2.338 - Collection<String> dependants = deps.get(p.name());
2.339 - if (dependants != null) {
2.340 - for (String depProp : dependants) {
2.341 - w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
2.342 - }
2.343 - }
2.344 - w.write(" }\n");
2.345 - dependants = functionDeps.get(p.name());
2.346 - if (dependants != null) {
2.347 - for (String call : dependants) {
2.348 - w.append(call);
2.349 - }
2.350 - }
2.351 - w.write("}\n");
2.352 - }
2.353 -
2.354 - props.add(p.name());
2.355 - props.add(gs[2]);
2.356 - props.add(gs[3]);
2.357 - props.add(gs[0]);
2.358 - }
2.359 - return ok;
2.360 - }
2.361 -
2.362 - private boolean generateComputedProperties(
2.363 - Writer w, Prprt[] fixedProps,
2.364 - Collection<? extends Element> arr, Collection<String> props,
2.365 - Map<String,Collection<String>> deps
2.366 - ) throws IOException {
2.367 - boolean ok = true;
2.368 - for (Element e : arr) {
2.369 - if (e.getKind() != ElementKind.METHOD) {
2.370 - continue;
2.371 - }
2.372 - if (e.getAnnotation(ComputedProperty.class) == null) {
2.373 - continue;
2.374 - }
2.375 - ExecutableElement ee = (ExecutableElement)e;
2.376 - final TypeMirror rt = ee.getReturnType();
2.377 - final Types tu = processingEnv.getTypeUtils();
2.378 - TypeMirror ert = tu.erasure(rt);
2.379 - String tn = fqn(ert, ee);
2.380 - boolean array = false;
2.381 - if (tn.equals("java.util.List")) {
2.382 - array = true;
2.383 - }
2.384 -
2.385 - final String sn = ee.getSimpleName().toString();
2.386 - String[] gs = toGetSet(sn, tn, array);
2.387 -
2.388 - w.write("public " + tn + " " + gs[0] + "() {\n");
2.389 - w.write(" if (locked) throw new IllegalStateException();\n");
2.390 - int arg = 0;
2.391 - for (VariableElement pe : ee.getParameters()) {
2.392 - final String dn = pe.getSimpleName().toString();
2.393 -
2.394 - if (!verifyPropName(pe, dn, fixedProps)) {
2.395 - ok = false;
2.396 - }
2.397 -
2.398 - final String dt = fqn(pe.asType(), ee);
2.399 - String[] call = toGetSet(dn, dt, false);
2.400 - w.write(" " + dt + " arg" + (++arg) + " = ");
2.401 - w.write(call[0] + "();\n");
2.402 -
2.403 - Collection<String> depends = deps.get(dn);
2.404 - if (depends == null) {
2.405 - depends = new LinkedHashSet<>();
2.406 - deps.put(dn, depends);
2.407 - }
2.408 - depends.add(sn);
2.409 - }
2.410 - w.write(" try {\n");
2.411 - w.write(" locked = true;\n");
2.412 - w.write(" return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
2.413 - String sep = "";
2.414 - for (int i = 1; i <= arg; i++) {
2.415 - w.write(sep);
2.416 - w.write("arg" + i);
2.417 - sep = ", ";
2.418 - }
2.419 - w.write(");\n");
2.420 - w.write(" } finally {\n");
2.421 - w.write(" locked = false;\n");
2.422 - w.write(" }\n");
2.423 - w.write("}\n");
2.424 -
2.425 - props.add(e.getSimpleName().toString());
2.426 - props.add(gs[2]);
2.427 - props.add(null);
2.428 - props.add(gs[0]);
2.429 - }
2.430 -
2.431 - return ok;
2.432 - }
2.433 -
2.434 - private static String[] toGetSet(String name, String type, boolean array) {
2.435 - String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
2.436 - String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
2.437 - if ("int".equals(type)) {
2.438 - bck2brwsrType = "I";
2.439 - }
2.440 - if ("double".equals(type)) {
2.441 - bck2brwsrType = "D";
2.442 - }
2.443 - String pref = "get";
2.444 - if ("boolean".equals(type)) {
2.445 - pref = "is";
2.446 - bck2brwsrType = "Z";
2.447 - }
2.448 - final String nu = n.replace('.', '_');
2.449 - if (array) {
2.450 - return new String[] {
2.451 - "get" + n,
2.452 - null,
2.453 - "get" + nu + "__Ljava_util_List_2",
2.454 - null
2.455 - };
2.456 - }
2.457 - return new String[]{
2.458 - pref + n,
2.459 - "set" + n,
2.460 - pref + nu + "__" + bck2brwsrType,
2.461 - "set" + nu + "__V" + bck2brwsrType
2.462 - };
2.463 - }
2.464 -
2.465 - private String typeName(Element where, Prprt p) {
2.466 - String ret;
2.467 - boolean[] isModel = { false };
2.468 - boolean[] isEnum = { false };
2.469 - boolean isPrimitive[] = { false };
2.470 - ret = checkType(p, isModel, isEnum, isPrimitive);
2.471 - if (p.array()) {
2.472 - String bt = findBoxedType(ret);
2.473 - if (bt != null) {
2.474 - return bt;
2.475 - }
2.476 - }
2.477 - return ret;
2.478 - }
2.479 -
2.480 - private static String findBoxedType(String ret) {
2.481 - if (ret.equals("boolean")) {
2.482 - return Boolean.class.getName();
2.483 - }
2.484 - if (ret.equals("byte")) {
2.485 - return Byte.class.getName();
2.486 - }
2.487 - if (ret.equals("short")) {
2.488 - return Short.class.getName();
2.489 - }
2.490 - if (ret.equals("char")) {
2.491 - return Character.class.getName();
2.492 - }
2.493 - if (ret.equals("int")) {
2.494 - return Integer.class.getName();
2.495 - }
2.496 - if (ret.equals("long")) {
2.497 - return Long.class.getName();
2.498 - }
2.499 - if (ret.equals("float")) {
2.500 - return Float.class.getName();
2.501 - }
2.502 - if (ret.equals("double")) {
2.503 - return Double.class.getName();
2.504 - }
2.505 - return null;
2.506 - }
2.507 -
2.508 - private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
2.509 - StringBuilder sb = new StringBuilder();
2.510 - String sep = "";
2.511 - for (Prprt Prprt : existingProps) {
2.512 - if (Prprt.name().equals(propName)) {
2.513 - return true;
2.514 - }
2.515 - sb.append(sep);
2.516 - sb.append('"');
2.517 - sb.append(Prprt.name());
2.518 - sb.append('"');
2.519 - sep = ", ";
2.520 - }
2.521 - error(
2.522 - propName + " is not one of known properties: " + sb
2.523 - , e
2.524 - );
2.525 - return false;
2.526 - }
2.527 -
2.528 - private static String findPkgName(Element e) {
2.529 - for (;;) {
2.530 - if (e.getKind() == ElementKind.PACKAGE) {
2.531 - return ((PackageElement)e).getQualifiedName().toString();
2.532 - }
2.533 - e = e.getEnclosingElement();
2.534 - }
2.535 - }
2.536 -
2.537 - private boolean generateFunctions(
2.538 - Element clazz, StringWriter body, String className,
2.539 - List<? extends Element> enclosedElements, List<String> functions
2.540 - ) {
2.541 - for (Element m : enclosedElements) {
2.542 - if (m.getKind() != ElementKind.METHOD) {
2.543 - continue;
2.544 - }
2.545 - ExecutableElement e = (ExecutableElement)m;
2.546 - Function onF = e.getAnnotation(Function.class);
2.547 - if (onF == null) {
2.548 - continue;
2.549 - }
2.550 - if (!e.getModifiers().contains(Modifier.STATIC)) {
2.551 - error("@OnFunction method needs to be static", e);
2.552 - return false;
2.553 - }
2.554 - if (e.getModifiers().contains(Modifier.PRIVATE)) {
2.555 - error("@OnFunction method cannot be private", e);
2.556 - return false;
2.557 - }
2.558 - if (e.getReturnType().getKind() != TypeKind.VOID) {
2.559 - error("@OnFunction method should return void", e);
2.560 - return false;
2.561 - }
2.562 - String n = e.getSimpleName().toString();
2.563 - body.append("private void ").append(n).append("(Object data, Object ev) {\n");
2.564 - body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
2.565 - body.append(wrapParams(e, null, className, "ev", "data"));
2.566 - body.append(");\n");
2.567 - body.append("}\n");
2.568 -
2.569 - functions.add(n);
2.570 - functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
2.571 - }
2.572 - return true;
2.573 - }
2.574 -
2.575 - private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
2.576 - Prprt[] properties, String className,
2.577 - Map<String, Collection<String>> functionDeps
2.578 - ) {
2.579 - for (Element m : clazz.getEnclosedElements()) {
2.580 - if (m.getKind() != ElementKind.METHOD) {
2.581 - continue;
2.582 - }
2.583 - ExecutableElement e = (ExecutableElement) m;
2.584 - OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
2.585 - if (onPC == null) {
2.586 - continue;
2.587 - }
2.588 - for (String pn : onPC.value()) {
2.589 - if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
2.590 - error("No Prprt named '" + pn + "' in the model", clazz);
2.591 - return false;
2.592 - }
2.593 - }
2.594 - if (!e.getModifiers().contains(Modifier.STATIC)) {
2.595 - error("@OnPrprtChange method needs to be static", e);
2.596 - return false;
2.597 - }
2.598 - if (e.getModifiers().contains(Modifier.PRIVATE)) {
2.599 - error("@OnPrprtChange method cannot be private", e);
2.600 - return false;
2.601 - }
2.602 - if (e.getReturnType().getKind() != TypeKind.VOID) {
2.603 - error("@OnPrprtChange method should return void", e);
2.604 - return false;
2.605 - }
2.606 - String n = e.getSimpleName().toString();
2.607 -
2.608 -
2.609 - for (String pn : onPC.value()) {
2.610 - StringBuilder call = new StringBuilder();
2.611 - call.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
2.612 - call.append(wrapPropName(e, className, "name", pn));
2.613 - call.append(");\n");
2.614 -
2.615 - Collection<String> change = functionDeps.get(pn);
2.616 - if (change == null) {
2.617 - change = new ArrayList<>();
2.618 - functionDeps.put(pn, change);
2.619 - }
2.620 - change.add(call.toString());
2.621 - for (String dpn : findDerivedFrom(propDeps, pn)) {
2.622 - change = functionDeps.get(dpn);
2.623 - if (change == null) {
2.624 - change = new ArrayList<>();
2.625 - functionDeps.put(dpn, change);
2.626 - }
2.627 - change.add(call.toString());
2.628 - }
2.629 - }
2.630 - }
2.631 - return true;
2.632 - }
2.633 -
2.634 - private boolean generateReceive(
2.635 - Element clazz, StringWriter body, String className,
2.636 - List<? extends Element> enclosedElements, List<String> functions
2.637 - ) {
2.638 - for (Element m : enclosedElements) {
2.639 - if (m.getKind() != ElementKind.METHOD) {
2.640 - continue;
2.641 - }
2.642 - ExecutableElement e = (ExecutableElement)m;
2.643 - OnReceive onR = e.getAnnotation(OnReceive.class);
2.644 - if (onR == null) {
2.645 - continue;
2.646 - }
2.647 - if (!e.getModifiers().contains(Modifier.STATIC)) {
2.648 - error("@OnReceive method needs to be static", e);
2.649 - return false;
2.650 - }
2.651 - if (e.getModifiers().contains(Modifier.PRIVATE)) {
2.652 - error("@OnReceive method cannot be private", e);
2.653 - return false;
2.654 - }
2.655 - if (e.getReturnType().getKind() != TypeKind.VOID) {
2.656 - error("@OnReceive method should return void", e);
2.657 - return false;
2.658 - }
2.659 - String modelClass = null;
2.660 - boolean expectsList = false;
2.661 - List<String> args = new ArrayList<>();
2.662 - {
2.663 - for (VariableElement ve : e.getParameters()) {
2.664 - TypeMirror modelType = null;
2.665 - if (ve.asType().toString().equals(className)) {
2.666 - args.add(className + ".this");
2.667 - } else if (isModel(ve.asType())) {
2.668 - modelType = ve.asType();
2.669 - } else if (ve.asType().getKind() == TypeKind.ARRAY) {
2.670 - modelType = ((ArrayType)ve.asType()).getComponentType();
2.671 - expectsList = true;
2.672 - }
2.673 - if (modelType != null) {
2.674 - if (modelClass != null) {
2.675 - error("There can be only one model class among arguments", e);
2.676 - } else {
2.677 - modelClass = modelType.toString();
2.678 - if (expectsList) {
2.679 - args.add("arr");
2.680 - } else {
2.681 - args.add("arr[0]");
2.682 - }
2.683 - }
2.684 - }
2.685 - }
2.686 - }
2.687 - if (modelClass == null) {
2.688 - error("The method needs to have one @Model class as parameter", e);
2.689 - }
2.690 - String n = e.getSimpleName().toString();
2.691 - body.append("public void ").append(n).append("(");
2.692 - StringBuilder assembleURL = new StringBuilder();
2.693 - String jsonpVarName = null;
2.694 - {
2.695 - String sep = "";
2.696 - boolean skipJSONP = onR.jsonp().isEmpty();
2.697 - for (String p : findParamNames(e, onR.url(), assembleURL)) {
2.698 - if (!skipJSONP && p.equals(onR.jsonp())) {
2.699 - skipJSONP = true;
2.700 - jsonpVarName = p;
2.701 - continue;
2.702 - }
2.703 - body.append(sep);
2.704 - body.append("String ").append(p);
2.705 - sep = ", ";
2.706 - }
2.707 - if (!skipJSONP) {
2.708 - error(
2.709 - "Name of jsonp attribute ('" + onR.jsonp() +
2.710 - "') is not used in url attribute '" + onR.url() + "'", e
2.711 - );
2.712 - }
2.713 - }
2.714 - body.append(") {\n");
2.715 - body.append(" final Object[] result = { null };\n");
2.716 - body.append(
2.717 - " class ProcessResult implements Runnable {\n" +
2.718 - " @Override\n" +
2.719 - " public void run() {\n" +
2.720 - " Object value = result[0];\n");
2.721 - body.append(
2.722 - " " + modelClass + "[] arr;\n");
2.723 - body.append(
2.724 - " if (value instanceof Object[]) {\n" +
2.725 - " Object[] data = ((Object[])value);\n" +
2.726 - " arr = new " + modelClass + "[data.length];\n" +
2.727 - " for (int i = 0; i < data.length; i++) {\n" +
2.728 - " arr[i] = new " + modelClass + "(data[i]);\n" +
2.729 - " }\n" +
2.730 - " } else {\n" +
2.731 - " arr = new " + modelClass + "[1];\n" +
2.732 - " arr[0] = new " + modelClass + "(value);\n" +
2.733 - " }\n"
2.734 - );
2.735 - {
2.736 - body.append(clazz.getSimpleName()).append(".").append(n).append("(");
2.737 - String sep = "";
2.738 - for (String arg : args) {
2.739 - body.append(sep);
2.740 - body.append(arg);
2.741 - sep = ", ";
2.742 - }
2.743 - body.append(");\n");
2.744 - }
2.745 - body.append(
2.746 - " }\n" +
2.747 - " }\n"
2.748 - );
2.749 - body.append(" ProcessResult pr = new ProcessResult();\n");
2.750 - if (jsonpVarName != null) {
2.751 - body.append(" String ").append(jsonpVarName).
2.752 - append(" = org.apidesign.html.json.impl.JSON.createJSONP(result, pr);\n");
2.753 - }
2.754 - body.append(" org.apidesign.html.json.impl.JSON.loadJSON(\n ");
2.755 - body.append(assembleURL);
2.756 - body.append(", result, pr, ").append(jsonpVarName).append("\n );\n");
2.757 -// body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
2.758 -// body.append(wrapParams(e, null, className, "ev", "data"));
2.759 -// body.append(");\n");
2.760 - body.append("}\n");
2.761 - }
2.762 - return true;
2.763 - }
2.764 -
2.765 - private CharSequence wrapParams(
2.766 - ExecutableElement ee, String id, String className, String evName, String dataName
2.767 - ) {
2.768 - TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
2.769 - StringBuilder params = new StringBuilder();
2.770 - boolean first = true;
2.771 - for (VariableElement ve : ee.getParameters()) {
2.772 - if (!first) {
2.773 - params.append(", ");
2.774 - }
2.775 - first = false;
2.776 - String toCall = null;
2.777 - if (ve.asType() == stringType) {
2.778 - if (ve.getSimpleName().contentEquals("id")) {
2.779 - params.append('"').append(id).append('"');
2.780 - continue;
2.781 - }
2.782 - toCall = "org.apidesign.html.json.impl.JSON.toString(";
2.783 - }
2.784 - if (ve.asType().getKind() == TypeKind.DOUBLE) {
2.785 - toCall = "org.apidesign.html.json.impl.JSON.toDouble(";
2.786 - }
2.787 - if (ve.asType().getKind() == TypeKind.INT) {
2.788 - toCall = "org.apidesign.html.json.impl.JSON.toInt(";
2.789 - }
2.790 - if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
2.791 - toCall = "org.apidesign.html.json.impl.JSON.toModel(" + ve.asType() + ".class, ";
2.792 - }
2.793 -
2.794 - if (toCall != null) {
2.795 - params.append(toCall);
2.796 - if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
2.797 - params.append(dataName);
2.798 - params.append(", null");
2.799 - } else {
2.800 - if (evName == null) {
2.801 - final StringBuilder sb = new StringBuilder();
2.802 - sb.append("Unexpected string parameter name.");
2.803 - if (dataName != null) {
2.804 - sb.append(" Try \"").append(dataName).append("\"");
2.805 - }
2.806 - error(sb.toString(), ee);
2.807 - }
2.808 - params.append(evName);
2.809 - params.append(", \"");
2.810 - params.append(ve.getSimpleName().toString());
2.811 - params.append("\"");
2.812 - }
2.813 - params.append(")");
2.814 - continue;
2.815 - }
2.816 - String rn = fqn(ve.asType(), ee);
2.817 - int last = rn.lastIndexOf('.');
2.818 - if (last >= 0) {
2.819 - rn = rn.substring(last + 1);
2.820 - }
2.821 - if (rn.equals(className)) {
2.822 - params.append(className).append(".this");
2.823 - continue;
2.824 - }
2.825 - error(
2.826 - "@On method can only accept String named 'id' or " + className + " arguments",
2.827 - ee
2.828 - );
2.829 - }
2.830 - return params;
2.831 - }
2.832 -
2.833 -
2.834 - private CharSequence wrapPropName(
2.835 - ExecutableElement ee, String className, String propName, String propValue
2.836 - ) {
2.837 - TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
2.838 - StringBuilder params = new StringBuilder();
2.839 - boolean first = true;
2.840 - for (VariableElement ve : ee.getParameters()) {
2.841 - if (!first) {
2.842 - params.append(", ");
2.843 - }
2.844 - first = false;
2.845 - if (ve.asType() == stringType) {
2.846 - if (propName != null && ve.getSimpleName().contentEquals(propName)) {
2.847 - params.append('"').append(propValue).append('"');
2.848 - } else {
2.849 - error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
2.850 - }
2.851 - continue;
2.852 - }
2.853 - String rn = fqn(ve.asType(), ee);
2.854 - int last = rn.lastIndexOf('.');
2.855 - if (last >= 0) {
2.856 - rn = rn.substring(last + 1);
2.857 - }
2.858 - if (rn.equals(className)) {
2.859 - params.append(className).append(".this");
2.860 - continue;
2.861 - }
2.862 - error(
2.863 - "@OnPrprtChange method can only accept String or " + className + " arguments",
2.864 - ee);
2.865 - }
2.866 - return params;
2.867 - }
2.868 -
2.869 - private boolean isModel(TypeMirror tm) {
2.870 - final Element e = processingEnv.getTypeUtils().asElement(tm);
2.871 - if (e == null) {
2.872 - return false;
2.873 - }
2.874 - for (Element ch : e.getEnclosedElements()) {
2.875 - if (ch.getKind() == ElementKind.METHOD) {
2.876 - ExecutableElement ee = (ExecutableElement)ch;
2.877 - if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
2.878 - return true;
2.879 - }
2.880 - }
2.881 - }
2.882 - return models.values().contains(e.getSimpleName().toString());
2.883 - }
2.884 -
2.885 - private void writeStringArray(List<String> strings, Writer w) throws IOException {
2.886 - w.write("new String[] {\n");
2.887 - String sep = "";
2.888 - for (String n : strings) {
2.889 - w.write(sep);
2.890 - if (n == null) {
2.891 - w.write(" null");
2.892 - } else {
2.893 - w.write(" \"" + n + "\"");
2.894 - }
2.895 - sep = ",\n";
2.896 - }
2.897 - w.write("\n }");
2.898 - }
2.899 -
2.900 - private void writeToString(Prprt[] props, Writer w) throws IOException {
2.901 - w.write(" public String toString() {\n");
2.902 - w.write(" StringBuilder sb = new StringBuilder();\n");
2.903 - w.write(" sb.append('{');\n");
2.904 - String sep = "";
2.905 - for (Prprt p : props) {
2.906 - w.write(sep);
2.907 - w.append(" sb.append(\"" + p.name() + ": \");\n");
2.908 - w.append(" sb.append(org.apidesign.html.json.impl.JSON.toJSON(prop_");
2.909 - w.append(p.name()).append("));\n");
2.910 - sep = " sb.append(',');\n";
2.911 - }
2.912 - w.write(" sb.append('}');\n");
2.913 - w.write(" return sb.toString();\n");
2.914 - w.write(" }\n");
2.915 - }
2.916 - private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
2.917 - w.write(" public " + className + " clone() {\n");
2.918 - w.write(" " + className + " ret = new " + className + "();\n");
2.919 - for (Prprt p : props) {
2.920 - if (!p.array()) {
2.921 - boolean isModel[] = { false };
2.922 - boolean isEnum[] = { false };
2.923 - boolean isPrimitive[] = { false };
2.924 - checkType(p, isModel, isEnum, isPrimitive);
2.925 - if (!isModel[0]) {
2.926 - w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
2.927 - continue;
2.928 - }
2.929 - w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
2.930 - } else {
2.931 - w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
2.932 - }
2.933 - }
2.934 -
2.935 - w.write(" return ret;\n");
2.936 - w.write(" }\n");
2.937 - }
2.938 -
2.939 - private String inPckName(Element e) {
2.940 - StringBuilder sb = new StringBuilder();
2.941 - while (e.getKind() != ElementKind.PACKAGE) {
2.942 - if (sb.length() == 0) {
2.943 - sb.append(e.getSimpleName());
2.944 - } else {
2.945 - sb.insert(0, '.');
2.946 - sb.insert(0, e.getSimpleName());
2.947 - }
2.948 - e = e.getEnclosingElement();
2.949 - }
2.950 - return sb.toString();
2.951 - }
2.952 -
2.953 - private String fqn(TypeMirror pt, Element relative) {
2.954 - if (pt.getKind() == TypeKind.ERROR) {
2.955 - final Elements eu = processingEnv.getElementUtils();
2.956 - PackageElement pckg = eu.getPackageOf(relative);
2.957 - return pckg.getQualifiedName() + "." + pt.toString();
2.958 - }
2.959 - return pt.toString();
2.960 - }
2.961 -
2.962 - private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
2.963 - TypeMirror tm;
2.964 - try {
2.965 - String ret = p.typeName(processingEnv);
2.966 - TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
2.967 - if (e == null) {
2.968 - isModel[0] = true;
2.969 - isEnum[0] = false;
2.970 - isPrimitive[0] = false;
2.971 - return ret;
2.972 - }
2.973 - tm = e.asType();
2.974 - } catch (MirroredTypeException ex) {
2.975 - tm = ex.getTypeMirror();
2.976 - }
2.977 - tm = processingEnv.getTypeUtils().erasure(tm);
2.978 - isPrimitive[0] = tm.getKind().isPrimitive();
2.979 - final Element e = processingEnv.getTypeUtils().asElement(tm);
2.980 - final Model m = e == null ? null : e.getAnnotation(Model.class);
2.981 -
2.982 - String ret;
2.983 - if (m != null) {
2.984 - ret = findPkgName(e) + '.' + m.className();
2.985 - isModel[0] = true;
2.986 - models.put(e, m.className());
2.987 - } else if (findModelForMthd(e)) {
2.988 - ret = ((TypeElement)e).getQualifiedName().toString();
2.989 - isModel[0] = true;
2.990 - } else {
2.991 - ret = tm.toString();
2.992 - }
2.993 - TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
2.994 - enm = processingEnv.getTypeUtils().erasure(enm);
2.995 - isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
2.996 - return ret;
2.997 - }
2.998 -
2.999 - private static boolean findModelForMthd(Element clazz) {
2.1000 - if (clazz == null) {
2.1001 - return false;
2.1002 - }
2.1003 - for (Element e : clazz.getEnclosedElements()) {
2.1004 - if (e.getKind() == ElementKind.METHOD) {
2.1005 - ExecutableElement ee = (ExecutableElement)e;
2.1006 - if (
2.1007 - ee.getSimpleName().contentEquals("modelFor") &&
2.1008 - ee.getParameters().isEmpty()
2.1009 - ) {
2.1010 - return true;
2.1011 - }
2.1012 - }
2.1013 - }
2.1014 - return false;
2.1015 - }
2.1016 -
2.1017 - private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
2.1018 - List<String> params = new ArrayList<>();
2.1019 -
2.1020 - for (int pos = 0; ;) {
2.1021 - int next = url.indexOf('{', pos);
2.1022 - if (next == -1) {
2.1023 - assembleURL.append('"')
2.1024 - .append(url.substring(pos))
2.1025 - .append('"');
2.1026 - return params;
2.1027 - }
2.1028 - int close = url.indexOf('}', next);
2.1029 - if (close == -1) {
2.1030 - error("Unbalanced '{' and '}' in " + url, e);
2.1031 - return params;
2.1032 - }
2.1033 - final String paramName = url.substring(next + 1, close);
2.1034 - params.add(paramName);
2.1035 - assembleURL.append('"')
2.1036 - .append(url.substring(pos, next))
2.1037 - .append("\" + ").append(paramName).append(" + ");
2.1038 - pos = close + 1;
2.1039 - }
2.1040 - }
2.1041 -
2.1042 - private static Prprt findPrprt(Prprt[] properties, String propName) {
2.1043 - for (Prprt p : properties) {
2.1044 - if (propName.equals(p.name())) {
2.1045 - return p;
2.1046 - }
2.1047 - }
2.1048 - return null;
2.1049 - }
2.1050 -
2.1051 - private boolean isPrimitive(String type) {
2.1052 - return
2.1053 - "int".equals(type) ||
2.1054 - "double".equals(type) ||
2.1055 - "long".equals(type) ||
2.1056 - "short".equals(type) ||
2.1057 - "byte".equals(type) ||
2.1058 - "float".equals(type);
2.1059 - }
2.1060 -
2.1061 - private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
2.1062 - Set<String> names = new HashSet<>();
2.1063 - for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
2.1064 - if (e.getValue().contains(derivedProp)) {
2.1065 - names.add(e.getKey());
2.1066 - }
2.1067 - }
2.1068 - return names;
2.1069 - }
2.1070 -
2.1071 - private Prprt[] createProps(Element e, Property[] arr) {
2.1072 - Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
2.1073 - Prprt[] prev = verify.put(e, ret);
2.1074 - if (prev != null) {
2.1075 - error("Two sets of properties for ", e);
2.1076 - }
2.1077 - return ret;
2.1078 - }
2.1079 -
2.1080 - private static class Prprt {
2.1081 - private final Element e;
2.1082 - private final AnnotationMirror tm;
2.1083 - private final Property p;
2.1084 -
2.1085 - public Prprt(Element e, AnnotationMirror tm, Property p) {
2.1086 - this.e = e;
2.1087 - this.tm = tm;
2.1088 - this.p = p;
2.1089 - }
2.1090 -
2.1091 - String name() {
2.1092 - return p.name();
2.1093 - }
2.1094 -
2.1095 - boolean array() {
2.1096 - return p.array();
2.1097 - }
2.1098 -
2.1099 - String typeName(ProcessingEnvironment env) {
2.1100 - try {
2.1101 - return p.type().getName();
2.1102 - } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) {
2.1103 - for (Object v : getAnnoValues(env)) {
2.1104 - String s = v.toString().replace(" ", "");
2.1105 - if (s.startsWith("type=") && s.endsWith(".class")) {
2.1106 - return s.substring(5, s.length() - 6);
2.1107 - }
2.1108 - }
2.1109 - throw ex;
2.1110 - }
2.1111 - }
2.1112 -
2.1113 -
2.1114 - static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
2.1115 - if (arr.length == 0) {
2.1116 - return new Prprt[0];
2.1117 - }
2.1118 -
2.1119 - if (e.getKind() != ElementKind.CLASS) {
2.1120 - throw new IllegalStateException("" + e.getKind());
2.1121 - }
2.1122 - TypeElement te = (TypeElement)e;
2.1123 - List<? extends AnnotationValue> val = null;
2.1124 - for (AnnotationMirror an : te.getAnnotationMirrors()) {
2.1125 - for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
2.1126 - if (entry.getKey().getSimpleName().contentEquals("properties")) {
2.1127 - val = (List)entry.getValue().getValue();
2.1128 - break;
2.1129 - }
2.1130 - }
2.1131 - }
2.1132 - if (val == null || val.size() != arr.length) {
2.1133 - pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
2.1134 - return new Prprt[0];
2.1135 - }
2.1136 - Prprt[] ret = new Prprt[arr.length];
2.1137 - BIG: for (int i = 0; i < ret.length; i++) {
2.1138 - AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
2.1139 - ret[i] = new Prprt(e, am, arr[i]);
2.1140 -
2.1141 - }
2.1142 - return ret;
2.1143 - }
2.1144 -
2.1145 - private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
2.1146 - try {
2.1147 - Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
2.1148 - Method m = trees.getMethod("instance", ProcessingEnvironment.class);
2.1149 - Object instance = m.invoke(null, pe);
2.1150 - m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
2.1151 - Object path = m.invoke(instance, e, tm);
2.1152 - m = path.getClass().getMethod("getLeaf");
2.1153 - Object leaf = m.invoke(path);
2.1154 - m = leaf.getClass().getMethod("getArguments");
2.1155 - return (List)m.invoke(leaf);
2.1156 - } catch (Exception ex) {
2.1157 - return Collections.emptyList();
2.1158 - }
2.1159 - }
2.1160 - }
2.1161 -
2.1162 -}
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/json/src/test/java/net/java/html/json/Compile.java Fri Apr 19 11:11:07 2013 +0200
3.3 @@ -0,0 +1,214 @@
3.4 +/**
3.5 + * HTML via Java(tm) Language Bindings
3.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
3.7 + *
3.8 + * This program is free software: you can redistribute it and/or modify
3.9 + * it under the terms of the GNU General Public License as published by
3.10 + * the Free Software Foundation, version 2 of the License.
3.11 + *
3.12 + * This program is distributed in the hope that it will be useful,
3.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
3.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3.15 + * GNU General Public License for more details. apidesign.org
3.16 + * designates this particular file as subject to the
3.17 + * "Classpath" exception as provided by apidesign.org
3.18 + * in the License file that accompanied this code.
3.19 + *
3.20 + * You should have received a copy of the GNU General Public License
3.21 + * along with this program. Look for COPYING file in the top folder.
3.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
3.23 + */
3.24 +package net.java.html.json;
3.25 +
3.26 +import java.io.ByteArrayInputStream;
3.27 +import java.io.ByteArrayOutputStream;
3.28 +import java.io.IOException;
3.29 +import java.io.InputStream;
3.30 +import java.io.OutputStream;
3.31 +import java.net.URI;
3.32 +import java.net.URISyntaxException;
3.33 +import java.util.ArrayList;
3.34 +import java.util.Arrays;
3.35 +import java.util.HashMap;
3.36 +import java.util.List;
3.37 +import java.util.Map;
3.38 +import java.util.regex.Matcher;
3.39 +import java.util.regex.Pattern;
3.40 +import javax.tools.Diagnostic;
3.41 +import javax.tools.DiagnosticListener;
3.42 +import javax.tools.FileObject;
3.43 +import javax.tools.ForwardingJavaFileManager;
3.44 +import javax.tools.JavaFileManager;
3.45 +import javax.tools.JavaFileObject;
3.46 +import javax.tools.JavaFileObject.Kind;
3.47 +import javax.tools.SimpleJavaFileObject;
3.48 +import javax.tools.StandardJavaFileManager;
3.49 +import javax.tools.StandardLocation;
3.50 +import javax.tools.ToolProvider;
3.51 +
3.52 +/**
3.53 + *
3.54 + * @author Jaroslav Tulach <jtulach@netbeans.org>
3.55 + */
3.56 +final class Compile implements DiagnosticListener<JavaFileObject> {
3.57 + private final List<Diagnostic<? extends JavaFileObject>> errors = new ArrayList<>();
3.58 + private final Map<String, byte[]> classes;
3.59 + private final String pkg;
3.60 + private final String cls;
3.61 + private final String html;
3.62 +
3.63 + private Compile(String html, String code) throws IOException {
3.64 + this.pkg = findPkg(code);
3.65 + this.cls = findCls(code);
3.66 + this.html = html;
3.67 + classes = compile(html, code);
3.68 + }
3.69 +
3.70 + /** Performs compilation of given HTML page and associated Java code
3.71 + */
3.72 + public static Compile create(String html, String code) throws IOException {
3.73 + return new Compile(html, code);
3.74 + }
3.75 +
3.76 + /** Checks for given class among compiled resources */
3.77 + public byte[] get(String res) {
3.78 + return classes.get(res);
3.79 + }
3.80 +
3.81 + /** Obtains errors created during compilation.
3.82 + */
3.83 + public List<Diagnostic<? extends JavaFileObject>> getErrors() {
3.84 + List<Diagnostic<? extends JavaFileObject>> err = new ArrayList<>();
3.85 + for (Diagnostic<? extends JavaFileObject> diagnostic : errors) {
3.86 + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
3.87 + err.add(diagnostic);
3.88 + }
3.89 + }
3.90 + return err;
3.91 + }
3.92 +
3.93 + private Map<String, byte[]> compile(final String html, final String code) throws IOException {
3.94 + StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null);
3.95 +
3.96 + final Map<String, ByteArrayOutputStream> class2BAOS = new HashMap<>();
3.97 +
3.98 + JavaFileObject file = new SimpleJavaFileObject(URI.create("mem://mem"), Kind.SOURCE) {
3.99 + @Override
3.100 + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
3.101 + return code;
3.102 + }
3.103 + };
3.104 + final JavaFileObject htmlFile = new SimpleJavaFileObject(URI.create("mem://mem2"), Kind.OTHER) {
3.105 + @Override
3.106 + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
3.107 + return html;
3.108 + }
3.109 +
3.110 + @Override
3.111 + public InputStream openInputStream() throws IOException {
3.112 + return new ByteArrayInputStream(html.getBytes());
3.113 + }
3.114 + };
3.115 +
3.116 + final URI scratch;
3.117 + try {
3.118 + scratch = new URI("mem://mem3");
3.119 + } catch (URISyntaxException ex) {
3.120 + throw new IOException(ex);
3.121 + }
3.122 +
3.123 + JavaFileManager jfm = new ForwardingJavaFileManager<JavaFileManager>(sjfm) {
3.124 + @Override
3.125 + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
3.126 + if (kind == Kind.CLASS) {
3.127 + final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
3.128 +
3.129 + class2BAOS.put(className.replace('.', '/') + ".class", buffer);
3.130 + return new SimpleJavaFileObject(sibling.toUri(), kind) {
3.131 + @Override
3.132 + public OutputStream openOutputStream() throws IOException {
3.133 + return buffer;
3.134 + }
3.135 + };
3.136 + }
3.137 +
3.138 + if (kind == Kind.SOURCE) {
3.139 + final String n = className.replace('.', '/') + ".java";
3.140 + return new SimpleJavaFileObject(scratch/*sibling.toUri()*/, kind) {
3.141 + private final ByteArrayOutputStream data = new ByteArrayOutputStream();
3.142 + @Override
3.143 + public OutputStream openOutputStream() throws IOException {
3.144 + return data;
3.145 + }
3.146 +
3.147 + @Override
3.148 + public String getName() {
3.149 + return n;
3.150 + }
3.151 +
3.152 +
3.153 +
3.154 + @Override
3.155 + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
3.156 + data.close();
3.157 + return new String(data.toByteArray());
3.158 + }
3.159 + };
3.160 + }
3.161 +
3.162 + throw new IllegalStateException();
3.163 + }
3.164 +
3.165 + @Override
3.166 + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
3.167 + if (location == StandardLocation.SOURCE_PATH) {
3.168 + if (packageName.equals(pkg)) {
3.169 + return htmlFile;
3.170 + }
3.171 + }
3.172 +
3.173 + return null;
3.174 + }
3.175 +
3.176 + };
3.177 +
3.178 + ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", "1.7", "-target", "1.7"), null, Arrays.asList(file)).call();
3.179 +
3.180 + Map<String, byte[]> result = new HashMap<>();
3.181 +
3.182 + for (Map.Entry<String, ByteArrayOutputStream> e : class2BAOS.entrySet()) {
3.183 + result.put(e.getKey(), e.getValue().toByteArray());
3.184 + }
3.185 +
3.186 + return result;
3.187 + }
3.188 +
3.189 +
3.190 + @Override
3.191 + public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
3.192 + errors.add(diagnostic);
3.193 + }
3.194 + private static String findPkg(String java) throws IOException {
3.195 + Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE);
3.196 + Matcher m = p.matcher(java);
3.197 + if (!m.find()) {
3.198 + throw new IOException("Can't find package declaration in the java file");
3.199 + }
3.200 + String pkg = m.group(1);
3.201 + return pkg;
3.202 + }
3.203 + private static String findCls(String java) throws IOException {
3.204 + Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE);
3.205 + Matcher m = p.matcher(java);
3.206 + if (!m.find()) {
3.207 + throw new IOException("Can't find package declaration in the java file");
3.208 + }
3.209 + String cls = m.group(1);
3.210 + return cls;
3.211 + }
3.212 +
3.213 + String getHtml() {
3.214 + String fqn = "'" + pkg + '.' + cls + "'";
3.215 + return html.replace("'${fqn}'", fqn);
3.216 + }
3.217 +}
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/json/src/test/java/net/java/html/json/ModelProcessorTest.java Fri Apr 19 11:11:07 2013 +0200
4.3 @@ -0,0 +1,63 @@
4.4 +/**
4.5 + * HTML via Java(tm) Language Bindings
4.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
4.7 + *
4.8 + * This program is free software: you can redistribute it and/or modify
4.9 + * it under the terms of the GNU General Public License as published by
4.10 + * the Free Software Foundation, version 2 of the License.
4.11 + *
4.12 + * This program is distributed in the hope that it will be useful,
4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4.15 + * GNU General Public License for more details. apidesign.org
4.16 + * designates this particular file as subject to the
4.17 + * "Classpath" exception as provided by apidesign.org
4.18 + * in the License file that accompanied this code.
4.19 + *
4.20 + * You should have received a copy of the GNU General Public License
4.21 + * along with this program. Look for COPYING file in the top folder.
4.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
4.23 + */
4.24 +package net.java.html.json;
4.25 +
4.26 +import java.io.IOException;
4.27 +import java.util.Locale;
4.28 +import javax.tools.Diagnostic;
4.29 +import javax.tools.JavaFileObject;
4.30 +import static org.testng.Assert.*;
4.31 +import org.testng.annotations.Test;
4.32 +
4.33 +/** Verify errors emitted by the processor.
4.34 + *
4.35 + * @author Jaroslav Tulach <jtulach@netbeans.org>
4.36 + */
4.37 +public class ModelProcessorTest {
4.38 + @Test public void verifyWrongType() throws IOException {
4.39 + String html = "<html><body>"
4.40 + + "</body></html>";
4.41 + String code = "package x.y.z;\n"
4.42 + + "import net.java.html.json.Model;\n"
4.43 + + "import net.java.html.json.Property;\n"
4.44 + + "@Model(className=\"XModel\", properties={\n"
4.45 + + " @Property(name=\"prop\", type=Runnable.class)\n"
4.46 + + "})\n"
4.47 + + "class X {\n"
4.48 + + "}\n";
4.49 +
4.50 + Compile c = Compile.create(html, code);
4.51 + assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
4.52 + boolean ok = false;
4.53 + StringBuilder msgs = new StringBuilder();
4.54 + for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
4.55 + String msg = e.getMessage(Locale.ENGLISH);
4.56 + if (!msg.contains("Runnable")) {
4.57 + ok = true;
4.58 + }
4.59 + msgs.append("\n").append(msg);
4.60 + }
4.61 + if (!ok) {
4.62 + fail("Should contain warning about Runnable:" + msgs);
4.63 + }
4.64 + }
4.65 +
4.66 +}