# HG changeset patch
# User toni.epple@eppleton.de
# Date 1368779261 -7200
# Node ID ae3f2aa509703f20184c6c2593b05e6ca30a3e3c
# Parent d127d41768bdab9af6ebfc9412e97887141281b8# Parent 8ee3112f5647f6180cc1a12ab9146ff7c1cc7644
Merging latest changes from main line
diff -r d127d41768bd -r ae3f2aa50970 .hgtags
--- a/.hgtags Tue Mar 05 07:57:16 2013 +0100
+++ b/.hgtags Fri May 17 10:27:41 2013 +0200
@@ -1,2 +1,12 @@
0a115f1c6f3c70458fc479ae82b4d7fcdeb7e95a jdk7-b147_base
7367a296a9ec4a88e0292a41244c96283818e563 bck2brwsr-0.3
+caf1e66268fd4100d57922d973ae09a6bf3be847 release-${releaseVersion}
+30e9ac29654fba6f67d0921e7e3aa21133442592 release-0.5
+caf1e66268fd4100d57922d973ae09a6bf3be847 release-0.4
+caf1e66268fd4100d57922d973ae09a6bf3be847 release-${releaseVersion}
+0000000000000000000000000000000000000000 release-${releaseVersion}
+52a4a5f868bccc67d50ad17f793b9ebabdf75d88 release-0.6
+6792dc0bafb9c76a099e45bfc9e967d6a2823827 release-0.7
+623816269b75e53fffb4b19960df7040a3c20056 release-0.7
+23572dc719bd630817d11eaabdee4565f63ef8e1 release-0.7.1
+56abd247f421febd8b2c5e59d666968692e11555 release-0.7.2
diff -r d127d41768bd -r ae3f2aa50970 benchmarks/matrix-multiplication/pom.xml
--- a/benchmarks/matrix-multiplication/pom.xml Tue Mar 05 07:57:16 2013 +0100
+++ b/benchmarks/matrix-multiplication/pom.xml Fri May 17 10:27:41 2013 +0200
@@ -4,12 +4,12 @@
org.apidesign.bck2brwsrmatrix.multiplication
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTjarbenchmarksorg.apidesign.bck2brwsr
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTMatrix multiplication
@@ -37,6 +37,36 @@
true
+
+ org.codehaus.mojo
+ xml-maven-plugin
+ 1.0
+
+
+
+ transform
+
+ install
+
+
+
+
+
+ target/surefire-reports
+ target/surefire-reports
+
+ TEST*.xml
+
+ src/main/select-time.xsl
+
+
+ .csv
+
+
+
+
+
+
@@ -44,7 +74,7 @@
org.apidesign.bck2brwsremul.mini
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTorg.testng
@@ -61,7 +91,13 @@
org.apidesign.bck2brwsrvmtest
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOT
+ test
+
+
+ org.apidesign.bck2brwsr
+ launcher.http
+ 0.8-SNAPSHOTtest
diff -r d127d41768bd -r ae3f2aa50970 benchmarks/matrix-multiplication/src/main/select-time.xsl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/benchmarks/matrix-multiplication/src/main/select-time.xsl Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+ End
+
+ NaN
+
+
+
+
+
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+ ,
+
+
+
+
+
+
diff -r d127d41768bd -r ae3f2aa50970 benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java
--- a/benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java Tue Mar 05 07:57:16 2013 +0100
+++ b/benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java Fri May 17 10:27:41 2013 +0200
@@ -31,6 +31,22 @@
}
@Compare(scripting = false)
+ public String oneIteration() throws IOException {
+
+ Matrix m1 = new Matrix(5);
+ Matrix m2 = new Matrix(5);
+
+ m1.generateData();
+ m2.generateData();
+
+ Matrix res = m1.multiply(m2);
+
+ StringBuilder sb = new StringBuilder();
+ res.printOn(sb);
+ return sb.toString();
+ }
+
+ @Compare(scripting = false)
public String tenThousandIterations() throws IOException {
Matrix m1 = new Matrix(5);
@@ -50,6 +66,27 @@
return sb.toString();
}
+ @Compare(scripting = false)
+ public String tenUselessIterations() throws IOException {
+
+ Matrix m1 = new Matrix(5);
+ Matrix m2 = new Matrix(5);
+
+ m1.generateData();
+ m2.generateData();
+
+ Matrix res = null;
+ for (int i = 0; i < 10; i++) {
+ res = m1.multiply(m2);
+ m1 = res;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ res.printOn(sb);
+ return sb.toString();
+ }
+
+
@Factory
public static Object[] create() {
return VMTest.create(MatrixTest.class);
diff -r d127d41768bd -r ae3f2aa50970 benchmarks/pom.xml
--- a/benchmarks/pom.xml Tue Mar 05 07:57:16 2013 +0100
+++ b/benchmarks/pom.xml Fri May 17 10:27:41 2013 +0200
@@ -4,11 +4,11 @@
bck2brwsrorg.apidesign
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTorg.apidesign.bck2brwsrbenchmarks
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTpomPerformance benchmarks
diff -r d127d41768bd -r ae3f2aa50970 dew/pom.xml
--- a/dew/pom.xml Tue Mar 05 07:57:16 2013 +0100
+++ b/dew/pom.xml Fri May 17 10:27:41 2013 +0200
@@ -4,11 +4,11 @@
org.apidesignbck2brwsr
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTorg.apidesign.bck2brwsrdew
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTDevelopment Environment for Webhttp://maven.apache.org
@@ -42,6 +42,14 @@
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 2.7
+
+ true
+
+
diff -r d127d41768bd -r ae3f2aa50970 ide/editor/pom.xml
--- a/ide/editor/pom.xml Tue Mar 05 07:57:16 2013 +0100
+++ b/ide/editor/pom.xml Fri May 17 10:27:41 2013 +0200
@@ -4,19 +4,18 @@
ideorg.apidesign.bck2brwsr
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOT
- org.apidesign.bck2brwsr.ide.editor
+ org.apidesign.bck2brwsr.ideeditor
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTnbmEditor Support for Bck2BrwsrUTF-8
- RELEASE72${project.build.directory}/endorsed
@@ -40,71 +39,59 @@
org.netbeans.apiorg-netbeans-api-annotations-common
- ${netbeans.version}org.netbeans.apiorg-netbeans-modules-java-source
- ${netbeans.version}org.netbeans.apiorg-netbeans-libs-javacapi
- ${netbeans.version}org.netbeans.apiorg-netbeans-spi-java-hints
- ${netbeans.version}org.netbeans.apiorg-netbeans-modules-parsing-api
- ${netbeans.version}org.netbeans.apiorg-netbeans-spi-editor-hints
- ${netbeans.version}org.netbeans.apiorg-openide-util
- ${netbeans.version}org.netbeans.apiorg-netbeans-modules-java-lexer
- ${netbeans.version}org.netbeans.apiorg-netbeans-modules-lexer
- ${netbeans.version}org.apidesign.bck2brwsrcore
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTjartestorg.netbeans.apiorg-netbeans-modules-java-hints-test
- ${netbeans.version}testorg.netbeans.apiorg-netbeans-libs-junit4
- ${netbeans.version}testorg.netbeans.modulesorg-netbeans-lib-nbjavac
- ${netbeans.version}test
diff -r d127d41768bd -r ae3f2aa50970 ide/pom.xml
--- a/ide/pom.xml Tue Mar 05 07:57:16 2013 +0100
+++ b/ide/pom.xml Fri May 17 10:27:41 2013 +0200
@@ -4,14 +4,26 @@
bck2brwsrorg.apidesign
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTorg.apidesign.bck2brwsride
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTpomIDE Supporteditor
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 2.7
+
+ true
+
+
+
+
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/pom.xml
--- a/javaquery/api/pom.xml Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/pom.xml Fri May 17 10:27:41 2013 +0200
@@ -4,11 +4,11 @@
org.apidesign.bck2brwsrjavaquery
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTorg.apidesign.bck2brwsrjavaquery.api
- 0.4-SNAPSHOT
+ 0.8-SNAPSHOTJavaQuery APIhttp://maven.apache.org
@@ -18,8 +18,8 @@
maven-compiler-plugin2.3.2
-
- 1.6
+
+ 1.7
@@ -73,5 +73,11 @@
${project.version}test
+
+ ${project.groupId}
+ launcher.http
+ ${project.version}
+ test
+
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,155 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage;
+
+import org.apidesign.bck2brwsr.core.JavaScriptBody;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public final class ConvertTypes {
+ ConvertTypes() {
+ }
+
+ public static String toString(Object object, String property) {
+ Object ret = getProperty(object, property);
+ return ret == null ? null : ret.toString();
+ }
+
+ public static double toDouble(Object object, String property) {
+ Object ret = getProperty(object, property);
+ return ret instanceof Number ? ((Number)ret).doubleValue() : Double.NaN;
+ }
+
+ public static int toInt(Object object, String property) {
+ Object ret = getProperty(object, property);
+ return ret instanceof Number ? ((Number)ret).intValue() : Integer.MIN_VALUE;
+ }
+
+ public static T toModel(Class modelClass, Object object, String property) {
+ Object ret = getProperty(object, property);
+ if (ret == null || modelClass.isInstance(ret)) {
+ return modelClass.cast(ret);
+ }
+ throw new IllegalStateException("Value " + ret + " is not of type " + modelClass);
+ }
+
+ public static String toJSON(Object value) {
+ if (value == null) {
+ return "null";
+ }
+ if (value instanceof Enum) {
+ value = value.toString();
+ }
+ if (value instanceof String) {
+ return '"' +
+ ((String)value).
+ replace("\"", "\\\"").
+ replace("\n", "\\n").
+ replace("\r", "\\r").
+ replace("\t", "\\t")
+ + '"';
+ }
+ return value.toString();
+ }
+
+ @JavaScriptBody(args = { "object", "property" },
+ body = "if (property === null) return object;\n"
+ + "var p = object[property]; return p ? p : null;"
+ )
+ private static Object getProperty(Object object, String property) {
+ return null;
+ }
+
+ public static String createJSONP(Object[] jsonResult, Runnable whenDone) {
+ int h = whenDone.hashCode();
+ String name;
+ for (;;) {
+ name = "jsonp" + Integer.toHexString(h);
+ if (defineIfUnused(name, jsonResult, whenDone)) {
+ return name;
+ }
+ h++;
+ }
+ }
+
+ @JavaScriptBody(args = { "name", "arr", "run" }, body =
+ "if (window[name]) return false;\n "
+ + "window[name] = function(data) {\n "
+ + " delete window[name];\n"
+ + " var el = window.document.getElementById(name);\n"
+ + " el.parentNode.removeChild(el);\n"
+ + " arr[0] = data;\n"
+ + " run.run__V();\n"
+ + "};\n"
+ + "return true;\n"
+ )
+ private static boolean defineIfUnused(String name, Object[] arr, Runnable run) {
+ return true;
+ }
+
+ @JavaScriptBody(args = { "url", "arr", "callback" }, body = ""
+ + "var request = new XMLHttpRequest();\n"
+ + "request.open('GET', url, true);\n"
+ + "request.setRequestHeader('Content-Type', 'application/json; charset=utf-8');\n"
+ + "request.onreadystatechange = function() {\n"
+ + " if (this.readyState!==4) return;\n"
+ + " try {\n"
+ + " arr[0] = eval('(' + this.response + ')');\n"
+ + " } catch (error) {;\n"
+ + " throw 'Cannot parse' + error + ':' + this.response;\n"
+ + " };\n"
+ + " callback.run__V();\n"
+ + "};"
+ + "request.send();"
+ )
+ private static void loadJSON(
+ String url, Object[] jsonResult, Runnable whenDone
+ ) {
+ }
+
+ public static void loadJSON(
+ String url, Object[] jsonResult, Runnable whenDone, String jsonp
+ ) {
+ if (jsonp == null) {
+ loadJSON(url, jsonResult, whenDone);
+ } else {
+ loadJSONP(url, jsonp);
+ }
+ }
+
+ @JavaScriptBody(args = { "url", "jsonp" }, body =
+ "var scrpt = window.document.createElement('script');\n "
+ + "scrpt.setAttribute('src', url);\n "
+ + "scrpt.setAttribute('id', jsonp);\n "
+ + "scrpt.setAttribute('type', 'text/javascript');\n "
+ + "var body = document.getElementsByTagName('body')[0];\n "
+ + "body.appendChild(scrpt);\n"
+ )
+ private static void loadJSONP(String url, String jsonp) {
+
+ }
+
+ public static void extractJSON(Object jsonObject, String[] props, Object[] values) {
+ for (int i = 0; i < props.length; i++) {
+ values[i] = getProperty(jsonObject, props[i]);
+ }
+ }
+
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/KOList.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/KOList.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,167 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import org.apidesign.bck2brwsr.core.JavaScriptOnly;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public final class KOList extends ArrayList {
+ private final String name;
+ private final String[] deps;
+ private Knockout model;
+ private Runnable onchange;
+
+ public KOList(String name, String... deps) {
+ this.name = name;
+ this.deps = deps;
+ }
+
+ public void assign(Knockout model) {
+ if (this.model != model) {
+ this.model = model;
+ notifyChange();
+ }
+ }
+
+ public KOList onChange(Runnable r) {
+ if (this.onchange != null) {
+ throw new IllegalStateException();
+ }
+ this.onchange = r;
+ return this;
+ }
+
+ @Override
+ public boolean add(T e) {
+ boolean ret = super.add(e);
+ notifyChange();
+ return ret;
+ }
+
+ @Override
+ public boolean addAll(Collection extends T> c) {
+ boolean ret = super.addAll(c);
+ notifyChange();
+ return ret;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection extends T> c) {
+ boolean ret = super.addAll(index, c);
+ notifyChange();
+ return ret;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ boolean ret = super.remove(o);
+ notifyChange();
+ return ret;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ notifyChange();
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ boolean ret = super.removeAll(c);
+ notifyChange();
+ return ret;
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ boolean ret = super.retainAll(c);
+ notifyChange();
+ return ret;
+ }
+
+ @Override
+ public T set(int index, T element) {
+ T ret = super.set(index, element);
+ notifyChange();
+ return ret;
+ }
+
+ @Override
+ public void add(int index, T element) {
+ super.add(index, element);
+ notifyChange();
+ }
+
+ @Override
+ public T remove(int index) {
+ T ret = super.remove(index);
+ notifyChange();
+ return ret;
+ }
+
+ @Override
+ public String toString() {
+ Iterator it = iterator();
+ if (!it.hasNext()) {
+ return "[]";
+ }
+ String sep = "";
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ while (it.hasNext()) {
+ T t = it.next();
+ sb.append(sep);
+ sb.append(ConvertTypes.toJSON(t));
+ sep = ",";
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+
+ @JavaScriptOnly(name = "koArray", value = "function() { return this.toArray___3Ljava_lang_Object_2(); }")
+ private static native int koArray();
+
+ private void notifyChange() {
+ Knockout m = model;
+ if (m != null) {
+ m.valueHasMutated(name);
+ for (String dependant : deps) {
+ m.valueHasMutated(dependant);
+ }
+ }
+ Runnable r = onchange;
+ if (r != null) {
+ r.run();
+ }
+ }
+
+ @Override
+ public KOList clone() {
+ KOList ko = (KOList)super.clone();
+ ko.model = null;
+ return ko;
+ }
+
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java
--- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Fri May 17 10:27:41 2013 +0200
@@ -18,6 +18,7 @@
package org.apidesign.bck2brwsr.htmlpage;
import java.lang.reflect.Method;
+import java.util.List;
import org.apidesign.bck2brwsr.core.ExtraJavaScript;
import org.apidesign.bck2brwsr.core.JavaScriptBody;
@@ -29,38 +30,40 @@
public class Knockout {
/** used by tests */
static Knockout next;
-
- Knockout() {
+ private final Object model;
+
+ Knockout(Object model) {
+ this.model = model == null ? this : model;
}
public static Knockout applyBindings(
- Class modelClass, M model, String[] propsGettersAndSetters
+ Object model, String[] propsGettersAndSetters,
+ String[] methodsAndSignatures
+ ) {
+ applyImpl(propsGettersAndSetters, model.getClass(), model, model, methodsAndSignatures);
+ return new Knockout(model);
+ }
+ public static Knockout applyBindings(
+ Class modelClass, M model, String[] propsGettersAndSetters,
+ String[] methodsAndSignatures
) {
Knockout bindings = next;
next = null;
if (bindings == null) {
- bindings = new Knockout();
+ bindings = new Knockout(null);
}
- for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
- try {
- Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]);
- bind(bindings, model, propsGettersAndSetters[i],
- propsGettersAndSetters[i + 1],
- propsGettersAndSetters[i + 2],
- getter.getReturnType().isPrimitive()
- );
- } catch (NoSuchMethodException ex) {
- throw new IllegalStateException(ex.getMessage());
- }
- }
+ applyImpl(propsGettersAndSetters, modelClass, bindings, model, methodsAndSignatures);
applyBindings(bindings);
return bindings;
}
- @JavaScriptBody(args = { "prop" }, body =
- "this[prop].valueHasMutated();"
+ public void valueHasMutated(String prop) {
+ valueHasMutated(model, prop);
+ }
+ @JavaScriptBody(args = { "self", "prop" }, body =
+ "self[prop].valueHasMutated();"
)
- public void valueHasMutated(String prop) {
+ public void valueHasMutated(Object self, String prop) {
}
@@ -68,26 +71,60 @@
public static void triggerEvent(String id, String ev) {
}
- @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive" }, body =
+ @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive", "array" }, body =
"var bnd = {\n"
- + " read: function() {\n"
+ + " 'read': function() {\n"
+ " var v = model[getter]();\n"
+ + " if (array) v = v.koArray();\n"
+ " return v;\n"
+ " },\n"
- + " owner: bindings\n"
+ + " 'owner': bindings\n"
+ "};\n"
+ "if (setter != null) {\n"
- + " bnd.write = function(val) {\n"
+ + " bnd['write'] = function(val) {\n"
+ " model[setter](primitive ? new Number(val) : val);\n"
+ " };\n"
+ "}\n"
- + "bindings[prop] = ko.computed(bnd);"
+ + "bindings[prop] = ko['computed'](bnd);"
)
private static void bind(
- Object bindings, Object model, String prop, String getter, String setter, boolean primitive
+ Object bindings, Object model, String prop, String getter, String setter, boolean primitive, boolean array
+ ) {
+ }
+
+ @JavaScriptBody(args = { "bindings", "model", "prop", "sig" }, body =
+ "bindings[prop] = function(data, ev) { model[sig](data, ev); };"
+ )
+ private static void expose(
+ Object bindings, Object model, String prop, String sig
) {
}
@JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
private static void applyBindings(Object bindings) {}
+
+ private static void applyImpl(
+ String[] propsGettersAndSetters,
+ Class> modelClass,
+ Object bindings,
+ Object model,
+ String[] methodsAndSignatures
+ ) throws IllegalStateException, SecurityException {
+ for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
+ try {
+ Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]);
+ bind(bindings, model, propsGettersAndSetters[i],
+ propsGettersAndSetters[i + 1],
+ propsGettersAndSetters[i + 2],
+ getter.getReturnType().isPrimitive(),
+ List.class.isAssignableFrom(getter.getReturnType()));
+ } catch (NoSuchMethodException ex) {
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+ for (int i = 0; i < methodsAndSignatures.length; i += 2) {
+ expose(
+ bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1]);
+ }
+ }
}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
--- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Fri May 17 10:27:41 2013 +0200
@@ -20,23 +20,30 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
+import java.io.StringWriter;
import java.io.Writer;
+import java.lang.annotation.AnnotationTypeMismatchException;
+import java.lang.annotation.IncompleteAnnotationException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.WeakHashMap;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Completion;
import javax.annotation.processing.Completions;
+import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
@@ -44,13 +51,21 @@
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
import javax.lang.model.type.MirroredTypeException;
+import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
+import org.apidesign.bck2brwsr.htmlpage.api.Model;
import org.apidesign.bck2brwsr.htmlpage.api.On;
+import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
+import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange;
+import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
import org.apidesign.bck2brwsr.htmlpage.api.Page;
import org.apidesign.bck2brwsr.htmlpage.api.Property;
import org.openide.util.lookup.ServiceProvider;
@@ -62,89 +77,70 @@
*/
@ServiceProvider(service=Processor.class)
@SupportedAnnotationTypes({
+ "org.apidesign.bck2brwsr.htmlpage.api.Model",
"org.apidesign.bck2brwsr.htmlpage.api.Page",
+ "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
+ "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
+ "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange",
+ "org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty",
"org.apidesign.bck2brwsr.htmlpage.api.On"
})
public final class PageProcessor extends AbstractProcessor {
+ private final Map models = new WeakHashMap<>();
+ private final Map verify = new WeakHashMap<>();
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
- for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
- Page p = e.getAnnotation(Page.class);
- if (p == null) {
- continue;
- }
- PackageElement pe = (PackageElement)e.getEnclosingElement();
- String pkg = pe.getQualifiedName().toString();
-
- ProcessPage pp;
- try {
- InputStream is = openStream(pkg, p.xhtml());
- pp = ProcessPage.readPage(is);
- is.close();
- } catch (IOException iOException) {
- processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
- return false;
- }
- Writer w;
- String className = p.className();
- if (className.isEmpty()) {
- int indx = p.xhtml().indexOf('.');
- className = p.xhtml().substring(0, indx);
- }
- try {
- FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
- w = new OutputStreamWriter(java.openOutputStream());
- try {
- w.append("package " + pkg + ";\n");
- w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
- w.append("final class ").append(className).append(" {\n");
- w.append(" private boolean locked;\n");
- if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
- return false;
- }
- for (String id : pp.ids()) {
- String tag = pp.tagNameForId(id);
- String type = type(tag);
- w.append(" ").append("public final ").
- append(type).append(' ').append(cnstnt(id)).append(" = new ").
- append(type).append("(\"").append(id).append("\");\n");
- }
- List propsGetSet = new ArrayList();
- Map> propsDeps = new HashMap>();
- generateComputedProperties(w, e.getEnclosedElements(), propsGetSet, propsDeps);
- generateProperties(w, p.properties(), propsGetSet, propsDeps);
- w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
- if (!propsGetSet.isEmpty()) {
- w.write("public " + className + " applyBindings() {\n");
- w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
- w.write(className + ".class, this, ");
- w.write("new String[] {\n");
- String sep = "";
- for (String n : propsGetSet) {
- w.write(sep);
- if (n == null) {
- w.write(" null");
- } else {
- w.write(" \"" + n + "\"");
- }
- sep = ",\n";
- }
- w.write("\n });\n return this;\n}\n");
-
- w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
- w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
- w.write("}\n");
- }
- w.append("}\n");
- } finally {
- w.close();
- }
- } catch (IOException ex) {
- processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
- return false;
+ boolean ok = true;
+ for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
+ if (!processModel(e)) {
+ ok = false;
}
}
- return true;
+ for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
+ if (!processPage(e)) {
+ ok = false;
+ }
+ }
+ if (roundEnv.processingOver()) {
+ models.clear();
+ for (Map.Entry entry : verify.entrySet()) {
+ TypeElement te = (TypeElement)entry.getKey();
+ String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
+ Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
+ if (finalElem == null) {
+ continue;
+ }
+ Prprt[] props;
+ Model m = finalElem.getAnnotation(Model.class);
+ if (m != null) {
+ props = Prprt.wrap(processingEnv, finalElem, m.properties());
+ } else {
+ Page p = finalElem.getAnnotation(Page.class);
+ props = Prprt.wrap(processingEnv, finalElem, p.properties());
+ }
+ for (Prprt p : props) {
+ boolean[] isModel = { false };
+ boolean[] isEnum = { false };
+ boolean[] isPrimitive = { false };
+ String t = checkType(p, isModel, isEnum, isPrimitive);
+ if (isEnum[0]) {
+ continue;
+ }
+ if (isPrimitive[0]) {
+ continue;
+ }
+ if (isModel[0]) {
+ continue;
+ }
+ if ("java.lang.String".equals(t)) {
+ continue;
+ }
+ error("The type " + t + " should be defined by @Model annotation", entry.getKey());
+ }
+ }
+ verify.clear();
+ }
+ return ok;
}
private InputStream openStream(String pkg, String name) throws IOException {
@@ -157,6 +153,236 @@
}
}
+ private void error(String msg, Element e) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
+ }
+
+ private boolean processModel(Element e) {
+ boolean ok = true;
+ Model m = e.getAnnotation(Model.class);
+ if (m == null) {
+ return true;
+ }
+ String pkg = findPkgName(e);
+ Writer w;
+ String className = m.className();
+ models.put(e, className);
+ try {
+ StringWriter body = new StringWriter();
+ List propsGetSet = new ArrayList<>();
+ List functions = new ArrayList<>();
+ Map> propsDeps = new HashMap<>();
+ Map> functionDeps = new HashMap<>();
+ Prprt[] props = createProps(e, m.properties());
+
+ if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
+ ok = false;
+ }
+ if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
+ ok = false;
+ }
+ if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
+ ok = false;
+ }
+ if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
+ ok = false;
+ }
+ FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
+ w = new OutputStreamWriter(java.openOutputStream());
+ try {
+ w.append("package " + pkg + ";\n");
+ w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
+ w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
+ w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
+ w.append("final class ").append(className).append(" implements Cloneable {\n");
+ w.append(" private boolean locked;\n");
+ w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
+ w.append(body.toString());
+ w.append(" private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
+ w.append(" public ").append(className).append("() {\n");
+ w.append(" intKnckt();\n");
+ w.append(" };\n");
+ w.append(" private void intKnckt() {\n");
+ w.append(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
+ writeStringArray(propsGetSet, w);
+ w.append(", ");
+ writeStringArray(functions, w);
+ w.append(" );\n");
+ w.append(" };\n");
+ w.append(" ").append(className).append("(Object json) {\n");
+ int values = 0;
+ for (int i = 0; i < propsGetSet.size(); i += 4) {
+ Prprt p = findPrprt(props, propsGetSet.get(i));
+ if (p == null) {
+ continue;
+ }
+ values++;
+ }
+ w.append(" Object[] ret = new Object[" + values + "];\n");
+ w.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
+ for (int i = 0; i < propsGetSet.size(); i += 4) {
+ Prprt p = findPrprt(props, propsGetSet.get(i));
+ if (p == null) {
+ continue;
+ }
+ w.append(" \"").append(propsGetSet.get(i)).append("\",\n");
+ }
+ w.append(" }, ret);\n");
+ for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
+ final String pn = propsGetSet.get(i);
+ Prprt p = findPrprt(props, pn);
+ if (p == null) {
+ continue;
+ }
+ boolean[] isModel = { false };
+ boolean[] isEnum = { false };
+ boolean isPrimitive[] = { false };
+ String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
+ if (p.array()) {
+ w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
+ w.append(" for (Object e : ((Object[])ret[" + cnt + "])) {\n");
+ if (isModel[0]) {
+ w.append(" this.prop_").append(pn).append(".add(new ");
+ w.append(type).append("(e));\n");
+ } else if (isEnum[0]) {
+ w.append(" this.prop_").append(pn);
+ w.append(".add(e == null ? null : ");
+ w.append(type).append(".valueOf((String)e));\n");
+ } else {
+ if (isPrimitive(type)) {
+ w.append(" this.prop_").append(pn).append(".add(((Number)e).");
+ w.append(type).append("Value());\n");
+ } else {
+ w.append(" this.prop_").append(pn).append(".add((");
+ w.append(type).append(")e);\n");
+ }
+ }
+ w.append(" }\n");
+ w.append("}\n");
+ } else {
+ if (isEnum[0]) {
+ w.append(" this.prop_").append(pn);
+ w.append(" = ret[" + cnt + "] == null ? null : ");
+ w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
+ } else if (isPrimitive(type)) {
+ w.append(" this.prop_").append(pn);
+ w.append(" = ((Number)").append("ret[" + cnt + "]).");
+ w.append(type).append("Value();\n");
+ } else {
+ w.append(" this.prop_").append(pn);
+ w.append(" = (").append(type).append(')');
+ w.append("ret[" + cnt + "];\n");
+ }
+ }
+ cnt++;
+ }
+ w.append(" intKnckt();\n");
+ w.append(" };\n");
+ writeToString(props, w);
+ writeClone(className, props, w);
+ w.append("}\n");
+ } finally {
+ w.close();
+ }
+ } catch (IOException ex) {
+ error("Can't create " + className + ".java", e);
+ return false;
+ }
+ return ok;
+ }
+
+ private boolean processPage(Element e) {
+ boolean ok = true;
+ Page p = e.getAnnotation(Page.class);
+ if (p == null) {
+ return true;
+ }
+ String pkg = findPkgName(e);
+
+ ProcessPage pp;
+ try (InputStream is = openStream(pkg, p.xhtml())) {
+ pp = ProcessPage.readPage(is);
+ is.close();
+ } catch (IOException iOException) {
+ error("Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
+ ok = false;
+ pp = null;
+ }
+ Writer w;
+ String className = p.className();
+ if (className.isEmpty()) {
+ int indx = p.xhtml().indexOf('.');
+ className = p.xhtml().substring(0, indx);
+ }
+ try {
+ StringWriter body = new StringWriter();
+ List propsGetSet = new ArrayList<>();
+ List functions = new ArrayList<>();
+ Map> propsDeps = new HashMap<>();
+ Map> functionDeps = new HashMap<>();
+
+ Prprt[] props = createProps(e, p.properties());
+ if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
+ ok = false;
+ }
+ if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
+ ok = false;
+ }
+ if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
+ ok = false;
+ }
+ if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
+ ok = false;
+ }
+ if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) {
+ ok = false;
+ }
+
+ FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
+ w = new OutputStreamWriter(java.openOutputStream());
+ try {
+ w.append("package " + pkg + ";\n");
+ w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
+ w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
+ w.append("final class ").append(className).append(" {\n");
+ w.append(" private boolean locked;\n");
+ if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
+ ok = false;
+ } else {
+ if (pp != null) for (String id : pp.ids()) {
+ String tag = pp.tagNameForId(id);
+ String type = type(tag);
+ w.append(" ").append("public final ").
+ append(type).append(' ').append(cnstnt(id)).append(" = new ").
+ append(type).append("(\"").append(id).append("\");\n");
+ }
+ }
+ w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
+ w.append(body.toString());
+ if (!propsGetSet.isEmpty()) {
+ w.write("public " + className + " applyBindings() {\n");
+ w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
+ w.write(className + ".class, this, ");
+ writeStringArray(propsGetSet, w);
+ w.append(", ");
+ writeStringArray(functions, w);
+ w.write(");\n return this;\n}\n");
+
+ w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
+ w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
+ w.write("}\n");
+ }
+ w.append("}\n");
+ } finally {
+ w.close();
+ }
+ } catch (IOException ex) {
+ error("Can't create " + className + ".java", e);
+ return false;
+ }
+ return ok;
+ }
+
private static String type(String tag) {
if (tag.equals("title")) {
return "Title";
@@ -177,12 +403,13 @@
}
private static String cnstnt(String id) {
- return id.toUpperCase(Locale.ENGLISH).replace('.', '_').replace('-', '_');
+ return id.replace('.', '_').replace('-', '_');
}
private boolean initializeOnClick(
String className, TypeElement type, Writer w, ProcessPage pp
) throws IOException {
+ boolean ok = true;
TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
{ //for (Element clazz : pe.getEnclosedElements()) {
// if (clazz.getKind() != ElementKind.CLASS) {
@@ -195,46 +422,27 @@
On oc = method.getAnnotation(On.class);
if (oc != null) {
for (String id : oc.id()) {
+ if (pp == null) {
+ error("id = " + id + " not found in HTML page.", method);
+ ok = false;
+ continue;
+ }
if (pp.tagNameForId(id) == null) {
- processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
- return false;
+ error("id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
+ ok = false;
+ continue;
}
ExecutableElement ee = (ExecutableElement)method;
- StringBuilder params = new StringBuilder();
- {
- boolean first = true;
- for (VariableElement ve : ee.getParameters()) {
- if (!first) {
- params.append(", ");
- }
- first = false;
- if (ve.asType() == stringType) {
- params.append('"').append(id).append('"');
- continue;
- }
- String rn = ve.asType().toString();
- int last = rn.lastIndexOf('.');
- if (last >= 0) {
- rn = rn.substring(last + 1);
- }
- if (rn.equals(className)) {
- params.append(className).append(".this");
- continue;
- }
- processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
- "@On method can only accept String or " + className + " arguments",
- ee
- );
- return false;
- }
- }
+ CharSequence params = wrapParams(ee, id, className, "ev", null);
if (!ee.getModifiers().contains(Modifier.STATIC)) {
- processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
- return false;
+ error("@On method has to be static", ee);
+ ok = false;
+ continue;
}
if (ee.getModifiers().contains(Modifier.PRIVATE)) {
- processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
- return false;
+ error("@On method can't be private", ee);
+ ok = false;
+ continue;
}
w.append(" OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
@@ -252,10 +460,10 @@
}
w.append(" }\n");
if (dispatchCnt > 0) {
- w.append("class OnDispatch implements Runnable {\n");
+ w.append("class OnDispatch implements OnHandler {\n");
w.append(" private final int dispatch;\n");
w.append(" OnDispatch(int d) { dispatch = d; }\n");
- w.append(" public void run() {\n");
+ w.append(" public void onEvent(Object ev) {\n");
w.append(" switch (dispatch) {\n");
w.append(dispatch);
w.append(" }\n");
@@ -265,7 +473,7 @@
}
- return true;
+ return ok;
}
@Override
@@ -279,8 +487,7 @@
Element cls = findClass(element);
Page p = cls.getAnnotation(Page.class);
- PackageElement pe = (PackageElement) cls.getEnclosingElement();
- String pkg = pe.getQualifiedName().toString();
+ String pkg = findPkgName(cls);
ProcessPage pp;
try {
InputStream is = openStream(pkg, p.xhtml());
@@ -290,7 +497,7 @@
return Collections.emptyList();
}
- List cc = new ArrayList();
+ List cc = new ArrayList<>();
userText = userText.substring(1);
for (String id : pp.ids()) {
if (id.startsWith(userText)) {
@@ -311,44 +518,89 @@
return e.getEnclosingElement();
}
- private static void generateProperties(
- Writer w, Property[] properties, Collection props,
- Map> deps
+ private boolean generateProperties(
+ Element where,
+ Writer w, Prprt[] properties,
+ Collection props,
+ Map> deps,
+ Map> functionDeps
) throws IOException {
- for (Property p : properties) {
- final String tn = typeName(p);
- String[] gs = toGetSet(p.name(), tn);
+ boolean ok = true;
+ for (Prprt p : properties) {
+ final String tn;
+ tn = typeName(where, p);
+ String[] gs = toGetSet(p.name(), tn, p.array());
- w.write("private " + tn + " prop_" + p.name() + ";\n");
- w.write("public " + tn + " " + gs[0] + "() {\n");
- w.write(" if (locked) throw new IllegalStateException();\n");
- w.write(" return prop_" + p.name() + ";\n");
- w.write("}\n");
- w.write("public void " + gs[1] + "(" + tn + " v) {\n");
- w.write(" if (locked) throw new IllegalStateException();\n");
- w.write(" prop_" + p.name() + " = v;\n");
- w.write(" if (ko != null) {\n");
- w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
- final Collection dependants = deps.get(p.name());
- if (dependants != null) {
- for (String depProp : dependants) {
- w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
+ if (p.array()) {
+ w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
+ + p.name() + "\"");
+ Collection dependants = deps.get(p.name());
+ if (dependants != null) {
+ for (String depProp : dependants) {
+ w.write(", ");
+ w.write('\"');
+ w.write(depProp);
+ w.write('\"');
+ }
}
+ w.write(")");
+
+ dependants = functionDeps.get(p.name());
+ if (dependants != null) {
+ w.write(".onChange(new Runnable() { public void run() {\n");
+ for (String call : dependants) {
+ w.append(call);
+ }
+ w.write("}})");
+ }
+ w.write(";\n");
+
+ w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
+ w.write(" if (locked) throw new IllegalStateException();\n");
+ w.write(" prop_" + p.name() + ".assign(ko);\n");
+ w.write(" return prop_" + p.name() + ";\n");
+ w.write("}\n");
+ } else {
+ w.write("private " + tn + " prop_" + p.name() + ";\n");
+ w.write("public " + tn + " " + gs[0] + "() {\n");
+ w.write(" if (locked) throw new IllegalStateException();\n");
+ w.write(" return prop_" + p.name() + ";\n");
+ w.write("}\n");
+ w.write("public void " + gs[1] + "(" + tn + " v) {\n");
+ w.write(" if (locked) throw new IllegalStateException();\n");
+ w.write(" prop_" + p.name() + " = v;\n");
+ w.write(" if (ko != null) {\n");
+ w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
+ Collection dependants = deps.get(p.name());
+ if (dependants != null) {
+ for (String depProp : dependants) {
+ w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
+ }
+ }
+ w.write(" }\n");
+ dependants = functionDeps.get(p.name());
+ if (dependants != null) {
+ for (String call : dependants) {
+ w.append(call);
+ }
+ }
+ w.write("}\n");
}
- w.write(" }\n");
- w.write("}\n");
props.add(p.name());
props.add(gs[2]);
props.add(gs[3]);
props.add(gs[0]);
}
+ return ok;
}
private boolean generateComputedProperties(
- Writer w, Collection extends Element> arr, Collection props,
+ Writer w, Prprt[] fixedProps,
+ Collection extends Element> arr, Collection props,
Map> deps
) throws IOException {
+ boolean ok = true;
for (Element e : arr) {
if (e.getKind() != ElementKind.METHOD) {
continue;
@@ -357,30 +609,43 @@
continue;
}
ExecutableElement ee = (ExecutableElement)e;
- final String tn = ee.getReturnType().toString();
+ final TypeMirror rt = ee.getReturnType();
+ final Types tu = processingEnv.getTypeUtils();
+ TypeMirror ert = tu.erasure(rt);
+ String tn = fqn(ert, ee);
+ boolean array = false;
+ if (tn.equals("java.util.List")) {
+ array = true;
+ }
+
final String sn = ee.getSimpleName().toString();
- String[] gs = toGetSet(sn, tn);
+ String[] gs = toGetSet(sn, tn, array);
w.write("public " + tn + " " + gs[0] + "() {\n");
w.write(" if (locked) throw new IllegalStateException();\n");
int arg = 0;
for (VariableElement pe : ee.getParameters()) {
final String dn = pe.getSimpleName().toString();
- final String dt = pe.asType().toString();
- String[] call = toGetSet(dn, dt);
+
+ if (!verifyPropName(pe, dn, fixedProps)) {
+ ok = false;
+ }
+
+ final String dt = fqn(pe.asType(), ee);
+ String[] call = toGetSet(dn, dt, false);
w.write(" " + dt + " arg" + (++arg) + " = ");
w.write(call[0] + "();\n");
Collection depends = deps.get(dn);
if (depends == null) {
- depends = new LinkedHashSet();
+ depends = new LinkedHashSet<>();
deps.put(dn, depends);
}
depends.add(sn);
}
w.write(" try {\n");
w.write(" locked = true;\n");
- w.write(" return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "(");
+ w.write(" return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
String sep = "";
for (int i = 1; i <= arg; i++) {
w.write(sep);
@@ -392,17 +657,17 @@
w.write(" locked = false;\n");
w.write(" }\n");
w.write("}\n");
-
+
props.add(e.getSimpleName().toString());
props.add(gs[2]);
props.add(null);
props.add(gs[0]);
}
- return true;
+ return ok;
}
- private static String[] toGetSet(String name, String type) {
+ private static String[] toGetSet(String name, String type, boolean array) {
String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
if ("int".equals(type)) {
@@ -417,6 +682,14 @@
bck2brwsrType = "Z";
}
final String nu = n.replace('.', '_');
+ if (array) {
+ return new String[] {
+ "get" + n,
+ null,
+ "get" + nu + "__Ljava_util_List_2",
+ null
+ };
+ }
return new String[]{
pref + n,
"set" + n,
@@ -425,11 +698,702 @@
};
}
- private static String typeName(Property p) {
- try {
- return p.type().getName();
- } catch (MirroredTypeException ex) {
- return ex.getTypeMirror().toString();
+ private String typeName(Element where, Prprt p) {
+ String ret;
+ boolean[] isModel = { false };
+ boolean[] isEnum = { false };
+ boolean isPrimitive[] = { false };
+ ret = checkType(p, isModel, isEnum, isPrimitive);
+ if (p.array()) {
+ String bt = findBoxedType(ret);
+ if (bt != null) {
+ return bt;
+ }
+ }
+ return ret;
+ }
+
+ private static String findBoxedType(String ret) {
+ if (ret.equals("boolean")) {
+ return Boolean.class.getName();
+ }
+ if (ret.equals("byte")) {
+ return Byte.class.getName();
+ }
+ if (ret.equals("short")) {
+ return Short.class.getName();
+ }
+ if (ret.equals("char")) {
+ return Character.class.getName();
+ }
+ if (ret.equals("int")) {
+ return Integer.class.getName();
+ }
+ if (ret.equals("long")) {
+ return Long.class.getName();
+ }
+ if (ret.equals("float")) {
+ return Float.class.getName();
+ }
+ if (ret.equals("double")) {
+ return Double.class.getName();
+ }
+ return null;
+ }
+
+ private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
+ StringBuilder sb = new StringBuilder();
+ String sep = "";
+ for (Prprt Prprt : existingProps) {
+ if (Prprt.name().equals(propName)) {
+ return true;
+ }
+ sb.append(sep);
+ sb.append('"');
+ sb.append(Prprt.name());
+ sb.append('"');
+ sep = ", ";
+ }
+ error(
+ propName + " is not one of known properties: " + sb
+ , e
+ );
+ return false;
+ }
+
+ private static String findPkgName(Element e) {
+ for (;;) {
+ if (e.getKind() == ElementKind.PACKAGE) {
+ return ((PackageElement)e).getQualifiedName().toString();
+ }
+ e = e.getEnclosingElement();
}
}
+
+ private boolean generateFunctions(
+ Element clazz, StringWriter body, String className,
+ List extends Element> enclosedElements, List functions
+ ) {
+ for (Element m : enclosedElements) {
+ if (m.getKind() != ElementKind.METHOD) {
+ continue;
+ }
+ ExecutableElement e = (ExecutableElement)m;
+ OnFunction onF = e.getAnnotation(OnFunction.class);
+ if (onF == null) {
+ continue;
+ }
+ if (!e.getModifiers().contains(Modifier.STATIC)) {
+ error("@OnFunction method needs to be static", e);
+ return false;
+ }
+ if (e.getModifiers().contains(Modifier.PRIVATE)) {
+ error("@OnFunction method cannot be private", e);
+ return false;
+ }
+ if (e.getReturnType().getKind() != TypeKind.VOID) {
+ error("@OnFunction method should return void", e);
+ return false;
+ }
+ String n = e.getSimpleName().toString();
+ body.append("private void ").append(n).append("(Object data, Object ev) {\n");
+ body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
+ body.append(wrapParams(e, null, className, "ev", "data"));
+ body.append(");\n");
+ body.append("}\n");
+
+ functions.add(n);
+ functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
+ }
+ return true;
+ }
+
+ private boolean generateOnChange(Element clazz, Map> propDeps,
+ Prprt[] properties, String className,
+ Map> functionDeps
+ ) {
+ for (Element m : clazz.getEnclosedElements()) {
+ if (m.getKind() != ElementKind.METHOD) {
+ continue;
+ }
+ ExecutableElement e = (ExecutableElement) m;
+ OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
+ if (onPC == null) {
+ continue;
+ }
+ for (String pn : onPC.value()) {
+ if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
+ error("No Prprt named '" + pn + "' in the model", clazz);
+ return false;
+ }
+ }
+ if (!e.getModifiers().contains(Modifier.STATIC)) {
+ error("@OnPrprtChange method needs to be static", e);
+ return false;
+ }
+ if (e.getModifiers().contains(Modifier.PRIVATE)) {
+ error("@OnPrprtChange method cannot be private", e);
+ return false;
+ }
+ if (e.getReturnType().getKind() != TypeKind.VOID) {
+ error("@OnPrprtChange method should return void", e);
+ return false;
+ }
+ String n = e.getSimpleName().toString();
+
+
+ for (String pn : onPC.value()) {
+ StringBuilder call = new StringBuilder();
+ call.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
+ call.append(wrapPropName(e, className, "name", pn));
+ call.append(");\n");
+
+ Collection change = functionDeps.get(pn);
+ if (change == null) {
+ change = new ArrayList<>();
+ functionDeps.put(pn, change);
+ }
+ change.add(call.toString());
+ for (String dpn : findDerivedFrom(propDeps, pn)) {
+ change = functionDeps.get(dpn);
+ if (change == null) {
+ change = new ArrayList<>();
+ functionDeps.put(dpn, change);
+ }
+ change.add(call.toString());
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean generateReceive(
+ Element clazz, StringWriter body, String className,
+ List extends Element> enclosedElements, List functions
+ ) {
+ for (Element m : enclosedElements) {
+ if (m.getKind() != ElementKind.METHOD) {
+ continue;
+ }
+ ExecutableElement e = (ExecutableElement)m;
+ OnReceive onR = e.getAnnotation(OnReceive.class);
+ if (onR == null) {
+ continue;
+ }
+ if (!e.getModifiers().contains(Modifier.STATIC)) {
+ error("@OnReceive method needs to be static", e);
+ return false;
+ }
+ if (e.getModifiers().contains(Modifier.PRIVATE)) {
+ error("@OnReceive method cannot be private", e);
+ return false;
+ }
+ if (e.getReturnType().getKind() != TypeKind.VOID) {
+ error("@OnReceive method should return void", e);
+ return false;
+ }
+ String modelClass = null;
+ boolean expectsList = false;
+ List args = new ArrayList<>();
+ {
+ for (VariableElement ve : e.getParameters()) {
+ TypeMirror modelType = null;
+ if (ve.asType().toString().equals(className)) {
+ args.add(className + ".this");
+ } else if (isModel(ve.asType())) {
+ modelType = ve.asType();
+ } else if (ve.asType().getKind() == TypeKind.ARRAY) {
+ modelType = ((ArrayType)ve.asType()).getComponentType();
+ expectsList = true;
+ }
+ if (modelType != null) {
+ if (modelClass != null) {
+ error("There can be only one model class among arguments", e);
+ } else {
+ modelClass = modelType.toString();
+ if (expectsList) {
+ args.add("arr");
+ } else {
+ args.add("arr[0]");
+ }
+ }
+ }
+ }
+ }
+ if (modelClass == null) {
+ error("The method needs to have one @Model class as parameter", e);
+ }
+ String n = e.getSimpleName().toString();
+ body.append("public void ").append(n).append("(");
+ StringBuilder assembleURL = new StringBuilder();
+ String jsonpVarName = null;
+ {
+ String sep = "";
+ boolean skipJSONP = onR.jsonp().isEmpty();
+ for (String p : findParamNames(e, onR.url(), assembleURL)) {
+ if (!skipJSONP && p.equals(onR.jsonp())) {
+ skipJSONP = true;
+ jsonpVarName = p;
+ continue;
+ }
+ body.append(sep);
+ body.append("String ").append(p);
+ sep = ", ";
+ }
+ if (!skipJSONP) {
+ error(
+ "Name of jsonp attribute ('" + onR.jsonp() +
+ "') is not used in url attribute '" + onR.url() + "'", e
+ );
+ }
+ }
+ body.append(") {\n");
+ body.append(" final Object[] result = { null };\n");
+ body.append(
+ " class ProcessResult implements Runnable {\n" +
+ " @Override\n" +
+ " public void run() {\n" +
+ " Object value = result[0];\n");
+ body.append(
+ " " + modelClass + "[] arr;\n");
+ body.append(
+ " if (value instanceof Object[]) {\n" +
+ " Object[] data = ((Object[])value);\n" +
+ " arr = new " + modelClass + "[data.length];\n" +
+ " for (int i = 0; i < data.length; i++) {\n" +
+ " arr[i] = new " + modelClass + "(data[i]);\n" +
+ " }\n" +
+ " } else {\n" +
+ " arr = new " + modelClass + "[1];\n" +
+ " arr[0] = new " + modelClass + "(value);\n" +
+ " }\n"
+ );
+ {
+ body.append(clazz.getSimpleName()).append(".").append(n).append("(");
+ String sep = "";
+ for (String arg : args) {
+ body.append(sep);
+ body.append(arg);
+ sep = ", ";
+ }
+ body.append(");\n");
+ }
+ body.append(
+ " }\n" +
+ " }\n"
+ );
+ body.append(" ProcessResult pr = new ProcessResult();\n");
+ if (jsonpVarName != null) {
+ body.append(" String ").append(jsonpVarName).
+ append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n");
+ }
+ body.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n ");
+ body.append(assembleURL);
+ body.append(", result, pr, ").append(jsonpVarName).append("\n );\n");
+// body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
+// body.append(wrapParams(e, null, className, "ev", "data"));
+// body.append(");\n");
+ body.append("}\n");
+ }
+ return true;
+ }
+
+ private CharSequence wrapParams(
+ ExecutableElement ee, String id, String className, String evName, String dataName
+ ) {
+ TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
+ StringBuilder params = new StringBuilder();
+ boolean first = true;
+ for (VariableElement ve : ee.getParameters()) {
+ if (!first) {
+ params.append(", ");
+ }
+ first = false;
+ String toCall = null;
+ if (ve.asType() == stringType) {
+ if (ve.getSimpleName().contentEquals("id")) {
+ params.append('"').append(id).append('"');
+ continue;
+ }
+ toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
+ }
+ if (ve.asType().getKind() == TypeKind.DOUBLE) {
+ toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
+ }
+ if (ve.asType().getKind() == TypeKind.INT) {
+ toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
+ }
+ if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
+ toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
+ }
+
+ if (toCall != null) {
+ params.append(toCall);
+ if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
+ params.append(dataName);
+ params.append(", null");
+ } else {
+ if (evName == null) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Unexpected string parameter name.");
+ if (dataName != null) {
+ sb.append(" Try \"").append(dataName).append("\"");
+ }
+ error(sb.toString(), ee);
+ }
+ params.append(evName);
+ params.append(", \"");
+ params.append(ve.getSimpleName().toString());
+ params.append("\"");
+ }
+ params.append(")");
+ continue;
+ }
+ String rn = fqn(ve.asType(), ee);
+ int last = rn.lastIndexOf('.');
+ if (last >= 0) {
+ rn = rn.substring(last + 1);
+ }
+ if (rn.equals(className)) {
+ params.append(className).append(".this");
+ continue;
+ }
+ error(
+ "@On method can only accept String named 'id' or " + className + " arguments",
+ ee
+ );
+ }
+ return params;
+ }
+
+
+ private CharSequence wrapPropName(
+ ExecutableElement ee, String className, String propName, String propValue
+ ) {
+ TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
+ StringBuilder params = new StringBuilder();
+ boolean first = true;
+ for (VariableElement ve : ee.getParameters()) {
+ if (!first) {
+ params.append(", ");
+ }
+ first = false;
+ if (ve.asType() == stringType) {
+ if (propName != null && ve.getSimpleName().contentEquals(propName)) {
+ params.append('"').append(propValue).append('"');
+ } else {
+ error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
+ }
+ continue;
+ }
+ String rn = fqn(ve.asType(), ee);
+ int last = rn.lastIndexOf('.');
+ if (last >= 0) {
+ rn = rn.substring(last + 1);
+ }
+ if (rn.equals(className)) {
+ params.append(className).append(".this");
+ continue;
+ }
+ error(
+ "@OnPrprtChange method can only accept String or " + className + " arguments",
+ ee);
+ }
+ return params;
+ }
+
+ private boolean isModel(TypeMirror tm) {
+ final Element e = processingEnv.getTypeUtils().asElement(tm);
+ if (e == null) {
+ return false;
+ }
+ for (Element ch : e.getEnclosedElements()) {
+ if (ch.getKind() == ElementKind.METHOD) {
+ ExecutableElement ee = (ExecutableElement)ch;
+ if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
+ return true;
+ }
+ }
+ }
+ return models.values().contains(e.getSimpleName().toString());
+ }
+
+ private void writeStringArray(List strings, Writer w) throws IOException {
+ w.write("new String[] {\n");
+ String sep = "";
+ for (String n : strings) {
+ w.write(sep);
+ if (n == null) {
+ w.write(" null");
+ } else {
+ w.write(" \"" + n + "\"");
+ }
+ sep = ",\n";
+ }
+ w.write("\n }");
+ }
+
+ private void writeToString(Prprt[] props, Writer w) throws IOException {
+ w.write(" public String toString() {\n");
+ w.write(" StringBuilder sb = new StringBuilder();\n");
+ w.write(" sb.append('{');\n");
+ String sep = "";
+ for (Prprt p : props) {
+ w.write(sep);
+ w.append(" sb.append('\"').append(\"" + p.name() + "\")");
+ w.append(".append('\"').append(\":\");\n");
+ w.append(" sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
+ w.append(p.name()).append("));\n");
+ sep = " sb.append(',');\n";
+ }
+ w.write(" sb.append('}');\n");
+ w.write(" return sb.toString();\n");
+ w.write(" }\n");
+ }
+ private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
+ w.write(" public " + className + " clone() {\n");
+ w.write(" " + className + " ret = new " + className + "();\n");
+ for (Prprt p : props) {
+ if (!p.array()) {
+ boolean isModel[] = { false };
+ boolean isEnum[] = { false };
+ boolean isPrimitive[] = { false };
+ checkType(p, isModel, isEnum, isPrimitive);
+ if (!isModel[0]) {
+ w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
+ continue;
+ }
+ w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
+ } else {
+ w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
+ }
+ }
+
+ w.write(" return ret;\n");
+ w.write(" }\n");
+ }
+
+ private String inPckName(Element e) {
+ StringBuilder sb = new StringBuilder();
+ while (e.getKind() != ElementKind.PACKAGE) {
+ if (sb.length() == 0) {
+ sb.append(e.getSimpleName());
+ } else {
+ sb.insert(0, '.');
+ sb.insert(0, e.getSimpleName());
+ }
+ e = e.getEnclosingElement();
+ }
+ return sb.toString();
+ }
+
+ private String fqn(TypeMirror pt, Element relative) {
+ if (pt.getKind() == TypeKind.ERROR) {
+ final Elements eu = processingEnv.getElementUtils();
+ PackageElement pckg = eu.getPackageOf(relative);
+ return pckg.getQualifiedName() + "." + pt.toString();
+ }
+ return pt.toString();
+ }
+
+ private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
+ TypeMirror tm;
+ try {
+ String ret = p.typeName(processingEnv);
+ TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
+ if (e == null) {
+ isModel[0] = true;
+ isEnum[0] = false;
+ isPrimitive[0] = false;
+ return ret;
+ }
+ tm = e.asType();
+ } catch (MirroredTypeException ex) {
+ tm = ex.getTypeMirror();
+ }
+ tm = processingEnv.getTypeUtils().erasure(tm);
+ isPrimitive[0] = tm.getKind().isPrimitive();
+ final Element e = processingEnv.getTypeUtils().asElement(tm);
+ final Model m = e == null ? null : e.getAnnotation(Model.class);
+
+ String ret;
+ if (m != null) {
+ ret = findPkgName(e) + '.' + m.className();
+ isModel[0] = true;
+ models.put(e, m.className());
+ } else if (findModelForMthd(e)) {
+ ret = ((TypeElement)e).getQualifiedName().toString();
+ isModel[0] = true;
+ } else {
+ ret = tm.toString();
+ }
+ TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
+ enm = processingEnv.getTypeUtils().erasure(enm);
+ isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
+ return ret;
+ }
+
+ private static boolean findModelForMthd(Element clazz) {
+ if (clazz == null) {
+ return false;
+ }
+ for (Element e : clazz.getEnclosedElements()) {
+ if (e.getKind() == ElementKind.METHOD) {
+ ExecutableElement ee = (ExecutableElement)e;
+ if (
+ ee.getSimpleName().contentEquals("modelFor") &&
+ ee.getParameters().isEmpty()
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private Iterable findParamNames(Element e, String url, StringBuilder assembleURL) {
+ List params = new ArrayList<>();
+
+ for (int pos = 0; ;) {
+ int next = url.indexOf('{', pos);
+ if (next == -1) {
+ assembleURL.append('"')
+ .append(url.substring(pos))
+ .append('"');
+ return params;
+ }
+ int close = url.indexOf('}', next);
+ if (close == -1) {
+ error("Unbalanced '{' and '}' in " + url, e);
+ return params;
+ }
+ final String paramName = url.substring(next + 1, close);
+ params.add(paramName);
+ assembleURL.append('"')
+ .append(url.substring(pos, next))
+ .append("\" + ").append(paramName).append(" + ");
+ pos = close + 1;
+ }
+ }
+
+ private static Prprt findPrprt(Prprt[] properties, String propName) {
+ for (Prprt p : properties) {
+ if (propName.equals(p.name())) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ private boolean isPrimitive(String type) {
+ return
+ "int".equals(type) ||
+ "double".equals(type) ||
+ "long".equals(type) ||
+ "short".equals(type) ||
+ "byte".equals(type) ||
+ "float".equals(type);
+ }
+
+ private static Collection findDerivedFrom(Map> propsDeps, String derivedProp) {
+ Set names = new HashSet<>();
+ for (Map.Entry> e : propsDeps.entrySet()) {
+ if (e.getValue().contains(derivedProp)) {
+ names.add(e.getKey());
+ }
+ }
+ return names;
+ }
+
+ private Prprt[] createProps(Element e, Property[] arr) {
+ Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
+ Prprt[] prev = verify.put(e, ret);
+ if (prev != null) {
+ error("Two sets of properties for ", e);
+ }
+ return ret;
+ }
+
+ private static class Prprt {
+ private final Element e;
+ private final AnnotationMirror tm;
+ private final Property p;
+
+ public Prprt(Element e, AnnotationMirror tm, Property p) {
+ this.e = e;
+ this.tm = tm;
+ this.p = p;
+ }
+
+ String name() {
+ return p.name();
+ }
+
+ boolean array() {
+ return p.array();
+ }
+
+ String typeName(ProcessingEnvironment env) {
+ try {
+ return p.type().getName();
+ } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) {
+ for (Object v : getAnnoValues(env)) {
+ String s = v.toString().replace(" ", "");
+ if (s.startsWith("type=") && s.endsWith(".class")) {
+ return s.substring(5, s.length() - 6);
+ }
+ }
+ throw ex;
+ }
+ }
+
+
+ static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
+ if (arr.length == 0) {
+ return new Prprt[0];
+ }
+
+ if (e.getKind() != ElementKind.CLASS) {
+ throw new IllegalStateException("" + e.getKind());
+ }
+ TypeElement te = (TypeElement)e;
+ List extends AnnotationValue> val = null;
+ for (AnnotationMirror an : te.getAnnotationMirrors()) {
+ for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
+ if (entry.getKey().getSimpleName().contentEquals("properties")) {
+ val = (List)entry.getValue().getValue();
+ break;
+ }
+ }
+ }
+ if (val == null || val.size() != arr.length) {
+ pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
+ return new Prprt[0];
+ }
+ Prprt[] ret = new Prprt[arr.length];
+ BIG: for (int i = 0; i < ret.length; i++) {
+ AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
+ ret[i] = new Prprt(e, am, arr[i]);
+
+ }
+ return ret;
+ }
+
+ private List extends Object> getAnnoValues(ProcessingEnvironment pe) {
+ try {
+ Class> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
+ Method m = trees.getMethod("instance", ProcessingEnvironment.class);
+ Object instance = m.invoke(null, pe);
+ m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
+ Object path = m.invoke(instance, e, tm);
+ m = path.getClass().getMethod("getLeaf");
+ Object leaf = m.invoke(path);
+ m = leaf.getClass().getMethod("getArguments");
+ return (List)m.invoke(leaf);
+ } catch (Exception ex) {
+ return Collections.emptyList();
+ }
+ }
+ }
+
}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java
--- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java Fri May 17 10:27:41 2013 +0200
@@ -22,16 +22,11 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-/** Can be used in classes annotated with {@link Page} annotation to
- * define a derived property. Value of derived property is based on values
- * of {@link Property} as enumerated by {@link Page#properties()}.
- *
- * The name of the derived property is the name of the method. The arguments
- * of the method define the property names (from {@link Page#properties()} list)
- * the value of property depends on.
- *
+/**
+ * @deprecated Replaced by new {@link net.java.html.json.ComputedProperty net.java.html.json} API.
* @author Jaroslav Tulach
*/
+@Deprecated
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface ComputedProperty {
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java
--- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java Fri May 17 10:27:41 2013 +0200
@@ -61,14 +61,18 @@
/** Executes given runnable when user performs a "click" on the given
* element.
+ * @param data an array of one element to fill with event parameter (if any)
* @param r the runnable to execute, never null
*/
@JavaScriptBody(
args={ "ev", "r" },
body="var e = window.document.getElementById(this._id());\n"
- + "e[ev._id()] = function() { r.run__V(); };\n"
+ + "e[ev._id()] = function(ev) {\n"
+ + " var d = ev ? ev : null;\n"
+ + " r.onEvent__VLjava_lang_Object_2(d);\n"
+ + "};\n"
)
- final void on(OnEvent ev, Runnable r) {
+ final void on(OnEvent ev, OnHandler r) {
}
/** Shows alert message dialog in a browser.
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Model.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Model.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,38 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @deprecated Replaced by new {@link net.java.html.json.Model net.java.html.json} API.
+ * @author Jaroslav Tulach
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+@Deprecated
+public @interface Model {
+ /** Name of the model class */
+ String className();
+ /** List of properties in the model.
+ */
+ Property[] properties();
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnController.java
--- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnController.java Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnController.java Fri May 17 10:27:41 2013 +0200
@@ -30,14 +30,33 @@
this.arr = arr;
}
+ /** Registers an event handler on associated {@link OnEvent}
+ * and {@link Element}
+ *
+ * @param handler the handler to be called when the event occurs
+ */
+ public void perform(final OnHandler handler) {
+ for (Element e : arr) {
+ e.on(event, handler);
+ }
+ }
+
/** Registers a runnable to be performed on associated {@link OnEvent}
* and {@link Element}.
*
* @see OnEvent#of
*/
- public void perform(Runnable r) {
+ public void perform(final Runnable r) {
+ class W implements OnHandler {
+ @Override
+ public void onEvent(Object event) throws Exception {
+ r.run();
+ }
+ }
+ perform(new W());
+ OnHandler w = new W();
for (Element e : arr) {
- e.on(event, r);
+ e.on(event, w);
}
}
}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnFunction.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnFunction.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,33 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @deprecated Replaced by new {@link net.java.html.json.Function net.java.html.json} API.
+ * @author Jaroslav Tulach
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.SOURCE)
+@Deprecated
+public @interface OnFunction {
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnHandler.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnHandler.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,33 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage.api;
+
+/** Handler to be called when an event in an HTML {@link Page} appears.
+ * @see OnEvent
+ * @see OnController
+ *
+ * @author Jaroslav Tulach
+ */
+public interface OnHandler {
+ /** Called when a DOM event appears
+ *
+ * @param event the event as produced by the browser
+ * @throws Exception execution can throw exception
+ */
+ public void onEvent(Object event) throws Exception;
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnPropertyChange.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnPropertyChange.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,38 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @deprecated Replaced by new {@link net.java.html.json.OnPropertyChange net.java.html.json} API.
+ * @author Jaroslav Tulach
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.METHOD)
+@Deprecated
+public @interface OnPropertyChange {
+ /** Name(s) of the properties. One wishes to observe.
+ *
+ * @return valid java identifier
+ */
+ String[] value();
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnReceive.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnReceive.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,53 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @deprecated Replaced by new {@link net.java.html.json.OnReceive net.java.html.json} API.
+ * @author Jaroslav Tulach
+ * @since 0.6
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.METHOD)
+@Deprecated
+public @interface OnReceive {
+ /** The URL to connect to. Can contain variable names surrounded by '{' and '}'.
+ * Those parameters will then become variables of the associated method.
+ *
+ * @return the (possibly parametrized) url to connect to
+ */
+ String url();
+
+ /** Support for JSONP requires
+ * a callback from the server generated page to a function defined in the
+ * system. The name of such function is usually specified as a property
+ * (of possibly different names). By defining the jsonp attribute
+ * one turns on the JSONP
+ * transmission and specifies the name of the property. The property should
+ * also be used in the {@link #url()} attribute on appropriate place.
+ *
+ * @return name of a property to carry the name of JSONP
+ * callback function.
+ */
+ String jsonp() default "";
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java
--- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Fri May 17 10:27:41 2013 +0200
@@ -20,15 +20,36 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.List;
-/** Represents a property in a generated model of an HTML
- * {@link Page}.
- *
+/**
+ * @deprecated Replaced by new {@link net.java.html.json.Property net.java.html.json} API.
* @author Jaroslav Tulach
*/
@Retention(RetentionPolicy.SOURCE)
@Target({})
+@Deprecated
public @interface Property {
+ /** Name of the property. Will be used to define proper getter and setter
+ * in the associated class.
+ *
+ * @return valid java identifier
+ */
String name();
+
+ /** Type of the property. Can either be primitive type (like int.class,
+ * double.class, etc.), {@link String} or complex model
+ * class (defined by {@link Model} property).
+ *
+ * @return the class of the property
+ */
Class> type();
+
+ /** Is this property an array of the {@link #type()} or a single value?
+ * If the property is an array, only its getter (returning mutable {@link List} of
+ * the boxed {@link #type()}).
+ *
+ * @return true, if this is supposed to be an array of values.
+ */
+ boolean array() default false;
}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js
--- a/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Fri May 17 10:27:41 2013 +0200
@@ -2193,7 +2193,14 @@
else
element[attrName] = attrValue;
} else if (!toRemove) {
- element.setAttribute(attrName, attrValue.toString());
+ try {
+ element.setAttribute(attrName, attrValue.toString());
+ } catch (err) {
+ // ignore for now
+ if (console) {
+ console.log("Can't set attribute " + attrName + " to " + attrValue + " error: " + err);
+ }
+ }
}
// Treat "name" specially - although you can think of it as an attribute, it also needs
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Compile.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Compile.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,203 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+final class Compile implements DiagnosticListener {
+ private final List> errors = new ArrayList<>();
+ private final Map classes;
+ private final String pkg;
+ private final String cls;
+ private final String html;
+
+ private Compile(String html, String code) throws IOException {
+ this.pkg = findPkg(code);
+ this.cls = findCls(code);
+ this.html = html;
+ classes = compile(html, code);
+ }
+
+ /** Performs compilation of given HTML page and associated Java code
+ */
+ public static Compile create(String html, String code) throws IOException {
+ return new Compile(html, code);
+ }
+
+ /** Checks for given class among compiled resources */
+ public byte[] get(String res) {
+ return classes.get(res);
+ }
+
+ /** Obtains errors created during compilation.
+ */
+ public List> getErrors() {
+ List> err = new ArrayList<>();
+ for (Diagnostic extends JavaFileObject> diagnostic : errors) {
+ if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
+ err.add(diagnostic);
+ }
+ }
+ return err;
+ }
+
+ private Map compile(final String html, final String code) throws IOException {
+ StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null);
+
+ final Map class2BAOS = new HashMap<>();
+
+ JavaFileObject file = new SimpleJavaFileObject(URI.create("mem://mem"), Kind.SOURCE) {
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return code;
+ }
+ };
+ final JavaFileObject htmlFile = new SimpleJavaFileObject(URI.create("mem://mem2"), Kind.OTHER) {
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return html;
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ return new ByteArrayInputStream(html.getBytes());
+ }
+ };
+
+ final URI scratch;
+ try {
+ scratch = new URI("mem://mem3");
+ } catch (URISyntaxException ex) {
+ throw new IOException(ex);
+ }
+
+ JavaFileManager jfm = new ForwardingJavaFileManager(sjfm) {
+ @Override
+ public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
+ if (kind == Kind.CLASS) {
+ final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ class2BAOS.put(className.replace('.', '/') + ".class", buffer);
+ return new SimpleJavaFileObject(sibling.toUri(), kind) {
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return buffer;
+ }
+ };
+ }
+
+ if (kind == Kind.SOURCE) {
+ return new SimpleJavaFileObject(scratch/*sibling.toUri()*/, kind) {
+ private final ByteArrayOutputStream data = new ByteArrayOutputStream();
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return data;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ data.close();
+ return new String(data.toByteArray());
+ }
+ };
+ }
+
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
+ if (location == StandardLocation.SOURCE_PATH) {
+ if (packageName.equals(pkg)) {
+ return htmlFile;
+ }
+ }
+
+ return null;
+ }
+
+ };
+
+ ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", "1.7", "-target", "1.7"), null, Arrays.asList(file)).call();
+
+ Map result = new HashMap<>();
+
+ for (Map.Entry e : class2BAOS.entrySet()) {
+ result.put(e.getKey(), e.getValue().toByteArray());
+ }
+
+ return result;
+ }
+
+
+ @Override
+ public void report(Diagnostic extends JavaFileObject> diagnostic) {
+ errors.add(diagnostic);
+ }
+ private static String findPkg(String java) throws IOException {
+ Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE);
+ Matcher m = p.matcher(java);
+ if (!m.find()) {
+ throw new IOException("Can't find package declaration in the java file");
+ }
+ String pkg = m.group(1);
+ return pkg;
+ }
+ private static String findCls(String java) throws IOException {
+ Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE);
+ Matcher m = p.matcher(java);
+ if (!m.find()) {
+ throw new IOException("Can't find package declaration in the java file");
+ }
+ String cls = m.group(1);
+ return cls;
+ }
+
+ String getHtml() {
+ String fqn = "'" + pkg + '.' + cls + "'";
+ return html.replace("'${fqn}'", fqn);
+ }
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypesTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypesTest.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,63 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage;
+
+import org.apidesign.bck2brwsr.core.JavaScriptBody;
+import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
+import org.apidesign.bck2brwsr.vmtest.VMTest;
+import org.testng.annotations.Factory;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class ConvertTypesTest {
+ @JavaScriptBody(args = { "includeSex" }, body = "var json = new Object();"
+ + "json.firstName = 'son';\n"
+ + "json.lastName = 'dj';\n"
+ + "if (includeSex) json.sex = 'MALE';\n"
+ + "return json;"
+ )
+ private static native Object createJSON(boolean includeSex);
+
+ @BrwsrTest
+ public void testConvertToPeople() throws Exception {
+ final Object o = createJSON(true);
+
+ Person p = new Person(o);
+
+ assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
+ assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName();
+ assert Sex.MALE.equals(p.getSex()) : "Sex: " + p.getSex();
+ }
+
+ @BrwsrTest
+ public void testConvertToPeopleWithoutSex() throws Exception {
+ final Object o = createJSON(false);
+
+ Person p = new Person(o);
+
+ assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
+ assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName();
+ assert p.getSex() == null : "No sex: " + p.getSex();
+ }
+
+ @Factory public static Object[] create() {
+ return VMTest.create(ConvertTypesTest.class);
+ }
+}
\ No newline at end of file
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/JSONTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/JSONTest.java Fri May 17 10:27:41 2013 +0200
@@ -0,0 +1,392 @@
+/**
+ * Back 2 Browser Bytecode Translator
+ * Copyright (C) 2012 Jaroslav Tulach
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. Look for COPYING file in the top folder.
+ * If not, see http://opensource.org/licenses/GPL-2.0.
+ */
+package org.apidesign.bck2brwsr.htmlpage;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import org.apidesign.bck2brwsr.core.JavaScriptBody;
+import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
+import org.apidesign.bck2brwsr.htmlpage.api.Page;
+import org.apidesign.bck2brwsr.htmlpage.api.Property;
+import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
+import org.apidesign.bck2brwsr.vmtest.Http;
+import org.apidesign.bck2brwsr.vmtest.VMTest;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+import org.testng.annotations.Factory;
+
+/** Need to verify that models produce reasonable JSON objects.
+ *
+ * @author Jaroslav Tulach
+ */
+@Page(xhtml = "Empty.html", className = "JSONik", properties = {
+ @Property(name = "fetched", type = PersonImpl.class),
+ @Property(name = "fetchedCount", type = int.class),
+ @Property(name = "fetchedSex", type = Sex.class, array = true)
+})
+public class JSONTest {
+ private JSONik js;
+ private Integer orig;
+
+ @Test public void personToString() throws JSONException {
+ Person p = new Person();
+ p.setSex(Sex.MALE);
+ p.setFirstName("Jarda");
+ p.setLastName("Tulach");
+
+ JSONTokener t = new JSONTokener(p.toString());
+ JSONObject o;
+ try {
+ o = new JSONObject(t);
+ } catch (JSONException ex) {
+ throw new AssertionError("Can't parse " + p.toString(), ex);
+ }
+
+ Iterator it = o.sortedKeys();
+ assertEquals(it.next(), "firstName");
+ assertEquals(it.next(), "lastName");
+ assertEquals(it.next(), "sex");
+
+ assertEquals(o.getString("firstName"), "Jarda");
+ assertEquals(o.getString("lastName"), "Tulach");
+ assertEquals(o.getString("sex"), "MALE");
+ }
+
+ @BrwsrTest public void toJSONInABrowser() throws Throwable {
+ Person p = new Person();
+ p.setSex(Sex.MALE);
+ p.setFirstName("Jarda");
+ p.setLastName("Tulach");
+
+ Object json;
+ try {
+ json = parseJSON(p.toString());
+ } catch (Throwable ex) {
+ throw new IllegalStateException("Can't parse " + p).initCause(ex);
+ }
+
+ Person p2 = new Person(json);
+
+ assert p2.getFirstName().equals(p.getFirstName()) :
+ "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName();
+ }
+
+ @Test public void personWithWildCharactersAndNulls() throws JSONException {
+ Person p = new Person();
+ p.setFirstName("'\"\n");
+ p.setLastName("\t\r\u0002");
+
+ JSONTokener t = new JSONTokener(p.toString());
+ JSONObject o;
+ try {
+ o = new JSONObject(t);
+ } catch (JSONException ex) {
+ throw new AssertionError("Can't parse " + p.toString(), ex);
+ }
+
+ Iterator it = o.sortedKeys();
+ assertEquals(it.next(), "firstName");
+ assertEquals(it.next(), "lastName");
+ assertEquals(it.next(), "sex");
+
+ assertEquals(o.getString("firstName"), p.getFirstName());
+ assertEquals(o.getString("lastName"), p.getLastName());
+ assertEquals(o.get("sex"), JSONObject.NULL);
+ }
+
+ @Test public void personsInArray() throws JSONException {
+ Person p1 = new Person();
+ p1.setFirstName("One");
+
+ Person p2 = new Person();
+ p2.setFirstName("Two");
+
+ People arr = new People();
+ arr.getInfo().add(p1);
+ arr.getInfo().add(p2);
+ arr.getNicknames().add("Prvn\u00ed k\u016f\u0148");
+ final String n2 = "Druh\u00fd hlem\u00fd\u017e\u010f, star\u0161\u00ed";
+ arr.getNicknames().add(n2);
+ arr.getAge().add(33);
+ arr.getAge().add(73);
+
+
+ final String json = arr.toString();
+
+ JSONTokener t = new JSONTokener(json);
+ JSONObject o;
+ try {
+ o = new JSONObject(t);
+ } catch (JSONException ex) {
+ throw new AssertionError("Can't parse " + json, ex);
+ }
+
+ assertEquals(o.getJSONArray("info").getJSONObject(0).getString("firstName"), "One");
+ assertEquals(o.getJSONArray("nicknames").getString(1), n2);
+ assertEquals(o.getJSONArray("age").getInt(1), 73);
+ }
+
+
+ @OnReceive(url="/{url}")
+ static void fetch(Person p, JSONik model) {
+ model.setFetched(p);
+ }
+
+ @OnReceive(url="/{url}")
+ static void fetchArray(Person[] p, JSONik model) {
+ model.setFetchedCount(p.length);
+ model.setFetched(p[0]);
+ }
+
+ @OnReceive(url="/{url}")
+ static void fetchPeople(People p, JSONik model) {
+ model.setFetchedCount(p.getInfo().size());
+ model.setFetched(p.getInfo().get(0));
+ }
+
+ @OnReceive(url="/{url}")
+ static void fetchPeopleAge(People p, JSONik model) {
+ int sum = 0;
+ for (int a : p.getAge()) {
+ sum += a;
+ }
+ model.setFetchedCount(sum);
+ }
+
+ @Http(@Http.Resource(
+ content = "{'firstName': 'Sitar', 'sex': 'MALE'}",
+ path="/person.json",
+ mimeType = "application/json"
+ ))
+ @BrwsrTest public void loadAndParseJSON() throws InterruptedException {
+ if (js == null) {
+ js = new JSONik();
+ js.applyBindings();
+
+ js.fetch("person.json");
+ }
+
+ Person p = js.getFetched();
+ if (p == null) {
+ throw new InterruptedException();
+ }
+
+ assert "Sitar".equals(p.getFirstName()) : "Expecting Sitar: " + p.getFirstName();
+ assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
+ }
+
+ @OnReceive(url="/{url}?callme={me}", jsonp = "me")
+ static void fetchViaJSONP(Person p, JSONik model) {
+ model.setFetched(p);
+ }
+
+ @Http(@Http.Resource(
+ content = "$0({'firstName': 'Mitar', 'sex': 'MALE'})",
+ path="/person.json",
+ mimeType = "application/javascript",
+ parameters = { "callme" }
+ ))
+ @BrwsrTest public void loadAndParseJSONP() throws InterruptedException {
+
+ if (js == null) {
+ orig = scriptElements();
+ assert orig > 0 : "There should be some scripts on the page";
+
+ js = new JSONik();
+ js.applyBindings();
+
+ js.fetchViaJSONP("person.json");
+ }
+
+ Person p = js.getFetched();
+ if (p == null) {
+ throw new InterruptedException();
+ }
+
+ assert "Mitar".equals(p.getFirstName()) : "Unexpected: " + p.getFirstName();
+ assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
+
+ int now = scriptElements();
+
+ assert orig == now : "The set of elements is unchanged. Delta: " + (now - orig);
+ }
+
+ @JavaScriptBody(args = { }, body = "return window.document.getElementsByTagName('script').length;")
+ private static native int scriptElements();
+
+ @JavaScriptBody(args = { "s" }, body = "return window.JSON.parse(s);")
+ private static native Object parseJSON(String s);
+
+ @Http(@Http.Resource(
+ content = "{'firstName': 'Sitar', 'sex': 'MALE'}",
+ path="/person.json",
+ mimeType = "application/json"
+ ))
+ @BrwsrTest public void loadAndParseJSONSentToArray() throws InterruptedException {
+ if (js == null) {
+ js = new JSONik();
+ js.applyBindings();
+
+ js.fetchArray("person.json");
+ }
+
+ Person p = js.getFetched();
+ if (p == null) {
+ throw new InterruptedException();
+ }
+
+ assert p != null : "We should get our person back: " + p;
+ assert "Sitar".equals(p.getFirstName()) : "Expecting Sitar: " + p.getFirstName();
+ assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
+ }
+
+ @Http(@Http.Resource(
+ content = "[{'firstName': 'Gitar', 'sex': 'FEMALE'}]",
+ path="/person.json",
+ mimeType = "application/json"
+ ))
+ @BrwsrTest public void loadAndParseJSONArraySingle() throws InterruptedException {
+ if (js == null) {
+ js = new JSONik();
+ js.applyBindings();
+
+ js.fetch("person.json");
+ }
+
+ Person p = js.getFetched();
+ if (p == null) {
+ throw new InterruptedException();
+ }
+
+ assert p != null : "We should get our person back: " + p;
+ assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName();
+ assert Sex.FEMALE.equals(p.getSex()) : "Expecting FEMALE: " + p.getSex();
+ }
+
+ @Http(@Http.Resource(
+ content = "{'info':[{'firstName': 'Gitar', 'sex': 'FEMALE'}]}",
+ path="/people.json",
+ mimeType = "application/json"
+ ))
+ @BrwsrTest public void loadAndParseArrayInPeople() throws InterruptedException {
+ if (js == null) {
+ js = new JSONik();
+ js.applyBindings();
+
+ js.fetchPeople("people.json");
+ }
+
+ if (0 == js.getFetchedCount()) {
+ throw new InterruptedException();
+ }
+
+ assert js.getFetchedCount() == 1 : "One person loaded: " + js.getFetchedCount();
+
+ Person p = js.getFetched();
+
+ assert p != null : "We should get our person back: " + p;
+ assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName();
+ assert Sex.FEMALE.equals(p.getSex()) : "Expecting FEMALE: " + p.getSex();
+ }
+
+ @Http(@Http.Resource(
+ content = "{'age':[1, 2, 3]}",
+ path="/people.json",
+ mimeType = "application/json"
+ ))
+ @BrwsrTest public void loadAndParseArrayOfIntegers() throws InterruptedException {
+ if (js == null) {
+ js = new JSONik();
+ js.applyBindings();
+
+ js.fetchPeopleAge("people.json");
+ }
+
+ if (0 == js.getFetchedCount()) {
+ throw new InterruptedException();
+ }
+
+ assert js.getFetchedCount() == 6 : "1 + 2 + 3 is " + js.getFetchedCount();
+ }
+
+ @OnReceive(url="/{url}")
+ static void fetchPeopleSex(People p, JSONik model) {
+ model.setFetchedCount(1);
+ model.getFetchedSex().addAll(p.getSex());
+ }
+
+
+ @Http(@Http.Resource(
+ content = "{'sex':['FEMALE', 'MALE', 'MALE']}",
+ path="/people.json",
+ mimeType = "application/json"
+ ))
+ @BrwsrTest public void loadAndParseArrayOfEnums() throws InterruptedException {
+ if (js == null) {
+ js = new JSONik();
+ js.applyBindings();
+
+ js.fetchPeopleSex("people.json");
+ }
+
+ if (0 == js.getFetchedCount()) {
+ throw new InterruptedException();
+ }
+
+ assert js.getFetchedCount() == 1 : "Loaded";
+
+ assert js.getFetchedSex().size() == 3 : "Three values " + js.getFetchedSex();
+ assert js.getFetchedSex().get(0) == Sex.FEMALE : "Female first " + js.getFetchedSex();
+ assert js.getFetchedSex().get(1) == Sex.MALE : "male 2nd " + js.getFetchedSex();
+ assert js.getFetchedSex().get(2) == Sex.MALE : "male 3rd " + js.getFetchedSex();
+ }
+
+ @Http(@Http.Resource(
+ content = "[{'firstName': 'Gitar', 'sex': 'FEMALE'},"
+ + "{'firstName': 'Peter', 'sex': 'MALE'}"
+ + "]",
+ path="/person.json",
+ mimeType = "application/json"
+ ))
+ @BrwsrTest public void loadAndParseJSONArray() throws InterruptedException {
+ if (js == null) {
+ js = new JSONik();
+ js.applyBindings();
+ js.fetchArray("person.json");
+ }
+
+
+ Person p = js.getFetched();
+ if (p == null) {
+ throw new InterruptedException();
+ }
+
+ assert js.getFetchedCount() == 2 : "We got two values: " + js.getFetchedCount();
+ assert p != null : "We should get our person back: " + p;
+ assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName();
+ assert Sex.FEMALE.equals(p.getSex()) : "Expecting FEMALE: " + p.getSex();
+ }
+
+ @Factory public static Object[] create() {
+ return VMTest.create(JSONTest.class);
+ }
+
+}
diff -r d127d41768bd -r ae3f2aa50970 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java
--- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Tue Mar 05 07:57:16 2013 +0100
+++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Fri May 17 10:27:41 2013 +0200
@@ -17,21 +17,29 @@
*/
package org.apidesign.bck2brwsr.htmlpage;
+import java.util.List;
+import org.apidesign.bck2brwsr.core.JavaScriptBody;
import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
import org.apidesign.bck2brwsr.htmlpage.api.OnEvent;
+import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
import org.apidesign.bck2brwsr.htmlpage.api.Page;
import org.apidesign.bck2brwsr.htmlpage.api.Property;
import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
import org.apidesign.bck2brwsr.vmtest.HtmlFragment;
import org.apidesign.bck2brwsr.vmtest.VMTest;
+import static org.testng.Assert.assertEquals;
import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
/**
*
* @author Jaroslav Tulach
*/
@Page(xhtml="Knockout.xhtml", className="KnockoutModel", properties={
- @Property(name="name", type=String.class)
+ @Property(name="name", type=String.class),
+ @Property(name="results", type=String.class, array = true),
+ @Property(name="callbackCount", type=int.class),
+ @Property(name="people", type=PersonImpl.class, array = true)
})
public class KnockoutTest {
@@ -44,19 +52,207 @@
KnockoutModel m = new KnockoutModel();
m.setName("Kukuc");
m.applyBindings();
- assert "Kukuc".equals(m.INPUT.getValue()) : "Value is really kukuc: " + m.INPUT.getValue();
- m.INPUT.setValue("Jardo");
- m.triggerEvent(m.INPUT, OnEvent.CHANGE);
+ assert "Kukuc".equals(m.input.getValue()) : "Value is really kukuc: " + m.input.getValue();
+ m.input.setValue("Jardo");
+ m.triggerEvent(m.input, OnEvent.CHANGE);
assert "Jardo".equals(m.getName()) : "Name property updated: " + m.getName();
}
+ @HtmlFragment(
+ "
\n"
+ + " \n"
+ + "
\n"
+ )
+ @BrwsrTest public void displayContentOfArray() {
+ KnockoutModel m = new KnockoutModel();
+ m.getResults().add("Ahoj");
+ m.applyBindings();
+
+ int cnt = countChildren("ul");
+ assert cnt == 1 : "One child, but was " + cnt;
+
+ m.getResults().add("Hi");
+
+ cnt = countChildren("ul");
+ assert cnt == 2 : "Two children now, but was " + cnt;
+
+ triggerChildClick("ul", 1);
+
+ assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount();
+ assert "Hi".equals(m.getName()) : "We got callback from 2nd child " + m.getName();
+ }
+
+ @HtmlFragment(
+ "
\n"
+ + "
\n"
+ + "
\n"
+ )
+ @BrwsrTest public void displayContentOfDerivedArray() {
+ KnockoutModel m = new KnockoutModel();
+ m.getResults().add("Ahoj");
+ m.applyBindings();
+
+ int cnt = countChildren("ul");
+ assert cnt == 1 : "One child, but was " + cnt;
+
+ m.getResults().add("hello");
+
+ cnt = countChildren("ul");
+ assert cnt == 2 : "Two children now, but was " + cnt;
+ }
+
+ @HtmlFragment(
+ "
\n"
+ + " \n"
+ + "
\n"
+ )
+ @BrwsrTest public void displayContentOfArrayOfPeople() {
+ KnockoutModel m = new KnockoutModel();
+
+ final Person first = new Person();
+ first.setFirstName("first");
+ m.getPeople().add(first);
+
+ m.applyBindings();
+
+ int cnt = countChildren("ul");
+ assert cnt == 1 : "One child, but was " + cnt;
+
+ final Person second = new Person();
+ second.setFirstName("second");
+ m.getPeople().add(second);
+
+ cnt = countChildren("ul");
+ assert cnt == 2 : "Two children now, but was " + cnt;
+
+ triggerChildClick("ul", 1);
+
+ assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount();
+
+ cnt = countChildren("ul");
+ assert cnt == 1 : "Again one child, but was " + cnt;
+
+ String txt = childText("ul", 0);
+ assert "first".equals(txt) : "Expecting 'first': " + txt;
+
+ first.setFirstName("changed");
+
+ txt = childText("ul", 0);
+ assert "changed".equals(txt) : "Expecting 'changed': " + txt;
+ }
+
+ @ComputedProperty
+ static Person firstPerson(List people) {
+ return people.isEmpty() ? null : people.get(0);
+ }
+
+ @HtmlFragment(
+ "