# HG changeset patch # User Jaroslav Tulach # Date 1358935025 -3600 # Node ID 600a84e53009eaf0d09804bdc04176586b19931d # Parent de22f66d685ff26ccc1ad735d473159af2372ead# Parent 7dbd0a097e44de27da9a0a1e4a0e0ca36e357112 Merging basic implementation of MVVC based on Knockout diff -r de22f66d685f -r 600a84e53009 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 Jan 22 19:21:34 2013 +0100 +++ b/benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -30,7 +30,8 @@ public MatrixTest() { } - @Compare public String tenThousandIterations() throws IOException { + @Compare(scripting = false) + public String tenThousandIterations() throws IOException { Matrix m1 = new Matrix(5); Matrix m2 = new Matrix(5); diff -r de22f66d685f -r 600a84e53009 javaquery/api/pom.xml --- a/javaquery/api/pom.xml Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/api/pom.xml Wed Jan 23 10:57:05 2013 +0100 @@ -64,5 +64,11 @@ jar test + + ${project.groupId} + vmtest + ${project.version} + test + diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,93 @@ +/** + * 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.lang.reflect.Method; +import org.apidesign.bck2brwsr.core.ExtraJavaScript; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** Provides binding between models and + * + * @author Jaroslav Tulach + */ +@ExtraJavaScript(resource = "/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js") +public class Knockout { + /** used by tests */ + static Knockout next; + + Knockout() { + } + + public static Knockout applyBindings( + Class modelClass, M model, String[] propsGettersAndSetters + ) { + Knockout bindings = next; + next = null; + if (bindings == null) { + bindings = new Knockout(); + } + 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()); + } + } + applyBindings(bindings); + return bindings; + } + + @JavaScriptBody(args = { "prop" }, body = + "this[prop].valueHasMutated();" + ) + public void valueHasMutated(String prop) { + } + + + @JavaScriptBody(args = { "id", "ev" }, body = "ko.utils.triggerEvent(window.document.getElementById(id), ev.substring(2));") + public static void triggerEvent(String id, String ev) { + } + + @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive" }, body = + "var bnd = {\n" + + " read: function() {\n" + + " var v = model[getter]();\n" + + " return v;\n" + + " },\n" + + " owner: bindings\n" + + "};\n" + + "if (setter != null) {\n" + + " bnd.write = function(val) {\n" + + " model[setter](primitive ? new Number(val) : val);\n" + + " };\n" + + "}\n" + + "bindings[prop] = ko.computed(bnd);" + ) + private static void bind( + Object bindings, Object model, String prop, String getter, String setter, boolean primitive + ) { + } + + @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);") + private static void applyBindings(Object bindings) {} +} diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Wed Jan 23 10:57:05 2013 +0100 @@ -22,9 +22,13 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Completion; @@ -39,12 +43,16 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.MirroredTypeException; import javax.lang.model.type.TypeMirror; 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.On; import org.apidesign.bck2brwsr.htmlpage.api.Page; +import org.apidesign.bck2brwsr.htmlpage.api.Property; import org.openide.util.lookup.ServiceProvider; /** Annotation processor to process an XHTML page and generate appropriate @@ -86,19 +94,44 @@ try { w.append("package " + pkg + ";\n"); w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n"); - w.append("class ").append(className).append(" {\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 static final "). + w.append(" ").append("public final "). append(type).append(' ').append(cnstnt(id)).append(" = new "). append(type).append("(\"").append(id).append("\");\n"); } - w.append(" static {\n"); - if (!initializeOnClick((TypeElement) e, w, pp)) { - return false; + 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"); w.append("}\n"); } finally { w.close(); @@ -144,12 +177,17 @@ return id.toUpperCase(Locale.ENGLISH).replace('.', '_').replace('-', '_'); } - private boolean initializeOnClick(TypeElement type, Writer w, ProcessPage pp) throws IOException { + private boolean initializeOnClick( + String className, TypeElement type, Writer w, ProcessPage pp + ) throws IOException { TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); { //for (Element clazz : pe.getEnclosedElements()) { // if (clazz.getKind() != ElementKind.CLASS) { // continue; // } + w.append(" public ").append(className).append("() {\n"); + StringBuilder dispatch = new StringBuilder(); + int dispatchCnt = 0; for (Element method : type.getEnclosedElements()) { On oc = method.getAnnotation(On.class); if (oc != null) { @@ -159,15 +197,33 @@ return false; } ExecutableElement ee = (ExecutableElement)method; - boolean hasParam; - if (ee.getParameters().isEmpty()) { - hasParam = false; - } else { - if (ee.getParameters().size() != 1 || ee.getParameters().get(0).asType() != stringType) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method should either have no arguments or one String argument", ee); + 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; } - hasParam = true; } if (!ee.getModifiers().contains(Modifier.STATIC)) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee); @@ -178,17 +234,33 @@ return false; } w.append(" OnEvent." + oc.event()).append(".of(").append(cnstnt(id)). - append(").perform(new Runnable() { public void run() {\n"); - w.append(" ").append(type.getSimpleName().toString()). - append('.').append(ee.getSimpleName()).append("("); - if (hasParam) { - w.append("\"").append(id).append("\""); - } - w.append(");\n"); - w.append(" }});\n"); - } + append(").perform(new OnDispatch(" + dispatchCnt + "));\n"); + + dispatch. + append(" case ").append(dispatchCnt).append(": "). + append(type.getSimpleName().toString()). + append('.').append(ee.getSimpleName()).append("("). + append(params). + append("); break;\n"); + + dispatchCnt++; + } } } + w.append(" }\n"); + if (dispatchCnt > 0) { + w.append("class OnDispatch implements Runnable {\n"); + w.append(" private final int dispatch;\n"); + w.append(" OnDispatch(int d) { dispatch = d; }\n"); + w.append(" public void run() {\n"); + w.append(" switch (dispatch) {\n"); + w.append(dispatch); + w.append(" }\n"); + w.append(" }\n"); + w.append("}\n"); + } + + } return true; } @@ -235,4 +307,126 @@ } return e.getEnclosingElement(); } + + private static void generateProperties( + Writer w, Property[] properties, Collection props, + Map> deps + ) throws IOException { + for (Property p : properties) { + final String tn = typeName(p); + String[] gs = toGetSet(p.name(), tn); + + 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"); + } + } + w.write(" }\n"); + w.write("}\n"); + + props.add(p.name()); + props.add(gs[2]); + props.add(gs[3]); + props.add(gs[0]); + } + } + + private boolean generateComputedProperties( + Writer w, Collection arr, Collection props, + Map> deps + ) throws IOException { + for (Element e : arr) { + if (e.getKind() != ElementKind.METHOD) { + continue; + } + if (e.getAnnotation(ComputedProperty.class) == null) { + continue; + } + ExecutableElement ee = (ExecutableElement)e; + final String tn = ee.getReturnType().toString(); + final String sn = ee.getSimpleName().toString(); + String[] gs = toGetSet(sn, tn); + + 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); + w.write(" " + dt + " arg" + (++arg) + " = "); + w.write(call[0] + "();\n"); + + Collection depends = deps.get(dn); + if (depends == null) { + 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() + "("); + String sep = ""; + for (int i = 1; i <= arg; i++) { + w.write(sep); + w.write("arg" + i); + sep = ", "; + } + w.write(");\n"); + w.write(" } finally {\n"); + 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; + } + + private static String[] toGetSet(String name, String type) { + String n = Character.toUpperCase(name.charAt(0)) + name.substring(1); + String bck2brwsrType = "L" + type.replace('.', '_') + "_2"; + if ("int".equals(type)) { + bck2brwsrType = "I"; + } + if ("double".equals(type)) { + bck2brwsrType = "D"; + } + String pref = "get"; + if ("boolean".equals(type)) { + pref = "is"; + bck2brwsrType = "Z"; + } + final String nu = n.replace('.', '_'); + return new String[]{ + pref + n, + "set" + n, + pref + nu + "__" + bck2brwsrType, + "set" + nu + "__V" + bck2brwsrType + }; + } + + private static String typeName(Property p) { + try { + return p.type().getName(); + } catch (MirroredTypeException ex) { + return ex.getTypeMirror().toString(); + } + } } diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java Wed Jan 23 10:57:05 2013 +0100 @@ -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; + +/** 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. + * + * @author Jaroslav Tulach + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface ComputedProperty { +} diff -r de22f66d685f -r 600a84e53009 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 Jan 22 19:21:34 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java Wed Jan 23 10:57:05 2013 +0100 @@ -30,6 +30,13 @@ this.id = id; } + /** Id of the element in the document. + * @return the id for this element + */ + public String getId() { + return id; + } + abstract void dontSubclass(); @JavaScriptBody( @@ -61,7 +68,8 @@ body="var e = window.document.getElementById(this.fld_id);\n" + "e[ev.fld_id] = function() { r.run__V(); };\n" ) - final native void on(OnEvent ev, Runnable r); + final void on(OnEvent ev, Runnable r) { + } /** Shows alert message dialog in a browser. * @param msg the message to show diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnEvent.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnEvent.java Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnEvent.java Wed Jan 23 10:57:05 2013 +0100 @@ -26,6 +26,7 @@ BLUR("onblur"), CAN_PLAY("oncanplay"), CAN_PLAY_THROUGH("oncanplaythrough"), + CHANGE("onchange"), CLICK("onclick"), CONTEXT_MENU("oncontextmenu"), DBL_CLICK("ondblclick"), @@ -82,6 +83,13 @@ this.id = id; } + /** The name of property this event is referenced by from an {@link Element}. + * For {@link OnEvent#CHANGE}, it is onchange. + */ + public String getElementPropertyName() { + return id; + } + /** What should happen when this even happen on one * of associated elements. Continue by calling {@link OnController#perform(java.lang.Runnable)} * method. diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Page.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Page.java Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Page.java Wed Jan 23 10:57:05 2013 +0100 @@ -36,4 +36,7 @@ * found elements with IDs. */ String className() default ""; + /** List of properties generated into the page. + */ + Property[] properties() default {}; } diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,34 @@ +/** + * 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.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Represents a property in a generated model of an HTML + * {@link Page}. + * + * @author Jaroslav Tulach + */ +@Retention(RetentionPolicy.SOURCE) +@Target({}) +public @interface Property { + String name(); + Class type(); +} diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,3587 @@ +// Knockout JavaScript library v2.2.1 +// (c) Steven Sanderson - http://knockoutjs.com/ +// License: MIT (http://www.opensource.org/licenses/mit-license.php) + +(function(){ +var DEBUG=true; +(function(window,document,navigator,jQuery,undefined){ +!function(factory) { + // Support three module loading scenarios + if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') { + // [1] CommonJS/Node.js + var target = module['exports'] || exports; // module.exports is for Node.js + factory(target); + } else if (typeof define === 'function' && define['amd']) { + // [2] AMD anonymous module + define(['exports'], factory); + } else { + // [3] No module loader (plain "); + }; + + if (jQueryTmplVersion > 0) { + jQuery['tmpl']['tag']['ko_code'] = { + open: "__.push($1 || '');" + }; + jQuery['tmpl']['tag']['ko_with'] = { + open: "with($1) {", + close: "} " + }; + } + }; + + ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine(); + + // Use this one by default *only if jquery.tmpl is referenced* + var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine(); + if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0) + ko.setTemplateEngine(jqueryTmplTemplateEngineInstance); + + ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine); +})(); +}); +})(window,document,navigator,window["jQuery"]); +})(); \ No newline at end of file diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,62 @@ +/** + * 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.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.OnEvent; +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 org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +@Page(xhtml="Knockout.xhtml", className="KnockoutModel", properties={ + @Property(name="name", type=String.class) +}) +public class KnockoutTest { + + @HtmlFragment( + "

Loading Bck2Brwsr's Hello World...

\n" + + "Your name: \n" + + "\n" + ) + @BrwsrTest public void modifyValueAssertChangeInModel() { + 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 "Jardo".equals(m.getName()) : "Name property updated: " + m.getName(); + } + + @ComputedProperty + static String helloMessage(String name) { + return "Hello " + name + "!"; + } + + @Factory + public static Object[] create() { + return VMTest.create(KnockoutTest.class); + } +} diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -17,18 +17,103 @@ */ package org.apidesign.bck2brwsr.htmlpage; +import java.util.ArrayList; +import java.util.List; +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; import org.apidesign.bck2brwsr.htmlpage.api.Page; +import org.apidesign.bck2brwsr.htmlpage.api.Property; import static org.testng.Assert.*; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * * @author Jaroslav Tulach */ -@Page(xhtml = "Empty.html", className = "Model") +@Page(xhtml = "Empty.html", className = "Model", properties = { + @Property(name = "value", type = int.class), + @Property(name = "unrelated", type = long.class) +}) public class ModelTest { - @Test public void classGenerated() { - Class c = Model.class; - assertNotNull(c, "Class for empty page generated"); + private Model model; + private static Model leakedModel; + + @BeforeMethod + public void createModel() { + model = new Model(); + } + + @Test public void classGeneratedWithSetterGetter() { + model.setValue(10); + assertEquals(10, model.getValue(), "Value changed"); + } + + @Test public void computedMethod() { + model.setValue(4); + assertEquals(16, model.getPowerValue()); + } + + @Test public void derivedPropertiesAreNotified() { + MockKnockout my = new MockKnockout(); + MockKnockout.next = my; + + model.applyBindings(); + + model.setValue(33); + + assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated); + assertTrue(my.mutated.contains("powerValue"), "Power value is in there: " + my.mutated); + assertTrue(my.mutated.contains("value"), "Simple value is in there: " + my.mutated); + + my.mutated.clear(); + + model.setUnrelated(44); + assertEquals(my.mutated.size(), 1, "One property changed"); + assertTrue(my.mutated.contains("unrelated"), "Its name is unrelated"); + } + + @Test public void computedPropertyCannotWriteToModel() { + leakedModel = model; + try { + String res = model.getNotAllowedWrite(); + fail("We should not be allowed to write to the model: " + res); + } catch (IllegalStateException ex) { + // OK, we can't read + } + } + + @Test public void computedPropertyCannotReadToModel() { + leakedModel = model; + try { + String res = model.getNotAllowedRead(); + fail("We should not be allowed to read from the model: " + res); + } catch (IllegalStateException ex) { + // OK, we can't read + } + } + + @ComputedProperty + static int powerValue(int value) { + return value * value; + } + + @ComputedProperty + static String notAllowedRead() { + return "Not allowed callback: " + leakedModel.getUnrelated(); + } + + @ComputedProperty + static String notAllowedWrite() { + leakedModel.setUnrelated(11); + return "Not allowed callback!"; + } + + static class MockKnockout extends Knockout { + List mutated = new ArrayList(); + + @Override + public void valueHasMutated(String prop) { + mutated.add(prop); + } } } diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java Wed Jan 23 10:57:05 2013 +0100 @@ -43,9 +43,14 @@ */ @Page(xhtml="TestPage.html") public class PageController { + private static final TestPage PAGE = new TestPage(); + @On(event = CLICK, id="pg.button") - static void updateTitle() { - TestPage.PG_TITLE.setText("You want this window to be named " + TestPage.PG_TEXT.getValue()); + static void updateTitle(TestPage ref) { + if (PAGE != ref) { + throw new IllegalStateException("Both references should be the same. " + ref + " != " + PAGE); + } + ref.PG_TITLE.setText("You want this window to be named " + ref.PG_TEXT.getValue()); } @On(event = CLICK, id={ "pg.title", "pg.text" }) @@ -53,6 +58,6 @@ if (!id.equals("pg.title")) { throw new IllegalStateException(); } - TestPage.PG_TITLE.setText(id); + PAGE.PG_TITLE.setText(id); } } diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ProcessPageTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ProcessPageTest.java Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ProcessPageTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -108,7 +108,9 @@ + "\n" + "window.document = doc;\n" ); - Invocable i = compileClass(sb, "org/apidesign/bck2brwsr/htmlpage/PageController"); + Invocable i = compileClass(sb, + "org/apidesign/bck2brwsr/htmlpage/PageController" + ); Object ret = null; try { diff -r de22f66d685f -r 600a84e53009 javaquery/api/src/test/resources/org/apidesign/bck2brwsr/htmlpage/Knockout.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/resources/org/apidesign/bck2brwsr/htmlpage/Knockout.xhtml Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,25 @@ + + +

+

Loading Bck2Brwsr's Hello World...

+ Your name: + +

diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator-dynamic/pom.xml --- a/javaquery/demo-calculator-dynamic/pom.xml Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/pom.xml Wed Jan 23 10:57:05 2013 +0100 @@ -28,7 +28,7 @@ - org/apidesign/bck2brwsr/mavenhtml/Calculator.xhtml + org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml @@ -54,5 +54,11 @@ javaquery.api 0.3-SNAPSHOT + + org.testng + testng + 6.5.2 + test + diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,112 @@ +/** + * 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.demo.calc; + +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.On; +import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; +import org.apidesign.bck2brwsr.htmlpage.api.Page; +import org.apidesign.bck2brwsr.htmlpage.api.Property; + +/** HTML5 & Java demo showing the power of + * annotation processors + * as well as other goodies. + * + * @author Jaroslav Tulach + */ +@Page(xhtml="Calculator.xhtml", properties = { + @Property(name = "memory", type = double.class), + @Property(name = "display", type = double.class), + @Property(name = "operation", type = String.class), + @Property(name = "hover", type = boolean.class) +}) +public class Calc { + static { + new Calculator().applyBindings(); + } + + @On(event = CLICK, id="clear") + static void clear(Calculator c) { + c.setMemory(0); + c.setOperation(null); + c.setDisplay(0); + } + + @On(event = CLICK, id= { "plus", "minus", "mul", "div" }) + static void applyOp(Calculator c, String op) { + c.setMemory(c.getDisplay()); + c.setOperation(op); + c.setDisplay(0); + } + + @On(event = MOUSE_OVER, id= { "result" }) + static void attemptingIn(Calculator c, String op) { + c.setHover(true); + } + @On(event = MOUSE_OUT, id= { "result" }) + static void attemptingOut(Calculator c, String op) { + c.setHover(false); + } + + @On(event = CLICK, id="result") + static void computeTheValue(Calculator c) { + c.setDisplay(compute( + c.getOperation(), + c.getMemory(), + c.getDisplay() + )); + c.setMemory(0); + } + + private static double compute(String op, double memory, double display) { + switch (op) { + case "plus": return memory + display; + case "minus": return memory - display; + case "mul": return memory * display; + case "div": return memory / display; + default: throw new IllegalStateException(op); + } + } + + @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) + static void addDigit(String digit, Calculator c) { + digit = digit.substring(1); + + double v = c.getDisplay(); + if (v == 0.0) { + c.setDisplay(Integer.parseInt(digit)); + } else { + String txt = Double.toString(v); + if (txt.endsWith(".0")) { + txt = txt.substring(0, txt.length() - 2); + } + txt = txt + digit; + c.setDisplay(Double.parseDouble(txt)); + } + } + + @ComputedProperty + public static String displayPreview( + double display, boolean hover, double memory, String operation + ) { + if (!hover) { + return "Type numbers and perform simple operations! Press '=' to get result."; + } + return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display); + } +} diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/mavenhtml/App.java --- a/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/mavenhtml/App.java Tue Jan 22 19:21:34 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * 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.mavenhtml; - -import org.apidesign.bck2brwsr.htmlpage.api.On; -import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; -import org.apidesign.bck2brwsr.htmlpage.api.Page; - -/** HTML5 & Java demo showing the power of - * annotation processors - * as well as other goodies. - * - * @author Jaroslav Tulach - */ -@Page(xhtml="Calculator.xhtml") -public class App { - private static double memory; - private static String operation; - - @On(event = CLICK, id="clear") - static void clear() { - memory = 0; - operation = null; - Calculator.DISPLAY.setValue("0"); - } - - @On(event = CLICK, id= { "plus", "minus", "mul", "div" }) - static void applyOp(String op) { - memory = getValue(); - operation = op; - Calculator.DISPLAY.setValue("0"); - } - - @On(event = CLICK, id="result") - static void computeTheValue() { - switch (operation) { - case "plus": setValue(memory + getValue()); break; - case "minus": setValue(memory - getValue()); break; - case "mul": setValue(memory * getValue()); break; - case "div": setValue(memory / getValue()); break; - default: throw new IllegalStateException(operation); - } - } - - @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) - static void addDigit(String digit) { - digit = digit.substring(1); - String v = Calculator.DISPLAY.getValue(); - if (getValue() == 0.0) { - Calculator.DISPLAY.setValue(digit); - } else { - Calculator.DISPLAY.setValue(v + digit); - } - } - - private static void setValue(double v) { - StringBuilder sb = new StringBuilder(); - sb.append(v); - if (sb.toString().endsWith(".0")) { - final int l = sb.length(); - sb.delete(l - 2, l); - } - Calculator.DISPLAY.setValue(sb.toString()); - } - - private static double getValue() { - try { - return Double.parseDouble(Calculator.DISPLAY.getValue()); - } catch (NumberFormatException ex) { - Calculator.DISPLAY.setValue("err"); - return 0.0; - } - } -} diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,159 @@ + + + + + + Simple Calculator in HTML5 and Java + + + + +

Java and HTML5 - Together at Last!

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + +
+
+    package org.apidesign.bck2brwsr.mavenhtml;
+
+    import org.apidesign.bck2brwsr.htmlpage.api.OnClick;
+    import org.apidesign.bck2brwsr.htmlpage.api.Page;
+
+    /** HTML5 & Java demo showing the power of annotation processors
+     * as well as other goodies, including type-safe association between
+     * an XHTML page and Java.
+     * 
+     * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
+     */
+    @Page(xhtml="Calculator.xhtml")
+    public class App {
+        private static double memory;
+        private static String operation;
+
+        @OnClick(id="clear")
+        static void clear() {
+            memory = 0;
+            operation = null;
+            Calculator.DISPLAY.setValue("0");
+        }
+
+        @OnClick(id= { "plus", "minus", "mul", "div" })
+        static void applyOp(String op) {
+            memory = getValue();
+            operation = op;
+            Calculator.DISPLAY.setValue("0");
+        }
+
+        @OnClick(id="result")
+        static void computeTheValue() {
+            switch (operation) {
+                case "plus": setValue(memory + getValue()); break;
+                case "minus": setValue(memory - getValue()); break;
+                case "mul": setValue(memory * getValue()); break;
+                case "div": setValue(memory / getValue()); break;
+                default: throw new IllegalStateException(operation);
+            }
+        }
+
+        @OnClick(id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) 
+        static void addDigit(String digit) {
+            digit = digit.substring(1);
+            String v = Calculator.DISPLAY.getValue();
+            if (getValue() == 0.0) {
+                Calculator.DISPLAY.setValue(digit);
+            } else {
+                Calculator.DISPLAY.setValue(v + digit);
+            }
+        }
+
+        private static void setValue(double v) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(v);
+            Calculator.DISPLAY.setValue(sb.toString());
+        }
+
+        private static double getValue() {
+            try {
+                return Double.parseDouble(Calculator.DISPLAY.getValue());
+            } catch (NumberFormatException ex) {
+                Calculator.DISPLAY.setValue("err");
+                return 0.0;
+            }
+        }
+    }
+
+    
+ + diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/mavenhtml/Calculator.xhtml --- a/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/mavenhtml/Calculator.xhtml Tue Jan 22 19:21:34 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,158 +0,0 @@ - - - - - - Simple Calculator in HTML5 and Java - - - - -

Java and HTML5 - Together at Last!

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
-    package org.apidesign.bck2brwsr.mavenhtml;
-
-    import org.apidesign.bck2brwsr.htmlpage.api.OnClick;
-    import org.apidesign.bck2brwsr.htmlpage.api.Page;
-
-    /** HTML5 & Java demo showing the power of annotation processors
-     * as well as other goodies, including type-safe association between
-     * an XHTML page and Java.
-     * 
-     * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
-     */
-    @Page(xhtml="Calculator.xhtml")
-    public class App {
-        private static double memory;
-        private static String operation;
-
-        @OnClick(id="clear")
-        static void clear() {
-            memory = 0;
-            operation = null;
-            Calculator.DISPLAY.setValue("0");
-        }
-
-        @OnClick(id= { "plus", "minus", "mul", "div" })
-        static void applyOp(String op) {
-            memory = getValue();
-            operation = op;
-            Calculator.DISPLAY.setValue("0");
-        }
-
-        @OnClick(id="result")
-        static void computeTheValue() {
-            switch (operation) {
-                case "plus": setValue(memory + getValue()); break;
-                case "minus": setValue(memory - getValue()); break;
-                case "mul": setValue(memory * getValue()); break;
-                case "div": setValue(memory / getValue()); break;
-                default: throw new IllegalStateException(operation);
-            }
-        }
-
-        @OnClick(id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) 
-        static void addDigit(String digit) {
-            digit = digit.substring(1);
-            String v = Calculator.DISPLAY.getValue();
-            if (getValue() == 0.0) {
-                Calculator.DISPLAY.setValue(digit);
-            } else {
-                Calculator.DISPLAY.setValue(v + digit);
-            }
-        }
-
-        private static void setValue(double v) {
-            StringBuilder sb = new StringBuilder();
-            sb.append(v);
-            Calculator.DISPLAY.setValue(sb.toString());
-        }
-
-        private static double getValue() {
-            try {
-                return Double.parseDouble(Calculator.DISPLAY.getValue());
-            } catch (NumberFormatException ex) {
-                Calculator.DISPLAY.setValue("err");
-                return 0.0;
-            }
-        }
-    }
-
-    
- - diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator-dynamic/src/test/java/org/apidesign/bck2brwsr/demo/calc/CalcTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-calculator-dynamic/src/test/java/org/apidesign/bck2brwsr/demo/calc/CalcTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,46 @@ +/** + * 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.demo.calc; + +import static org.testng.Assert.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Demonstrating POJO testing of HTML page model. + * + * @author Jaroslav Tulach + */ +public class CalcTest { + private Calculator model; + + + @BeforeMethod + public void initModel() { + model = new Calculator().applyBindings(); + } + + @Test + public void testSomeMethod() { + model.setDisplay(10); + Calc.applyOp(model, "plus"); + assertEquals(0.0, model.getDisplay(), "Cleared after pressing +"); + model.setDisplay(5); + Calc.computeTheValue(model); + assertEquals(15.0, model.getDisplay(), "Shows fifteen"); + } +} diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator/pom.xml --- a/javaquery/demo-calculator/pom.xml Tue Jan 22 19:21:34 2013 +0100 +++ b/javaquery/demo-calculator/pom.xml Wed Jan 23 10:57:05 2013 +0100 @@ -42,7 +42,7 @@ xdg-open - ${project.build.directory}/classes/org/apidesign/bck2brwsr/mavenhtml/Calculator.xhtml + ${project.build.directory}/classes/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml
diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,112 @@ +/** + * 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.demo.calc.staticcompilation; + +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.On; +import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; +import org.apidesign.bck2brwsr.htmlpage.api.Page; +import org.apidesign.bck2brwsr.htmlpage.api.Property; + +/** HTML5 & Java demo showing the power of + * annotation processors + * as well as other goodies. + * + * @author Jaroslav Tulach + */ +@Page(xhtml="Calculator.xhtml", properties = { + @Property(name = "memory", type = double.class), + @Property(name = "display", type = double.class), + @Property(name = "operation", type = String.class), + @Property(name = "hover", type = boolean.class) +}) +public class Calc { + static { + new Calculator().applyBindings(); + } + + @On(event = CLICK, id="clear") + static void clear(Calculator c) { + c.setMemory(0); + c.setOperation(null); + c.setDisplay(0); + } + + @On(event = CLICK, id= { "plus", "minus", "mul", "div" }) + static void applyOp(Calculator c, String op) { + c.setMemory(c.getDisplay()); + c.setOperation(op); + c.setDisplay(0); + } + + @On(event = MOUSE_OVER, id= { "result" }) + static void attemptingIn(Calculator c, String op) { + c.setHover(true); + } + @On(event = MOUSE_OUT, id= { "result" }) + static void attemptingOut(Calculator c, String op) { + c.setHover(false); + } + + @On(event = CLICK, id="result") + static void computeTheValue(Calculator c) { + c.setDisplay(compute( + c.getOperation(), + c.getMemory(), + c.getDisplay() + )); + c.setMemory(0); + } + + private static double compute(String op, double memory, double display) { + switch (op) { + case "plus": return memory + display; + case "minus": return memory - display; + case "mul": return memory * display; + case "div": return memory / display; + default: throw new IllegalStateException(op); + } + } + + @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) + static void addDigit(String digit, Calculator c) { + digit = digit.substring(1); + + double v = c.getDisplay(); + if (v == 0.0) { + c.setDisplay(Integer.parseInt(digit)); + } else { + String txt = Double.toString(v); + if (txt.endsWith(".0")) { + txt = txt.substring(0, txt.length() - 2); + } + txt = txt + digit; + c.setDisplay(Double.parseDouble(txt)); + } + } + + @ComputedProperty + public static String displayPreview( + double display, boolean hover, double memory, String operation + ) { + if (!hover) { + return "Type numbers and perform simple operations! Press '=' to get result."; + } + return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display); + } +} diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/mavenhtml/App.java --- a/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/mavenhtml/App.java Tue Jan 22 19:21:34 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * 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.mavenhtml; - -import org.apidesign.bck2brwsr.htmlpage.api.On; -import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; -import org.apidesign.bck2brwsr.htmlpage.api.Page; - -/** HTML5 & Java demo showing the power of - * annotation processors - * as well as other goodies. - * - * @author Jaroslav Tulach - */ -@Page(xhtml="Calculator.xhtml") -public class App { - private static double memory; - private static String operation; - - @On(event = CLICK, id="clear") - static void clear() { - memory = 0; - operation = null; - Calculator.DISPLAY.setValue("0"); - } - - @On(event = CLICK, id= { "plus", "minus", "mul", "div" }) - static void applyOp(String op) { - memory = getValue(); - operation = op; - Calculator.DISPLAY.setValue("0"); - } - - @On(event = CLICK, id="result") - static void computeTheValue() { - switch (operation) { - case "plus": setValue(memory + getValue()); break; - case "minus": setValue(memory - getValue()); break; - case "mul": setValue(memory * getValue()); break; - case "div": setValue(memory / getValue()); break; - default: throw new IllegalStateException(operation); - } - } - - @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) - static void addDigit(String digit) { - digit = digit.substring(1); - String v = Calculator.DISPLAY.getValue(); - if (getValue() == 0.0) { - Calculator.DISPLAY.setValue(digit); - } else { - Calculator.DISPLAY.setValue(v + digit); - } - } - - private static void setValue(double v) { - StringBuilder sb = new StringBuilder(); - sb.append(v); - if (sb.toString().endsWith(".0")) { - final int l = sb.length(); - sb.delete(l - 2, l); - } - Calculator.DISPLAY.setValue(sb.toString()); - } - - private static double getValue() { - try { - return Double.parseDouble(Calculator.DISPLAY.getValue()); - } catch (NumberFormatException ex) { - Calculator.DISPLAY.setValue("err"); - return 0.0; - } - } -} diff -r de22f66d685f -r 600a84e53009 javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,154 @@ + + + + + + Simple Calculator in HTML5 and Java + + + + +

Java and HTML5 - Together at Last!

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ diff -r de22f66d685f -r 600a84e53009 mojo/src/main/resources/archetype-resources/src/test/java/AppTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mojo/src/main/resources/archetype-resources/src/test/java/AppTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,26 @@ +package ${package}; + +import static org.testng.Assert.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Demonstrating POJO testing of HTML page model. Runs in good old HotSpot + * as it does not reference any HTML elements or browser functionality. Just + * operates on the page model. + * + * @author Jaroslav Tulach + */ +public class AppTest { + private Index model; + + + @BeforeMethod + public void initModel() { + model = new Index().applyBindings(); + } + + @Test public void testHelloMessage() { + model.setName("Joe"); + assertEquals(model.getHelloMessage(), "Hello Joe!", "Cleared after pressing +"); + } +} diff -r de22f66d685f -r 600a84e53009 mojo/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mojo/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,40 @@ +package ${package}; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** Bck2brwsr cares about compatibility with real Java. Whatever API is + * supported by bck2brwsr, it needs to behave the same way as when running + * in HotSpot VM. + *

+ * There can be bugs, however. To help us fix them, we kindly ask you to + * write an "inconsistency" test. A test that compares behavior of the API + * between real VM and bck2brwsr VM. This class is skeleton of such test. + * + * @author Jaroslav Tulach + */ +public class InconsistencyTest { + /** A method to demonstrate inconsistency between bck2brwsr and HotSpot. + * Make calls to an API that behaves strangely, return some result at + * the end. No need to use any assert. + * + * @return value to compare between HotSpot and bck2brwsr + */ + @Compare + public int checkStringHashCode() throws Exception { + return "Is string hashCode the same?".hashCode(); + } + + /** Factory method that creates a three tests for each method annotated with + * {@link org.apidesign.bck2brwsr.vmtest.Compare}. One executes the code in + * HotSpot, one in Rhino and the last one compares the results. + * + * @see org.apidesign.bck2brwsr.vmtest.VMTest + */ + @Factory + public static Object[] create() { + return VMTest.create(InconsistencyTest.class); + } + +} diff -r de22f66d685f -r 600a84e53009 mojo/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mojo/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -0,0 +1,46 @@ +package ${package}; + +import org.apidesign.bck2brwsr.htmlpage.api.OnEvent; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.HtmlFragment; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** Sometimes it is useful to run tests inside of the real browser. + * To do that just annotate your method with {@link org.apidesign.bck2brwsr.vmtest.BrwsrTest} + * and that is it. If your code references elements on the HTML page, + * you can pass in an {@link org.apidesign.bck2brwsr.vmtest.HtmlFragment} which + * will be made available on the page before your test starts. + * + * @author Jaroslav Tulach + */ +public class IntegrationTest { + + /** Write to testing code here. Use assert (but not TestNG's + * Assert, as TestNG is not compiled with target 1.6 yet). + */ + @HtmlFragment( + "

Loading Bck2Brwsr's Hello World...

\n" + + "Your name: \n" + + "\n" + + "

\n" + + " \n" + + "

\n" + ) + @BrwsrTest + public void modifyValueAssertChangeInModel() { + Index m = new Index(); + m.setName("Joe Hacker"); + m.applyBindings(); + assert "Joe Hacker".equals(m.INPUT.getValue()) : "Value is really Joe Hacker: " + m.INPUT.getValue(); + m.INPUT.setValue("Happy Joe"); + m.triggerEvent(m.INPUT, OnEvent.CHANGE); + assert "Happy Joe".equals(m.getName()) : "Name property updated to Happy Joe: " + m.getName(); + } + + @Factory + public static Object[] create() { + return VMTest.create(IntegrationTest.class); + } + +} diff -r de22f66d685f -r 600a84e53009 pom.xml --- a/pom.xml Tue Jan 22 19:21:34 2013 +0100 +++ b/pom.xml Wed Jan 23 10:57:05 2013 +0100 @@ -75,6 +75,7 @@ .*/** mojo/src/main/resources/archetype-resources/** vmtest/src/test/resources/** + javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout*.js @@ -112,4 +113,4 @@ COPYING - \ No newline at end of file + diff -r de22f66d685f -r 600a84e53009 vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- a/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Tue Jan 22 19:21:34 2013 +0100 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Wed Jan 23 10:57:05 2013 +0100 @@ -212,7 +212,7 @@ out.append("\n return this;"); out.append("\n }"); out.append("\n return arguments[0] ? new CLS() : CLS.prototype;"); - out.append("\n}"); + out.append("\n};"); StringBuilder sb = new StringBuilder(); for (String init : toInitilize.toArray()) { sb.append("\n").append(init).append("();"); diff -r de22f66d685f -r 600a84e53009 vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java --- a/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java Tue Jan 22 19:21:34 2013 +0100 +++ b/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java Wed Jan 23 10:57:05 2013 +0100 @@ -45,7 +45,9 @@ sb.append("\n return c[method]();"); sb.append("\n}"); - + sb.append("\nfunction checkKO() {"); + sb.append("\n return ko !== null;"); + sb.append("\n}"); ScriptEngine[] arr = { null }; code = StaticMethodTest.compileClass(sb, arr, @@ -71,6 +73,9 @@ assertExec("ko is defined", "test", true, Script.class.getName(), "checkNotNull__Z" ); + + Object res = code.invokeFunction("checkKO"); + assertEquals(res, true, "KO is defined on a global level"); } private static void assertExec(String msg, String methodName, Object expRes, Object... args) throws Exception { diff -r de22f66d685f -r 600a84e53009 vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Compare.java --- a/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Compare.java Tue Jan 22 19:21:34 2013 +0100 +++ b/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Compare.java Wed Jan 23 10:57:05 2013 +0100 @@ -24,7 +24,7 @@ /** Can be applied on a method that yields a return value. * Together with {@link VMTest#create} it can be used to write - * methods which are executed in real as well as JavaScript VMs and + * methods which are executed in real VM as well as JavaScript VMs and * their results are compared. * * @author Jaroslav Tulach @@ -32,4 +32,14 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Compare { + /** Specifies whether the system should internal JavaScript interpreter + * as available via {@link javax.script.ScriptEngine}. Defaults to true, + * but in some situations (benchmarking comes to my mind), one may set this + * to false. In such case only browsers provided via + * vmtest.brwsrs property are used. For example + * "vmtest.brwsrs=firefox,google-chrome" would run the test + * in HotSpot VM, firefox and chrome and would compare the results. + * @return + */ + boolean scripting() default true; } diff -r de22f66d685f -r 600a84e53009 vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java --- a/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Tue Jan 22 19:21:34 2013 +0100 +++ b/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Wed Jan 23 10:57:05 2013 +0100 @@ -112,10 +112,12 @@ return; } final Bck2BrwsrCase real = new Bck2BrwsrCase(m, "Java", null, false, null); - final Bck2BrwsrCase js = new Bck2BrwsrCase(m, "JavaScript", l.javaScript(), false, null); ret.add(real); - ret.add(js); - ret.add(new CompareCase(m, real, js)); + if (c.scripting()) { + final Bck2BrwsrCase js = new Bck2BrwsrCase(m, "JavaScript", l.javaScript(), false, null); + ret.add(js); + ret.add(new CompareCase(m, real, js)); + } for (String b : brwsr) { final Launcher s = l.brwsr(b); ret.add(s);