# HG changeset patch # User Lubomir Nerad # Date 1365692382 -7200 # Node ID fe7ff18eae8db7b88bd7a969b86956ce1673571d # Parent 60d9ea48ec9938f1bc33084ce051de50072eddf5# Parent 5c7cdd2b3f8f9f3be7349520409349531d5d9a6c Merge with trunk diff -r 60d9ea48ec99 -r fe7ff18eae8d .hgtags --- a/.hgtags Mon Mar 25 13:29:42 2013 +0100 +++ b/.hgtags Thu Apr 11 16:59:42 2013 +0200 @@ -1,3 +1,7 @@ 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} diff -r 60d9ea48ec99 -r fe7ff18eae8d benchmarks/matrix-multiplication/pom.xml --- a/benchmarks/matrix-multiplication/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/benchmarks/matrix-multiplication/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,12 +4,12 @@ org.apidesign.bck2brwsr matrix.multiplication - 0.5-SNAPSHOT + 0.6-SNAPSHOT jar benchmarks org.apidesign.bck2brwsr - 0.5-SNAPSHOT + 0.6-SNAPSHOT Matrix multiplication @@ -74,7 +74,7 @@ org.apidesign.bck2brwsr emul.mini - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.testng @@ -91,7 +91,7 @@ org.apidesign.bck2brwsr vmtest - 0.5-SNAPSHOT + 0.6-SNAPSHOT test diff -r 60d9ea48ec99 -r fe7ff18eae8d benchmarks/pom.xml --- a/benchmarks/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/benchmarks/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ bck2brwsr org.apidesign - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr benchmarks - 0.5-SNAPSHOT + 0.6-SNAPSHOT pom Performance benchmarks diff -r 60d9ea48ec99 -r fe7ff18eae8d dew/pom.xml --- a/dew/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/dew/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ org.apidesign bck2brwsr - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr dew - 0.5-SNAPSHOT + 0.6-SNAPSHOT Development Environment for Web http://maven.apache.org diff -r 60d9ea48ec99 -r fe7ff18eae8d ide/editor/pom.xml --- a/ide/editor/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/ide/editor/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,19 +4,18 @@ ide org.apidesign.bck2brwsr - 0.5-SNAPSHOT + 0.6-SNAPSHOT - org.apidesign.bck2brwsr.ide.editor + org.apidesign.bck2brwsr.ide editor - 0.5-SNAPSHOT + 0.6-SNAPSHOT nbm Editor Support for Bck2Brwsr UTF-8 - RELEASE72 ${project.build.directory}/endorsed @@ -40,71 +39,59 @@ org.netbeans.api org-netbeans-api-annotations-common - ${netbeans.version} org.netbeans.api org-netbeans-modules-java-source - ${netbeans.version} org.netbeans.api org-netbeans-libs-javacapi - ${netbeans.version} org.netbeans.api org-netbeans-spi-java-hints - ${netbeans.version} org.netbeans.api org-netbeans-modules-parsing-api - ${netbeans.version} org.netbeans.api org-netbeans-spi-editor-hints - ${netbeans.version} org.netbeans.api org-openide-util - ${netbeans.version} org.netbeans.api org-netbeans-modules-java-lexer - ${netbeans.version} org.netbeans.api org-netbeans-modules-lexer - ${netbeans.version} org.apidesign.bck2brwsr core - 0.5-SNAPSHOT + 0.6-SNAPSHOT jar test org.netbeans.api org-netbeans-modules-java-hints-test - ${netbeans.version} test org.netbeans.api org-netbeans-libs-junit4 - ${netbeans.version} test org.netbeans.modules org-netbeans-lib-nbjavac - ${netbeans.version} test diff -r 60d9ea48ec99 -r fe7ff18eae8d ide/pom.xml --- a/ide/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/ide/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ bck2brwsr org.apidesign - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr ide - 0.5-SNAPSHOT + 0.6-SNAPSHOT pom IDE Support diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/pom.xml --- a/javaquery/api/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr javaquery - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr javaquery.api - 0.5-SNAPSHOT + 0.6-SNAPSHOT JavaQuery API http://maven.apache.org @@ -18,8 +18,8 @@ maven-compiler-plugin 2.3.2 - 1.6 - 1.6 + 1.7 + 1.7 diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java Thu Apr 11 16:59:42 2013 +0200 @@ -41,11 +41,112 @@ 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 String) { + return '"' + + ((String)value). + replace("\"", "\\\""). + replace("\n", "\\n"). + replace("\r", "\\r"). + replace("\t", "\\t") + + '"'; + } + return value.toString(); + } @JavaScriptBody(args = { "object", "property" }, - body = "var p = object[property]; return p ? p : null;" + 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 60d9ea48ec99 -r fe7ff18eae8d 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 Thu Apr 11 16:59:42 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 c) { + boolean ret = super.addAll(c); + notifyChange(); + return ret; + } + + @Override + public boolean addAll(int index, Collection 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 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Thu Apr 11 16:59:42 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,10 +71,11 @@ 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" + " var v = model[getter]();\n" + + " if (array) v = v.koArray();\n" + " return v;\n" + " },\n" + " 'owner': bindings\n" @@ -84,10 +88,43 @@ + "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 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Thu Apr 11 16:59:42 2013 +0200 @@ -20,23 +20,29 @@ 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.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,14 +50,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; @@ -63,89 +76,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 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 { @@ -158,6 +152,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("); + 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(" = "); + 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"; @@ -178,12 +402,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) { @@ -196,58 +421,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) { - if (ve.getSimpleName().contentEquals("id")) { - params.append('"').append(id).append('"'); - continue; - } - params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(ev, \""); - params.append(ve.getSimpleName().toString()); - params.append("\")"); - continue; - } - if (processingEnv.getTypeUtils().getPrimitiveType(TypeKind.DOUBLE) == ve.asType()) { - params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(ev, \""); - params.append(ve.getSimpleName().toString()); - params.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 named 'id' 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"); @@ -278,7 +472,7 @@ } - return true; + return ok; } @Override @@ -292,8 +486,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()); @@ -303,7 +496,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)) { @@ -324,44 +517,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 arr, Collection props, + Writer w, Prprt[] fixedProps, + Collection arr, Collection props, Map> deps ) throws IOException { + boolean ok = true; for (Element e : arr) { if (e.getKind() != ElementKind.METHOD) { continue; @@ -370,30 +608,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); @@ -405,17 +656,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)) { @@ -430,6 +681,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, @@ -438,11 +697,701 @@ }; } - 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 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 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(\"" + p.name() + ": \");\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 (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 val = null; + for (AnnotationMirror an : te.getAnnotationMirrors()) { + for (Map.Entry 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 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 60d9ea48ec99 -r fe7ff18eae8d 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 Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,44 @@ +/** + * 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; + +/** Defines a model class named {@link #className()} which contains + * defined {@link #properties()}. This class can have methods + * annotated by {@link ComputedProperty} which define derived + * properties in the model class. + *

+ * The {@link #className() generated class}'s toString + * converts the state of the object into + * JSON format. + * + * @author Jaroslav Tulach + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Model { + /** Name of the model class */ + String className(); + /** List of properties in the model. + */ + Property[] properties(); +} diff -r 60d9ea48ec99 -r fe7ff18eae8d 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 Thu Apr 11 16:59:42 2013 +0200 @@ -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.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Methods in class annotated by {@link Model} or {@link Page} can be + * annotated by this annotation to signal that they should be available + * as functions to users of the model classes. + * + * @author Jaroslav Tulach + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface OnFunction { +} diff -r 60d9ea48ec99 -r fe7ff18eae8d 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 Thu Apr 11 16:59:42 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; + +/** Represents a property. Either in a generated model of an HTML + * {@link Page} or in a class defined by {@link Model}. + * + * @author Jaroslav Tulach + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface OnPropertyChange { + /** Name(s) of the properties. One wishes to observe. + * + * @return valid java identifier + */ + String[] value(); +} diff -r 60d9ea48ec99 -r fe7ff18eae8d 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 Thu Apr 11 16:59:42 2013 +0200 @@ -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.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Static methods in classes annotated by {@link Page} + * can be marked by this annotation to establish a + * JSON + * communication point. + * The associated model page then gets new method to invoke a network + * connection. Example follows: + * + *

+ * {@link Page @Page}(className="MyModel", xhtml="page.html", properties={
+ *   {@link Property @Property}(name = "people", type=Person.class, array=true)
+ * })
+ * class MyModelImpl {
+ *   {@link Model @Model}(className="Person", properties={
+ *     {@link Property @Property}(name = "firstName", type=String.class),
+ *     {@link Property @Property}(name = "lastName", type=String.class)
+ *   })
+ *   static class PersonImpl {
+ *     {@link ComputedProperty @ComputedProperty}
+ *     static String fullName(String firstName, String lastName) {
+ *       return firstName + " " + lastName;
+ *     }
+ *   }
+ * 
+ *   {@link OnReceive @OnReceive}(url = "{protocol}://your.server.com/person/{name}")
+ *   static void getANewPerson(MyModel m, Person p) {
+ *     {@link Element#alert Element.alert}("Adding " + p.getFullName() + '!');
+ *     m.getPeople().add(p);
+ *   }
+ * 
+ *   // the above will generate method getANewPerson in class MyModel.
+ *   // with protocol and name arguments
+ *   // which asynchronously contacts the server and in case of success calls
+ *   // your {@link OnReceive @OnReceive} with parsed in data
+ * 
+ *   {@link On @On}(event={@link OnEvent#CLICK OnEvent.CLICK}, id="rqst")
+ *   static void requestSmith(MyModel m) {
+ *     m.getANewPerson("http", "Smith");
+ *   }
+ * }
+ * 
+ * When the server returns { "firstName" : "John", "lastName" : "Smith" } + * the browser will show alert message Adding John Smith!. + * + * @author Jaroslav Tulach + * @since 0.6 + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +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 60d9ea48ec99 -r fe7ff18eae8d 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 Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Thu Apr 11 16:59:42 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}. +/** Represents a property. Either in a generated model of an HTML + * {@link Page} or in a class defined by {@link Model}. * * @author Jaroslav Tulach */ @Retention(RetentionPolicy.SOURCE) @Target({}) 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 60d9ea48ec99 -r fe7ff18eae8d 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 Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Thu Apr 11 16:59:42 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 60d9ea48ec99 -r fe7ff18eae8d 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 Thu Apr 11 16:59:42 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 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 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 60d9ea48ec99 -r fe7ff18eae8d 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 Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,52 @@ +/** + * 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 = { }, body = "var json = new Object();" + + "json.firstName = 'son';\n" + + "json.lastName = 'dj';\n" + + "json.sex = 'MALE';\n" + + "return json;" + ) + private static native Object createJSON(); + + @BrwsrTest + public void testConvertToPeople() { + final Object o = createJSON(); + + 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(); + } + + @Factory public static Object[] create() { + return VMTest.create(ConvertTypesTest.class); + } +} \ No newline at end of file diff -r 60d9ea48ec99 -r fe7ff18eae8d 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 Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,370 @@ +/** + * 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"); + } + + @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(); + + @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 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Thu Apr 11 16:59:42 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( + "

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

\n" + ) + @BrwsrTest public void accessFirstPersonWithOnFunction() { + trasfertToFemale(); + } + + @HtmlFragment( + "
    \n" + + "
  • \n" + + "
\n" + ) + @BrwsrTest public void onPersonFunction() { + trasfertToFemale(); + } + + private void trasfertToFemale() { + KnockoutModel m = new KnockoutModel(); + + final Person first = new Person(); + first.setFirstName("first"); + first.setSex(Sex.MALE); + m.getPeople().add(first); + + + m.applyBindings(); + + int cnt = countChildren("ul"); + assert cnt == 1 : "One child, but was " + cnt; + + + triggerChildClick("ul", 0); + + assert first.getSex() == Sex.FEMALE : "Transverted to female: " + first.getSex(); + } + + @Test public void cloneModel() { + Person model = new Person(); + + model.setFirstName("first"); + Person snd = model.clone(); + snd.setFirstName("clone"); + assertEquals("first", model.getFirstName(), "Value has not changed"); + assertEquals("clone", snd.getFirstName(), "Value has changed in clone"); + } + + + @Test public void deepCopyOnClone() { + People model = new People(); + model.getNicknames().add("Jarda"); + assertEquals(model.getNicknames().size(), 1, "One element"); + People snd = model.clone(); + snd.getNicknames().clear(); + assertEquals(snd.getNicknames().size(), 0, "Clone is empty"); + assertEquals(model.getNicknames().size(), 1, "Still one element"); + } + + + @OnFunction + static void call(KnockoutModel m, String data) { + m.setName(data); + m.setCallbackCount(m.getCallbackCount() + 1); + } + + @OnFunction + static void removePerson(KnockoutModel model, Person data) { + model.setCallbackCount(model.getCallbackCount() + 1); + model.getPeople().remove(data); + } + + @ComputedProperty static String helloMessage(String name) { return "Hello " + name + "!"; } + @ComputedProperty + static List cmpResults(List results) { + return results; + } + @Factory public static Object[] create() { return VMTest.create(KnockoutTest.class); } + + @JavaScriptBody(args = { "id" }, body = + "var e = window.document.getElementById(id);\n " + + "if (typeof e === 'undefined') return -2;\n " + + "return e.children.length;\n " + ) + private static native int countChildren(String id); + + @JavaScriptBody(args = { "id", "pos" }, body = + "var e = window.document.getElementById(id);\n " + + "var ev = window.document.createEvent('MouseEvents');\n " + + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " + + "e.children[pos].dispatchEvent(ev);\n " + ) + private static native void triggerChildClick(String id, int pos); + + @JavaScriptBody(args = { "id", "pos" }, body = + "var e = window.document.getElementById(id);\n " + + "var t = e.children[pos].innerHTML;\n " + + "return t ? t : null;" + ) + private static native String childText(String id, int pos); } diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -18,8 +18,13 @@ package org.apidesign.bck2brwsr.htmlpage; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; +import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; import static org.testng.Assert.*; @@ -30,17 +35,22 @@ * * @author Jaroslav Tulach */ -@Page(xhtml = "Empty.html", className = "Model", properties = { +@Page(xhtml = "Empty.html", className = "Modelik", properties = { @Property(name = "value", type = int.class), - @Property(name = "unrelated", type = long.class) + @Property(name = "count", type = int.class), + @Property(name = "unrelated", type = long.class), + @Property(name = "names", type = String.class, array = true), + @Property(name = "values", type = int.class, array = true), + @Property(name = "people", type = PersonImpl.class, array = true), + @Property(name = "changedProperty", type=String.class) }) public class ModelTest { - private Model model; - private static Model leakedModel; + private Modelik model; + private static Modelik leakedModel; @BeforeMethod public void createModel() { - model = new Model(); + model = new Modelik(); } @Test public void classGeneratedWithSetterGetter() { @@ -53,6 +63,75 @@ assertEquals(16, model.getPowerValue()); } + @Test public void arrayIsMutable() { + assertEquals(model.getNames().size(), 0, "Is empty"); + model.getNames().add("Jarda"); + assertEquals(model.getNames().size(), 1, "One element"); + } + + @Test public void arrayChangesNotified() { + MockKnockout my = new MockKnockout(); + MockKnockout.next = my; + + model.applyBindings(); + + model.getNames().add("Hello"); + + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated); + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated); + + my.mutated.clear(); + + Iterator it = model.getNames().iterator(); + assertEquals(it.next(), "Hello"); + it.remove(); + + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated); + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated); + + my.mutated.clear(); + + ListIterator lit = model.getNames().listIterator(); + lit.add("Jarda"); + + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated); + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated); + } + + @Test public void autoboxedArray() { + MockKnockout my = new MockKnockout(); + MockKnockout.next = my; + + model.applyBindings(); + + model.getValues().add(10); + + assertEquals(model.getValues().get(0), Integer.valueOf(10), "Really ten"); + } + + @Test public void derivedArrayProp() { + MockKnockout my = new MockKnockout(); + MockKnockout.next = my; + + model.applyBindings(); + + model.setCount(10); + + List arr = model.getRepeat(); + assertEquals(arr.size(), 10, "Ten items: " + arr); + + my.mutated.clear(); + + model.setCount(5); + + arr = model.getRepeat(); + assertEquals(arr.size(), 5, "Five items: " + arr); + + assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated); + assertTrue(my.mutated.contains("repeat"), "Array is in there: " + my.mutated); + assertTrue(my.mutated.contains("count"), "Count is in there: " + my.mutated); + } + @Test public void derivedPropertiesAreNotified() { MockKnockout my = new MockKnockout(); MockKnockout.next = my; @@ -61,6 +140,9 @@ model.setValue(33); + // not interested in change of this property + my.mutated.remove("changedProperty"); + 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); @@ -68,7 +150,11 @@ my.mutated.clear(); model.setUnrelated(44); - assertEquals(my.mutated.size(), 1, "One property changed"); + + + // not interested in change of this property + my.mutated.remove("changedProperty"); + assertEquals(my.mutated.size(), 1, "One property changed: " + my.mutated); assertTrue(my.mutated.contains("unrelated"), "Its name is unrelated"); } @@ -92,11 +178,43 @@ } } + @OnFunction + static void doSomething() { + } + @ComputedProperty static int powerValue(int value) { return value * value; } + @OnPropertyChange({ "powerValue", "unrelated" }) + static void aPropertyChanged(Modelik m, String name) { + m.setChangedProperty(name); + } + + @OnPropertyChange({ "values" }) + static void anArrayPropertyChanged(String name, Modelik m) { + m.setChangedProperty(name); + } + + @Test public void changeAnything() { + model.setCount(44); + assertNull(model.getChangedProperty(), "No observed value change"); + } + @Test public void changeValue() { + model.setValue(33); + assertEquals(model.getChangedProperty(), "powerValue", "power property changed"); + } + @Test public void changeUnrelated() { + model.setUnrelated(333); + assertEquals(model.getChangedProperty(), "unrelated", "unrelated changed"); + } + + @Test public void changeInArray() { + model.getValues().add(10); + assertEquals(model.getChangedProperty(), "values", "Something added into the array"); + } + @ComputedProperty static String notAllowedRead() { return "Not allowed callback: " + leakedModel.getUnrelated(); @@ -108,12 +226,31 @@ return "Not allowed callback!"; } + @ComputedProperty + static List repeat(int count) { + return Collections.nCopies(count, "Hello"); + } + static class MockKnockout extends Knockout { - List mutated = new ArrayList(); + List mutated = new ArrayList<>(); + + MockKnockout() { + super(null); + } @Override public void valueHasMutated(String prop) { mutated.add(prop); } } + + public @Test void hasPersonPropertyAndComputedFullName() { + List arr = model.getPeople(); + assertEquals(arr.size(), 0, "By default empty"); + Person p = null; + if (p != null) { + String fullNameGenerated = p.getFullName(); + assertNotNull(fullNameGenerated); + } + } } diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java Thu Apr 11 16:59:42 2013 +0200 @@ -50,7 +50,7 @@ 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()); + ref.pg_title.setText("You want this window to be named " + ref.pg_text.getValue()); } @On(event = CLICK, id={ "pg.title", "pg.text" }) @@ -58,11 +58,11 @@ if (!id.equals("pg.title")) { throw new IllegalStateException(); } - PAGE.PG_TITLE.setText(id); + PAGE.pg_title.setText(id); } @On(event = CLICK, id={ "pg.canvas" }) static void clickCanvas(String id, double layerX) { - PAGE.PG_CANVAS.setWidth((int) layerX); + PAGE.pg_canvas.setWidth((int) layerX); } } diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageTest.java Thu Apr 11 16:59:42 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; + +import java.io.IOException; +import java.util.Locale; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +/** Verify errors emitted by the processor. + * + * @author Jaroslav Tulach + */ +public class PageTest { + @Test public void verifyWrongType() throws IOException { + String html = "" + + ""; + String code = "package x.y.z;\n" + + "import org.apidesign.bck2brwsr.htmlpage.api.*;\n" + + "@Page(xhtml=\"index.xhtml\", className=\"Model\", properties={\n" + + " @Property(name=\"prop\", type=Runnable.class)\n" + + "})\n" + + "class X {\n" + + "}\n"; + + Compile c = Compile.create(html, code); + assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); + for (Diagnostic e : c.getErrors()) { + String msg = e.getMessage(Locale.ENGLISH); + if (!msg.contains("Runnable")) { + fail("Should contain warning about Runnable: " + msg); + } + } + } + +} diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java Thu Apr 11 16:59:42 2013 +0200 @@ -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.Model; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; +import org.apidesign.bck2brwsr.htmlpage.api.Property; + +/** + * + * @author Jaroslav Tulach + */ +@Model(className = "Person", properties = { + @Property(name = "firstName", type = String.class), + @Property(name = "lastName", type = String.class), + @Property(name = "sex", type = Sex.class) +}) +final class PersonImpl { + @ComputedProperty + public static String fullName(String firstName, String lastName) { + return firstName + " " + lastName; + } + + @ComputedProperty + public static String sexType(Sex sex) { + return sex == null ? "unknown" : sex.toString(); + } + + @OnFunction + static void changeSex(Person p) { + if (p.getSex() == Sex.MALE) { + p.setSex(Sex.FEMALE); + } else { + p.setSex(Sex.MALE); + } + } + + @Model(className = "People", properties = { + @Property(array = true, name = "info", type = Person.class), + @Property(array = true, name = "nicknames", type = String.class), + @Property(array = true, name = "age", type = int.class), + @Property(array = true, name = "sex", type = Sex.class) + }) + public class PeopleImpl { + } +} diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Sex.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Sex.java Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,26 @@ +/** + * 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; + +/** + * + * @author Jaroslav Tulach + */ +public enum Sex { + MALE, FEMALE; +} diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-calculator-dynamic/nbactions.xml --- a/javaquery/demo-calculator-dynamic/nbactions.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/nbactions.xml Thu Apr 11 16:59:42 2013 +0200 @@ -23,7 +23,7 @@ run process-classes - org.apidesign.bck2brwsr:mojo:0.5-SNAPSHOT:brwsr + org.apidesign.bck2brwsr:mojo:0.6-SNAPSHOT:brwsr diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-calculator-dynamic/pom.xml --- a/javaquery/demo-calculator-dynamic/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,7 +4,7 @@ org.apidesign.bck2brwsr demo.calculator - 0.5-SNAPSHOT + 0.6-SNAPSHOT jar JavaQuery Demo - Calculator @@ -18,7 +18,7 @@ org.apidesign.bck2brwsr mojo - 0.5-SNAPSHOT + 0.6-SNAPSHOT @@ -27,8 +27,7 @@ - ${project.build.directory}/${project.build.finalName}-bck2brwsr/public_html - index.xhtml + org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml @@ -94,13 +93,13 @@ org.apidesign.bck2brwsr emul - 0.5-SNAPSHOT + 0.6-SNAPSHOT rt org.apidesign.bck2brwsr javaquery.api - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.testng @@ -113,7 +112,7 @@ vm4brwsr js zip - 0.5-SNAPSHOT + 0.6-SNAPSHOT provided diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java --- a/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java Thu Apr 11 16:59:42 2013 +0200 @@ -17,9 +17,11 @@ */ package org.apidesign.bck2brwsr.demo.calc; +import java.util.List; 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.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; @@ -33,11 +35,12 @@ @Property(name = "memory", type = double.class), @Property(name = "display", type = double.class), @Property(name = "operation", type = String.class), - @Property(name = "hover", type = boolean.class) + @Property(name = "hover", type = boolean.class), + @Property(name = "history", type = HistoryImpl.class, array = true) }) public class Calc { static { - new Calculator().applyBindings(); + new Calculator().applyBindings().setOperation("plus"); } @On(event = CLICK, id="clear") @@ -65,14 +68,31 @@ @On(event = CLICK, id="result") static void computeTheValue(Calculator c) { - c.setDisplay(compute( + final double newValue = compute( c.getOperation(), c.getMemory(), c.getDisplay() - )); + ); + c.setDisplay(newValue); + if (!containsValue(c.getHistory(), newValue)) { + History h = new History(); + h.setValue(newValue); + h.setOperation(c.getOperation()); + c.getHistory().add(h); + } c.setMemory(0); } + @OnFunction + static void recoverMemory(Calculator c, History data) { + c.setDisplay(data.getValue()); + } + + @OnFunction + static void removeMemory(Calculator c, History data) { + c.getHistory().remove(data); + } + private static double compute(String op, double memory, double display) { switch (op) { case "plus": return memory + display; @@ -109,4 +129,18 @@ } return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display); } + + @ComputedProperty + static boolean emptyHistory(List history) { + return history.isEmpty(); + } + + private static boolean containsValue(List arr, final double newValue) { + for (History history : arr) { + if (history.getValue() == newValue) { + return true; + } + } + return false; + } } diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/HistoryImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/HistoryImpl.java Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,43 @@ +/** + * 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.Model; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; +import org.apidesign.bck2brwsr.htmlpage.api.Property; + +/** + * + * @author Jaroslav Tulach + */ +@Model(className = "History", properties = { + @Property(name = "value", type = double.class), + @Property(name = "operation", type = String.class) +}) +public class HistoryImpl { + @ComputedProperty + static String resultOf(String operation) { + return "result of " + operation; + } + + @OnFunction + static void twice(History data) { + data.setValue(2.0 * data.getValue()); + } +} diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml --- a/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml Thu Apr 11 16:59:42 2013 +0200 @@ -78,6 +78,19 @@
+

Previous Results

+ +
No results yet.
+ +
-
-    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 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-calculator/pom.xml --- a/javaquery/demo-calculator/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/demo-calculator/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,7 +4,7 @@ org.apidesign.bck2brwsr demo.static.calculator - 0.5-SNAPSHOT + 0.6-SNAPSHOT jar JavaQuery Demo - Calculator - Static Compilation @@ -12,13 +12,14 @@ UTF-8 + FULL org.apidesign.bck2brwsr mojo - 0.5-SNAPSHOT + 0.6-SNAPSHOT @@ -31,7 +32,7 @@ ${project.build.directory}/${project.build.finalName}-bck2brwsr/public_html/ index.xhtml ${project.build.directory}/bck2brwsr.js - FULL + ${bck2brwsr.obfuscationlevel} @@ -97,13 +98,13 @@ org.apidesign.bck2brwsr emul - 0.5-SNAPSHOT + 0.6-SNAPSHOT rt org.apidesign.bck2brwsr javaquery.api - 0.5-SNAPSHOT + 0.6-SNAPSHOT diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java --- a/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java Thu Apr 11 16:59:42 2013 +0200 @@ -17,9 +17,11 @@ */ package org.apidesign.bck2brwsr.demo.calc.staticcompilation; +import java.util.List; 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.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; @@ -33,11 +35,12 @@ @Property(name = "memory", type = double.class), @Property(name = "display", type = double.class), @Property(name = "operation", type = String.class), - @Property(name = "hover", type = boolean.class) + @Property(name = "hover", type = boolean.class), + @Property(name = "history", type = double.class, array = true) }) public class Calc { static { - new Calculator().applyBindings(); + new Calculator().applyBindings().setOperation("plus"); } @On(event = CLICK, id="clear") @@ -65,14 +68,28 @@ @On(event = CLICK, id="result") static void computeTheValue(Calculator c) { - c.setDisplay(compute( + final double newValue = compute( c.getOperation(), c.getMemory(), c.getDisplay() - )); + ); + c.setDisplay(newValue); + if (!c.getHistory().contains(newValue)) { + c.getHistory().add(newValue); + } c.setMemory(0); } + @OnFunction + static void recoverMemory(Calculator c, double data) { + c.setDisplay(data); + } + + @OnFunction + static void removeMemory(Calculator c, double data) { + c.getHistory().remove(data); + } + private static double compute(String op, double memory, double display) { switch (op) { case "plus": return memory + display; @@ -109,4 +126,9 @@ } return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display); } + + @ComputedProperty + static boolean emptyHistory(List history) { + return history.isEmpty(); + } } diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml --- a/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml Thu Apr 11 16:59:42 2013 +0200 @@ -76,8 +76,20 @@ + +

Previous Results

+ +
No results yet.
+ +
- + + + + + diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/twitterExample.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/twitterExample.css Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,50 @@ +/** + * 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. + */ + +/* + Copied from knockout.js Twitter example: + http://knockoutjs.com/examples/twitter.html +*/ + +.configuration, .tweets, .tweets td { font-family: Verdana; font-size: 13px; } +.configuration { background-color: #DEDEDE; border: 2px solid gray; float:left; height: 40em; width: 40%; padding: 0.5em; border-right-width:0; } +.tweets { width: 55%; border: 2px solid gray; height: 40em; overflow: scroll; overflow-x: hidden; background-color: Black; color: White; padding: 0.5em; position: relative; } +.tweets table { border-width: 0;} +.tweets tr { vertical-align: top; } +.tweets td { padding: 0.4em 0.3em 1em 0.4em; border-width: 0; } +.tweets img { width: 4em; } +.tweetInfo { color: Gray; font-size: 0.9em; } +.twitterUser { color: #77AAFF; text-decoration: none; font-size: 1.1em; font-weight: bold; } +input.invalid { border: 1px solid red !important; background-color: #FFAAAA !important; } + +.listChooser select, .listChooser button { vertical-align:top; } +.listChooser select { width: 60%; font-size:1.2em; height:1.4em; } +.listChooser button { width: 19%; height:1.68em; float:right; } + +.currentUsers { height: 28em; overflow-y: auto; overflow-x: hidden; } +.currentUsers button { float: right; height: 2.5em; margin: 0.1em; padding-left: 1em; padding-right: 1em; } +.currentUsers ul, .configuration li { list-style: none; margin: 0; padding: 0 } +.currentUsers li { height: 2.4em; font-size: 1.2em; background-color: #A7D0E3; border: 1px solid gray; margin-bottom: 0.3em; -webkit-border-radius: 5px; -moz-border-radius: 5px; -webkit-box-shadow: 0 0.2em 0.5em gray; -moz-box-shadow: 0 0.2em 0.5em gray; } +.currentUsers li div { padding: 0.6em; } +.currentUsers li:hover { background-color: #EEC; } + +.configuration form label { width: 25%; display: inline-block; text-align:right; overflow: hidden; } +.configuration form input { width:40%; font-size: 1.3em; border:1px solid silver; background-color: White; padding: 0.1em; } +.configuration form button { width: 20%; margin-left: 0.3em; height: 2em; } + +.loadingIndicator { position: absolute; top: 0.1em; left: 0.1em; font: 0.8em Arial; background-color: #229; color: White; padding: 0.2em 0.5em 0.2em 0.5em; display: none; } diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClientTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClientTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,67 @@ +/** + * 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.twitter; + +import java.util.List; +import static org.testng.Assert.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** We can unit test the TwitterModel smoothly. + * + * @author Jaroslav Tulach + */ +public class TwitterClientTest { + private TwitterModel model; + + + @BeforeMethod + public void initModel() { + model = new TwitterModel().applyBindings(); + } + + @Test public void testIsValidToAdd() { + model.setUserNameToAdd("Joe"); + Tweeters t = new Tweeters(); + t.setName("test"); + model.getSavedLists().add(t); + model.setActiveTweetersName("test"); + + assertTrue(model.isUserNameToAddIsValid(), "Joe is OK"); + TwitterClient.addUser(model); + assertFalse(model.isUserNameToAddIsValid(), "Can't add Joe for the 2nd time"); + assertEquals(t.getUserNames().size(), 0, "Original tweeters list remains empty"); + + List mod = model.getActiveTweeters(); + assertTrue(model.isHasUnsavedChanges(), "We have modifications"); + assertEquals(mod.size(), 1, "One element in the list"); + assertEquals(mod.get(0), "Joe", "Its name is Joe"); + + assertSame(model.getActiveTweeters(), mod, "Editing list is the modified one"); + + TwitterClient.saveChanges(model); + assertFalse(model.isHasUnsavedChanges(), "Does not have anything to save"); + + assertSame(model.getActiveTweeters(), mod, "Still editing the old modified one"); + } + + @Test public void httpAtTheEnd() { + String res = TwitterClient.Twt.html("Ahoj http://kuk"); + assertEquals(res, "Ahoj http://kuk"); + } +} diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterProtocolTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterProtocolTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,94 @@ +/** + * 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.twitter; + +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Http; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class TwitterProtocolTest { + private TwitterModel page; + @Http(@Http.Resource( + path = "/search.json", + mimeType = "application/json", + parameters = {"callback"}, + content = "$0({\"completed_in\":0.04,\"max_id\":320055706885689344,\"max_id_str\"" + + ":\"320055706885689344\",\"page\":1,\"query\":\"from%3AJaroslavTulach\",\"refresh_url\":" + + "\"?since_id=320055706885689344&q=from%3AJaroslavTulach\"," + + "\"results\":[{\"created_at\":\"Fri, 05 Apr 2013 06:10:01 +0000\"," + + "\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,\"from_user_id_str\":" + + "\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,\"id\":320055706885689344," + + "\"id_str\":\"320055706885689344\",\"iso_language_code\":\"en\",\"metadata\":{\"result_type\":" + + "\"recent\"},\"profile_image_url\":\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + + "\"@tom_enebo Amzng! Not that I would like #ruby, but I am really glad you guys stabilized the plugin + " + + "made it work in #netbeans 7.3! Gd wrk.\",\"to_user\":\"tom_enebo\",\"to_user_id\":14498747," + + "\"to_user_id_str\":\"14498747\",\"to_user_name\":\"tom_enebo\",\"in_reply_to_status_id\":319832359509839872," + + "\"in_reply_to_status_id_str\":\"319832359509839872\"},{\"created_at\":\"Thu, 04 Apr 2013 07:33:06 +0000\"," + + "\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,\"from_user_id_str\":" + + "\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,\"id\":319714227088678913," + + "\"id_str\":\"319714227088678913\",\"iso_language_code\":\"en\",\"metadata\":{\"result_type\":" + + "\"recent\"},\"profile_image_url\":\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + + "\"RT @drkrab: At #erlangfactory @joerl: Frameworks grow in complexity until nobody can use them.\"}," + + "{\"created_at\":\"Tue, 02 Apr 2013 07:44:34 +0000\",\"from_user\":\"JaroslavTulach\"," + + "\"from_user_id\":420944648,\"from_user_id_str\":\"420944648\",\"from_user_name\":\"Jaroslav Tulach\"," + + "\"geo\":null,\"id\":318992336145248256,\"id_str\":\"318992336145248256\",\"iso_language_code\":\"en\"," + + "\"metadata\":{\"result_type\":\"recent\"},\"profile_image_url\":" + + "\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + + "\"Twitter renamed to twttr http:\\/\\/t.co\\/tqaN4T1xlZ - good, I don't have to rename #bck2brwsr!\"}," + + "{\"created_at\":\"Sun, 31 Mar 2013 03:52:04 +0000\",\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648," + + "\"from_user_id_str\":\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null," + + "\"id\":318209051223789568,\"id_str\":\"318209051223789568\",\"iso_language_code\":\"en\",\"metadata\":" + + "{\"result_type\":\"recent\"},\"profile_image_url\":" + + "\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + + "\"Math proofs without words. Ingenious: http:\\/\\/t.co\\/sz7yVbfpGw\"}],\"results_per_page\":100," + + "\"since_id\":0,\"since_id_str\":\"0\"})" + )) + @BrwsrTest public void readFromTwttr() throws InterruptedException { + if (page == null) { + page = new TwitterModel(); + page.applyBindings(); + page.queryTweets("", "q=xyz"); + } + + if (page.getCurrentTweets().isEmpty()) { + throw new InterruptedException(); + } + + assert 4 == page.getCurrentTweets().size() : "Four tweets: " + page.getCurrentTweets(); + + String firstDate = page.getCurrentTweets().get(0).getCreated_at(); + assert "Fri, 05 Apr 2013 06:10:01 +0000".equals(firstDate) : "Date is OK: " + firstDate; + } + + @Factory public static Object[] create() { + return VMTest.create(TwitterProtocolTest.class); + } +} diff -r 60d9ea48ec99 -r fe7ff18eae8d javaquery/pom.xml --- a/javaquery/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/javaquery/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,16 +4,17 @@ bck2brwsr org.apidesign - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr javaquery - 0.5-SNAPSHOT + 0.6-SNAPSHOT pom JavaQuery API and Demo api demo-calculator demo-calculator-dynamic - - + demo-twitter +
+ \ No newline at end of file diff -r 60d9ea48ec99 -r fe7ff18eae8d pom.xml --- a/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -3,7 +3,7 @@ 4.0.0 org.apidesign bck2brwsr - 0.5-SNAPSHOT + 0.6-SNAPSHOT pom Back 2 Browser @@ -11,6 +11,11 @@ jvnet-parent 3 + + UTF-8 + RELEASE73 + COPYING + dew javaquery @@ -80,21 +85,24 @@ .*/** rt/emul/*/src/main/** rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java - rt/mojo/src/main/resources/archetype-resources/** + rt/archetype/src/main/resources/archetype-resources/** rt/vmtest/src/test/resources/** dew/src/main/resources/org/apidesign/bck2brwsr/dew/** javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout*.js - - org.apache.maven.plugins - maven-release-plugin - 2.4 - - release-${releaseVersion} - - + + maven-release-plugin + 2.4 + + forked-path + false + -Pjvnet-release -Pgpg + release-${releaseVersion} + + + @@ -139,19 +147,113 @@ org.netbeans.api org-netbeans-modules-classfile - RELEASE72 + ${netbeans.version} jar org.netbeans.api org-openide-util-lookup - RELEASE72 + ${netbeans.version} compile jar + + org.netbeans.api + org-netbeans-api-annotations-common + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-java-source + ${netbeans.version} + + + org.netbeans.api + org-netbeans-libs-javacapi + ${netbeans.version} + + + org.netbeans.api + org-netbeans-spi-java-hints + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-parsing-api + ${netbeans.version} + + + org.netbeans.api + org-netbeans-spi-editor-hints + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-java-lexer + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-lexer + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-java-hints-test + ${netbeans.version} + + + org.netbeans.api + org-netbeans-libs-junit4 + ${netbeans.version} + + + org.netbeans.modules + org-netbeans-lib-nbjavac + ${netbeans.version} + + + org.netbeans.modules + org-netbeans-modules-web-browser-api + ${netbeans.version} + + + org-netbeans-core + org.netbeans.modules + + + org-netbeans-core-multiview + org.netbeans.api + + + org-netbeans-libs-lucene + org.netbeans.api + + + org-netbeans-modules-diff + org.netbeans.api + + + org-netbeans-modules-editor-fold + org.netbeans.api + + + org-netbeans-modules-editor-guards + org.netbeans.api + + + + + org-netbeans-modules-projectapi + org.netbeans.api + jar + ${netbeans.version} + - - COPYING - \ No newline at end of file diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,62 @@ + + + 4.0.0 + + rt + org.apidesign.bck2brwsr + 0.6-SNAPSHOT + + org.apidesign.bck2brwsr + bck2brwsr-archetype-html-sample + 0.6-SNAPSHOT + jar + Bck2Brwsr Maven Archetype + + Creates a skeletal HTML page and associated Java controller class. + Runs in any browser (even without Java plugin) with the help of Bck2Brwsr + virtual machine. + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + test + + test + + integration-test + + + ${project.build.directory}/bck2brwsr-archetype-html-sample-${project.version}.jar + + false + + + + + + + + + + org.testng + testng + test + + + diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/java/org/apidesign/bck2brwsr/archetype/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/java/org/apidesign/bck2brwsr/archetype/package-info.java Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,18 @@ +/** + * 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.archetype; diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,55 @@ + + + + + + src/main/java + + **/App.java + + + + src/main/resources + + **/*.xhtml + **/*.html + + + + src/test/java + + **/*Test.java + + + + + + nbactions.xml + + + + + + bck2brwsr-assembly.xml + + + + \ No newline at end of file diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/archetype-resources/bck2brwsr-assembly.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/archetype-resources/bck2brwsr-assembly.xml Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,61 @@ + + + + + bck2brwsr + + zip + + public_html + + + false + runtime + lib + + *:jar + *:rt + + + + false + provided + + *:js + + true + / + + + + + ${project.build.directory}/${project.build.finalName}.jar + / + + + ${project.build.directory}/classes/${package.replace('.','/')}/index.html + / + index.html + + + + \ No newline at end of file diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/archetype-resources/nbactions.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/archetype-resources/nbactions.xml Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,10 @@ + + + + run + + process-classes + org.apidesign.bck2brwsr:mojo:0.6-SNAPSHOT:brwsr + + + diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/archetype-resources/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/archetype-resources/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,135 @@ + + + 4.0.0 + + ${groupId} + ${artifactId} + ${version} + jar + + ${artifactId} + + + + java.net + Java.net + https://maven.java.net/content/repositories/releases/ + + true + + + + netbeans + NetBeans + http://bits.netbeans.org/maven2/ + + + + + java.net + Java.net + https://maven.java.net/content/repositories/releases/ + + true + + + + + + UTF-8 + + + + + org.apidesign.bck2brwsr + mojo + 0.6-SNAPSHOT + + + + brwsr + + + + + ${package.replace('.','/')}/index.html + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + lib/ + + + + + + maven-assembly-plugin + 2.4 + + + distro-assembly + package + + single + + + + bck2brwsr-assembly.xml + + + + + + + + + + + org.apidesign.bck2brwsr + emul + 0.6-SNAPSHOT + rt + + + org.apidesign.bck2brwsr + javaquery.api + 0.6-SNAPSHOT + + + org.testng + testng + 6.5.2 + test + + + org.apidesign.bck2brwsr + vm4brwsr + js + zip + 0.6-SNAPSHOT + provided + + + org.apidesign.bck2brwsr + vmtest + 0.6-SNAPSHOT + test + + + diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/archetype-resources/src/main/java/App.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/archetype-resources/src/main/java/App.java Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,88 @@ +package ${package}; + +import java.util.List; +import org.apidesign.bck2brwsr.htmlpage.api.*; +import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; +import org.apidesign.bck2brwsr.htmlpage.api.Page; +import org.apidesign.bck2brwsr.htmlpage.api.Property; +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; + +/** This is the controller class for associated index.html page. The Index + * is autogenerated by parsing the index.html page. It fields represent individual + * elements annotated by "id" in the page. + */ +@Page(xhtml="index.html", className="Index", properties={ + @Property(name="name", type=String.class), + @Property(name="messages", type=String.class, array=true), +}) +public class App { + static { + Index model = new Index(); + model.setName("World"); + model.applyBindings(); + } + + /** + * @param m the model of the index page creates in static initializer + */ + @On(event = CLICK, id="hello") + static void hello(Index m) { + display(m.getHelloMessage(), m); + m.getMessages().add(m.getHelloMessage()); + } + + /** Reacts when mouse moves over the canvas. + * + * @param m the model of the page + * @param x property "x" extracted from the event generated by the browser + * @param y property "y" from the mouse event + */ + @On(event = MOUSE_MOVE, id="canvas") + static void clearPoint(Index m, int x, int y) { + GraphicsContext g = m.canvas.getContext(); + boolean even = (x + y) % 2 == 0; + if (even) { + g.setFillStyle("blue"); + } else { + g.setFillStyle("red"); + } + g.clearRect(0, 0, 1000, 1000); + g.setFont("italic 40px Calibri"); + g.fillText(m.getHelloMessage(), 10, 40); + } + + /** Callback function called by the KnockOut/Java binding on elements + * representing href's with individual messages being their data. + * + * @param data the data associated with the element + * @param m the model of the page + */ + @OnFunction + static void display(String data, Index m) { + GraphicsContext g = m.canvas.getContext(); + g.clearRect(0, 0, 1000, 1000); + g.setFillStyle("black"); + g.setFont("italic 40px Calibri"); + g.fillText(data, 10, 40); + } + + /** Callback function. + * + * @param data data associated with the actual element on the page + * @param m the model of the page + */ + @OnFunction + static void remove(String data, Index m) { + m.getMessages().remove(data); + } + + @ComputedProperty + static String helloMessage(String name) { + return "Hello " + name + "!"; + } + + @ComputedProperty + static boolean noMessages(List messages) { + return messages.isEmpty(); + } +} diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/archetype-resources/src/main/resources/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/archetype-resources/src/main/resources/index.html Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,31 @@ + + + + + Bck2Brwsr's Hello World + + +

Loading Bck2Brwsr's Hello World...

+ Your name: + +

+ + +

+ + +
No message displayed yet.
+ + + + + + diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/archetype-resources/src/test/java/AppTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/archetype-resources/src/test/java/AppTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -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 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -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 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -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 60d9ea48ec99 -r fe7ff18eae8d rt/archetype/src/test/java/org/apidesign/bck2brwsr/archetype/ArchetypeVersionTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/archetype/src/test/java/org/apidesign/bck2brwsr/archetype/ArchetypeVersionTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,104 @@ +/** + * 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.archetype; + +import java.net.URL; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathFactory; +import org.testng.annotations.Test; +import org.xml.sax.InputSource; +import static org.testng.Assert.*; +import org.testng.annotations.BeforeClass; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + +/** + * + * @author Jaroslav Tulach + */ +public class ArchetypeVersionTest { + private String version; + + public ArchetypeVersionTest() { + } + + @BeforeClass public void readCurrentVersion() throws Exception { + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); + URL u = l.getResource("META-INF/maven/org.apidesign.bck2brwsr/bck2brwsr-archetype-html-sample/pom.xml"); + assertNotNull(u, "Own pom found: " + System.getProperty("java.class.path")); + + final XPathFactory fact = XPathFactory.newInstance(); + fact.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + + XPathExpression xp = fact.newXPath().compile("project/version/text()"); + + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(u.openStream()); + version = xp.evaluate(dom); + + assertFalse(version.isEmpty(), "There should be some version string"); + } + + + @Test public void testComparePomDepsVersions() throws Exception { + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); + URL r = l.getResource("archetype-resources/pom.xml"); + assertNotNull(r, "Archetype pom found"); + + final XPathFactory fact = XPathFactory.newInstance(); + XPathExpression xp2 = fact.newXPath().compile( + "//version[../groupId/text() = 'org.apidesign.bck2brwsr']/text()" + ); + + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); + NodeList arch = (NodeList) xp2.evaluate(dom, XPathConstants.NODESET); + + if (arch.getLength() < 3) { + fail("There should be at least three dependencies to bck2brwsr APIs: " + arch.getLength()); + } + + for (int i = 0; i < arch.getLength(); i++) { + assertEquals(arch.item(i).getTextContent(), version, i + "th dependency needs to be on latest version of bck2brwsr"); + } + } + + @Test public void testNbActions() throws Exception { + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); + URL r = l.getResource("archetype-resources/nbactions.xml"); + assertNotNull(r, "Archetype nb file found"); + + final XPathFactory fact = XPathFactory.newInstance(); + XPathExpression xp2 = fact.newXPath().compile( + "//goal/text()" + ); + + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); + NodeList goals = (NodeList) xp2.evaluate(dom, XPathConstants.NODESET); + + for (int i = 0; i < goals.getLength(); i++) { + String s = goals.item(i).getTextContent(); + if (s.contains("bck2brwsr")) { + String[] arr = s.split(":"); + assertEquals(arr.length, 4, "Three :"); + assertEquals(arr[2], version, "Proper version is used"); + } + } + } +} diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/core/pom.xml --- a/rt/core/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/core/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr rt - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr core - 0.5-SNAPSHOT + 0.6-SNAPSHOT Bck2Brwsr Native Annotations http://maven.apache.org diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/brwsrtest/pom.xml --- a/rt/emul/brwsrtest/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/brwsrtest/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -1,15 +1,14 @@ - + 4.0.0 org.apidesign.bck2brwsr emul.pom - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr brwsrtest - 0.5-SNAPSHOT + 0.6-SNAPSHOT Tests Inside Real Browser http://maven.apache.org diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/compact/pom.xml --- a/rt/emul/compact/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/compact/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr emul.pom - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr emul - 0.5-SNAPSHOT + 0.6-SNAPSHOT Bck2Brwsr API Profile http://maven.apache.org diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/mini/pom.xml --- a/rt/emul/mini/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/mini/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr emul.pom - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr emul.mini - 0.5-SNAPSHOT + 0.6-SNAPSHOT Minimal API Profile http://maven.apache.org @@ -18,7 +18,7 @@ org.apidesign.bck2brwsr core - 0.5-SNAPSHOT + 0.6-SNAPSHOT jar diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/mini/src/main/java/java/lang/Class.java --- a/rt/emul/mini/src/main/java/java/lang/Class.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Class.java Thu Apr 11 16:59:42 2013 +0200 @@ -402,8 +402,15 @@ } return cmpType != null && getComponentType().isAssignableFrom(cmpType); } - String prop = "$instOf_" + getName().replace('.', '_'); - return hasCnstrProperty(cls, prop); + if (isPrimitive()) { + return false; + } else { + if (cls.isPrimitive()) { + return false; + } + String prop = "$instOf_" + getName().replace('.', '_'); + return hasCnstrProperty(cls, prop); + } } @JavaScriptBody(args = { "who", "prop" }, body = @@ -1245,6 +1252,7 @@ } @JavaScriptBody(args = { "sig" }, body = + "if (!sig) sig = '[Ljava/lang/Object;';\n" + "var c = Array[sig];\n" + "if (c) return c;\n" + "c = vm.java_lang_Class(true);\n" + diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/mini/src/main/java/java/lang/Enum.java --- a/rt/emul/mini/src/main/java/java/lang/Enum.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Enum.java Thu Apr 11 16:59:42 2013 +0200 @@ -27,6 +27,7 @@ import java.io.Serializable; import java.io.IOException; +import org.apidesign.bck2brwsr.core.JavaScriptBody; /** * This is the common base class of all Java language enumeration types. @@ -225,15 +226,17 @@ */ public static > T valueOf(Class enumType, String name) { - throw new UnsupportedOperationException(); -// T result = enumType.enumConstantDirectory().get(name); -// if (result != null) -// return result; -// if (name == null) -// throw new NullPointerException("Name is null"); -// throw new IllegalArgumentException( -// "No enum constant " + enumType.getCanonicalName() + "." + name); + for (Object o : values(enumType)) { + T t = enumType.cast(o); + if (name.equals(((Enum)t).name)) { + return t; + } + } + throw new IllegalArgumentException(); } + + @JavaScriptBody(args = { "enumType" }, body = "return enumType.cnstr.$VALUES;") + private static native Object[] values(Class enumType); /** * enum classes cannot have finalize methods. diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/mini/src/main/java/java/lang/String.java --- a/rt/emul/mini/src/main/java/java/lang/String.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/String.java Thu Apr 11 16:59:42 2013 +0200 @@ -2220,9 +2220,19 @@ * replacement is null. * @since 1.5 */ - public String replace(CharSequence target, CharSequence replacement) { - throw new UnsupportedOperationException("This one should be supported, but without dep on rest of regexp"); - } + @JavaScriptBody(args = { "target", "replacement" }, body = + "var s = this.toString();\n" + + "target = target.toString();\n" + + "replacement = replacement.toString();\n" + + "for (;;) {\n" + + " var ret = s.replace(target, replacement);\n" + + " if (ret === s) {\n" + + " return ret;\n" + + " }\n" + + " s = ret;\n" + + "}" + ) + public native String replace(CharSequence target, CharSequence replacement); /** * Splits this string around matches of the given diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/mini/src/main/java/java/lang/reflect/Method.java --- a/rt/emul/mini/src/main/java/java/lang/reflect/Method.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/reflect/Method.java Thu Apr 11 16:59:42 2013 +0200 @@ -501,8 +501,8 @@ throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { - final boolean isStatic = (getModifiers() & Modifier.STATIC) == 0; - if (isStatic && obj == null) { + final boolean nonStatic = (getModifiers() & Modifier.STATIC) == 0; + if (nonStatic && obj == null) { throw new NullPointerException(); } Class[] types = getParameterTypes(); @@ -517,7 +517,7 @@ } } } - Object res = invoke0(isStatic, this, obj, args); + Object res = invokeTry(nonStatic, this, obj, args); if (getReturnType().isPrimitive()) { res = fromPrimitive(getReturnType(), res); } @@ -536,6 +536,15 @@ + "return method._data().apply(self, p);\n" ) private static native Object invoke0(boolean isStatic, Method m, Object self, Object[] args); + + private static Object invokeTry(boolean isStatic, Method m, Object self, Object[] args) + throws InvocationTargetException { + try { + return invoke0(isStatic, m, self, args); + } catch (Throwable ex) { + throw new InvocationTargetException(ex, ex.getMessage()); + } + } static Object fromPrimitive(Class type, Object o) { if (type == Integer.TYPE) { diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/mini/src/main/resources/org/apidesign/vm4brwsr/emul/lang/java_lang_Number.js --- a/rt/emul/mini/src/main/resources/org/apidesign/vm4brwsr/emul/lang/java_lang_Number.js Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/mini/src/main/resources/org/apidesign/vm4brwsr/emul/lang/java_lang_Number.js Thu Apr 11 16:59:42 2013 +0200 @@ -1,226 +1,244 @@ // empty line needed here -Number.prototype.add32 = function(x) { return (this + x) | 0; }; -Number.prototype.sub32 = function(x) { return (this - x) | 0; }; -Number.prototype.mul32 = function(x) { - return (((this * (x >> 16)) << 16) + this * (x & 0xFFFF)) | 0; -}; -Number.prototype.neg32 = function() { return (-this) | 0; }; -Number.prototype.toInt8 = function() { return (this << 24) >> 24; }; -Number.prototype.toInt16 = function() { return (this << 16) >> 16; }; +(function(numberPrototype) { + numberPrototype.add32 = function(x) { + return (this + x) | 0; + }; + numberPrototype.sub32 = function(x) { + return (this - x) | 0; + }; + numberPrototype.mul32 = function(x) { + return (((this * (x >> 16)) << 16) + this * (x & 0xFFFF)) | 0; + }; + numberPrototype.neg32 = function() { + return (-this) | 0; + }; -var __m32 = 0xFFFFFFFF; + numberPrototype.toInt8 = function() { + return (this << 24) >> 24; + }; + numberPrototype.toInt16 = function() { + return (this << 16) >> 16; + }; -Number.prototype.next32 = function(low) { - if (this === 0) { - return low; - } - var l = new Number(low); - l.hi = this | 0; - return l; -}; + var __m32 = 0xFFFFFFFF; -Number.prototype.high32 = function() { - return this.hi ? this.hi : (Math.floor(this / (__m32+1))) | 0; -}; -Number.prototype.toInt32 = function() { return this | 0; }; -Number.prototype.toFP = function() { - return this.hi ? this.hi * (__m32+1) + this : this; -}; -Number.prototype.toLong = function() { - var hi = (this / (__m32+1)) | 0; - var low = (this % (__m32+1)) | 0; - if (low < 0) { - low += __m32+1; - } - - if (this < 0) { - hi -= 1; - } + numberPrototype.next32 = function(low) { + if (this === 0) { + return low; + } + var l = new Number(low); + l.hi = this | 0; + return l; + }; - return hi.next32(low); -}; + numberPrototype.high32 = function() { + return this.hi ? this.hi : (Math.floor(this / (__m32 + 1))) | 0; + }; + numberPrototype.toInt32 = function() { + return this | 0; + }; + numberPrototype.toFP = function() { + return this.hi ? this.hi * (__m32 + 1) + this : this; + }; + numberPrototype.toLong = function() { + var hi = (this / (__m32 + 1)) | 0; + var low = (this % (__m32 + 1)) | 0; + if (low < 0) { + low += __m32 + 1; + } -Number.prototype.toExactString = function() { - if (this.hi) { - // check for Long.MIN_VALUE - if ((this.hi == (0x80000000 | 0)) && (this == 0)) { - return '-9223372036854775808'; + if (this < 0) { + hi -= 1; } - var res = 0; - var a = [ 6,9,2,7,6,9,4,9,2,4 ]; - var s = ''; - var digit; - var neg = this.hi < 0; - if (neg) { - var x = this.neg64(); - var hi = x.hi; - var low = x; - } else { - var hi = this.hi; - var low = this; - } - for (var i = 0; i < a.length; i++) { - res += hi * a[i]; - var low_digit = low % 10; - digit = (res % 10) + low_digit; - low = Math.floor(low / 10); - res = Math.floor(res / 10); - - if (digit >= 10) { - digit -= 10; - res++; - } - s = String(digit).concat(s); - } - s = String(res).concat(s).replace(/^0+/, ''); - return (neg ? '-' : '').concat(s); - } - return String(this); -}; - -Number.prototype.add64 = function(x) { - var low = this + x; - carry = 0; - if (low > __m32) { - carry = 1; - low -= (__m32+1); - } - var hi = (this.high32() + x.high32() + carry) | 0; - return hi.next32(low); -}; - -Number.prototype.sub64 = function(x) { - var low = this - x; - carry = 0; - if (low < 0) { - carry = 1; - low += (__m32+1); - } - var hi = (this.high32() - x.high32() - carry) | 0; - return hi.next32(low); -}; - -Number.prototype.mul64 = function(x) { - var low = this.mul32(x); - low += (low < 0) ? (__m32+1) : 0; - // first count upper 32 bits of (this.low * x.low) - var hi_hi = 0; - var hi_low = 0; - var m = 1; - for (var i = 0; i < 32; i++) { - if (x & m) { - hi_hi += this >>> 16; - hi_low += this & 0xFFFF - } - hi_low >>= 1; - hi_low += (hi_hi & 1) ? 0x8000 : 0; - hi_hi >>= 1; - m <<= 1; - } - var hi = (hi_hi << 16) + hi_low; - - var m1 = this.high32().mul32(x); - var m2 = this.mul32(x.high32()); - hi = hi.add32(m1).add32(m2); - - return hi.next32(low); -}; - -Number.prototype.and64 = function(x) { - var low = this & x; - low += (low < 0) ? (__m32+1) : 0; - if (this.hi && x.hi) { - var hi = this.hi & x.hi; return hi.next32(low); }; - return low; -}; -Number.prototype.or64 = function(x) { - var low = this | x; - low += (low < 0) ? (__m32+1) : 0; - if (this.hi || x.hi) { - var hi = this.hi | x.hi; + numberPrototype.toExactString = function() { + if (this.hi) { + // check for Long.MIN_VALUE + if ((this.hi == (0x80000000 | 0)) && (this == 0)) { + return '-9223372036854775808'; + } + var res = 0; + var a = [6, 9, 2, 7, 6, 9, 4, 9, 2, 4]; + var s = ''; + var digit; + var neg = this.hi < 0; + if (neg) { + var x = this.neg64(); + var hi = x.hi; + var low = x; + } else { + var hi = this.hi; + var low = this; + } + for (var i = 0; i < a.length; i++) { + res += hi * a[i]; + var low_digit = low % 10; + digit = (res % 10) + low_digit; + + low = Math.floor(low / 10); + res = Math.floor(res / 10); + + if (digit >= 10) { + digit -= 10; + res++; + } + s = String(digit).concat(s); + } + s = String(res).concat(s).replace(/^0+/, ''); + return (neg ? '-' : '').concat(s); + } + return String(this); + }; + + numberPrototype.add64 = function(x) { + var low = this + x; + carry = 0; + if (low > __m32) { + carry = 1; + low -= (__m32 + 1); + } + var hi = (this.high32() + x.high32() + carry) | 0; return hi.next32(low); }; - return low; -}; -Number.prototype.xor64 = function(x) { - var low = this ^ x; - low += (low < 0) ? (__m32+1) : 0; - if (this.hi || x.hi) { - var hi = this.hi ^ x.hi; + numberPrototype.sub64 = function(x) { + var low = this - x; + carry = 0; + if (low < 0) { + carry = 1; + low += (__m32 + 1); + } + var hi = (this.high32() - x.high32() - carry) | 0; return hi.next32(low); }; - return low; -}; -Number.prototype.shl64 = function(x) { - if (x >= 32) { - var hi = this << (x - 32); - return hi.next32(0); - } else { - var hi = this.high32() << x; - var low_reminder = this >> (32 - x); - hi |= low_reminder; - var low = this << x; - low += (low < 0) ? (__m32+1) : 0; + numberPrototype.mul64 = function(x) { + var low = this.mul32(x); + low += (low < 0) ? (__m32 + 1) : 0; + // first count upper 32 bits of (this.low * x.low) + var hi_hi = 0; + var hi_low = 0; + var m = 1; + for (var i = 0; i < 32; i++) { + if (x & m) { + hi_hi += this >>> 16; + hi_low += this & 0xFFFF + } + hi_low >>= 1; + hi_low += (hi_hi & 1) ? 0x8000 : 0; + hi_hi >>= 1; + m <<= 1; + } + var hi = (hi_hi << 16) + hi_low; + + var m1 = this.high32().mul32(x); + var m2 = this.mul32(x.high32()); + hi = hi.add32(m1).add32(m2); + return hi.next32(low); - } -}; + }; -Number.prototype.shr64 = function(x) { - if (x >= 32) { - var low = this.high32() >> (x - 32); - low += (low < 0) ? (__m32+1) : 0; + numberPrototype.and64 = function(x) { + var low = this & x; + low += (low < 0) ? (__m32 + 1) : 0; + if (this.hi && x.hi) { + var hi = this.hi & x.hi; + return hi.next32(low); + } + ; return low; - } else { - var low = this >> x; - var hi_reminder = this.high32() << (32 - x); - low |= hi_reminder; - low += (low < 0) ? (__m32+1) : 0; - var hi = this.high32() >> x; - return hi.next32(low); - } -}; + }; -Number.prototype.ushr64 = function(x) { - if (x >= 32) { - var low = this.high32() >>> (x - 32); - low += (low < 0) ? (__m32+1) : 0; + numberPrototype.or64 = function(x) { + var low = this | x; + low += (low < 0) ? (__m32 + 1) : 0; + if (this.hi || x.hi) { + var hi = this.hi | x.hi; + return hi.next32(low); + } + ; return low; - } else { - var low = this >>> x; - var hi_reminder = this.high32() << (32 - x); - low |= hi_reminder; - low += (low < 0) ? (__m32+1) : 0; - var hi = this.high32() >>> x; - return hi.next32(low); - } -}; + }; -Number.prototype.compare64 = function(x) { - if (this.high32() === x.high32()) { - return (this < x) ? -1 : ((this > x) ? 1 : 0); - } - return (this.high32() < x.high32()) ? -1 : 1; -}; + numberPrototype.xor64 = function(x) { + var low = this ^ x; + low += (low < 0) ? (__m32 + 1) : 0; + if (this.hi || x.hi) { + var hi = this.hi ^ x.hi; + return hi.next32(low); + } + ; + return low; + }; -Number.prototype.neg64 = function() { - var hi = this.high32(); - var low = this; - if ((hi === 0) && (low < 0)) { return -low; } - hi = ~hi; - low = ~low; - low += (low < 0) ? (__m32+1) : 0; - var ret = hi.next32(low); - return ret.add64(1); -}; + numberPrototype.shl64 = function(x) { + if (x >= 32) { + var hi = this << (x - 32); + return hi.next32(0); + } else { + var hi = this.high32() << x; + var low_reminder = this >> (32 - x); + hi |= low_reminder; + var low = this << x; + low += (low < 0) ? (__m32 + 1) : 0; + return hi.next32(low); + } + }; -(function(numberPrototype) { + numberPrototype.shr64 = function(x) { + if (x >= 32) { + var low = this.high32() >> (x - 32); + low += (low < 0) ? (__m32 + 1) : 0; + return low; + } else { + var low = this >> x; + var hi_reminder = this.high32() << (32 - x); + low |= hi_reminder; + low += (low < 0) ? (__m32 + 1) : 0; + var hi = this.high32() >> x; + return hi.next32(low); + } + }; + + numberPrototype.ushr64 = function(x) { + if (x >= 32) { + var low = this.high32() >>> (x - 32); + low += (low < 0) ? (__m32 + 1) : 0; + return low; + } else { + var low = this >>> x; + var hi_reminder = this.high32() << (32 - x); + low |= hi_reminder; + low += (low < 0) ? (__m32 + 1) : 0; + var hi = this.high32() >>> x; + return hi.next32(low); + } + }; + + numberPrototype.compare64 = function(x) { + if (this.high32() === x.high32()) { + return (this < x) ? -1 : ((this > x) ? 1 : 0); + } + return (this.high32() < x.high32()) ? -1 : 1; + }; + + numberPrototype.neg64 = function() { + var hi = this.high32(); + var low = this; + if ((hi === 0) && (low < 0)) { + return -low; + } + hi = ~hi; + low = ~low; + low += (low < 0) ? (__m32 + 1) : 0; + var ret = hi.next32(low); + return ret.add64(1); + }; + function __handleDivByZero() { var exception = new vm.java_lang_ArithmeticException; vm.java_lang_ArithmeticException(false).constructor diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/emul/pom.xml --- a/rt/emul/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/emul/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr rt - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr emul.pom - 0.5-SNAPSHOT + 0.6-SNAPSHOT pom Emulation of Core Libraries diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/launcher/pom.xml --- a/rt/launcher/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/launcher/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr rt - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr launcher - 0.5-SNAPSHOT + 0.6-SNAPSHOT Bck2Brwsr Launcher http://maven.apache.org diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Thu Apr 11 16:59:42 2013 +0200 @@ -98,9 +98,9 @@ } HttpServer s = initServer(".", true); int last = startpage.lastIndexOf('/'); + String prefix = startpage.substring(0, last); String simpleName = startpage.substring(last); - s.getServerConfiguration().addHttpHandler(new Page(resources, startpage), simpleName); - s.getServerConfiguration().addHttpHandler(new Page(resources, null), "/"); + s.getServerConfiguration().addHttpHandler(new SubTree(resources, prefix), "/"); try { launchServerAndBrwsr(s, simpleName); } catch (URISyntaxException | InterruptedException ex) { @@ -177,7 +177,16 @@ if (r.httpPath.equals(request.getRequestURI())) { LOG.log(Level.INFO, "Serving HttpResource for {0}", request.getRequestURI()); response.setContentType(r.httpType); - copyStream(r.httpContent, response.getOutputStream(), null); + r.httpContent.reset(); + String[] params = null; + if (r.parameters.length != 0) { + params = new String[r.parameters.length]; + for (int i = 0; i < r.parameters.length; i++) { + params[i] = request.getParameter(r.parameters[i]); + } + } + + copyStream(r.httpContent, response.getOutputStream(), null, params); } } } @@ -315,11 +324,15 @@ } if (ch == '$' && params.length > 0) { int cnt = is.read() - '0'; - if (cnt == 'U' - '0') { + if (baseURL != null && cnt == 'U' - '0') { os.write(baseURL.getBytes("UTF-8")); - } - if (cnt >= 0 && cnt < params.length) { - os.write(params[cnt].getBytes("UTF-8")); + } else { + if (cnt >= 0 && cnt < params.length) { + os.write(params[cnt].getBytes("UTF-8")); + } else { + os.write('$'); + os.write(cnt + '0'); + } } } else { os.write(ch); @@ -450,7 +463,7 @@ } private static class Page extends HttpHandler { - private final String resource; + final String resource; private final String[] args; private final Res res; @@ -462,10 +475,7 @@ @Override public void service(Request request, Response response) throws Exception { - String r = resource; - if (r == null) { - r = request.getHttpHandlerPath(); - } + String r = computePage(request); if (r.startsWith("/")) { r = r.substring(1); } @@ -489,6 +499,28 @@ response.setStatus(404); } } + + protected String computePage(Request request) { + String r = resource; + if (r == null) { + r = request.getHttpHandlerPath(); + } + return r; + } + } + + private static class SubTree extends Page { + + public SubTree(Res res, String resource, String... args) { + super(res, resource, args); + } + + @Override + protected String computePage(Request request) { + return resource + request.getHttpHandlerPath(); + } + + } private static class VM extends HttpHandler { diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Thu Apr 11 16:59:42 2013 +0200 @@ -55,11 +55,11 @@ /** HTTP resource to be available during execution. An invocation may * perform an HTTP query and obtain a resource relative to the page. */ - public void addHttpResource(String relativePath, String mimeType, InputStream content) { - if (relativePath == null || mimeType == null || content == null) { + public void addHttpResource(String relativePath, String mimeType, String[] parameters, InputStream content) { + if (relativePath == null || mimeType == null || content == null || parameters == null) { throw new NullPointerException(); } - resources.add(new Resource(content, mimeType, relativePath)); + resources.add(new Resource(content, mimeType, relativePath, parameters)); } /** Invokes the associated method. @@ -100,11 +100,16 @@ final InputStream httpContent; final String httpType; final String httpPath; + final String[] parameters; - Resource(InputStream httpContent, String httpType, String httpPath) { + Resource(InputStream httpContent, String httpType, String httpPath, + String[] parameters + ) { + httpContent.mark(Integer.MAX_VALUE); this.httpContent = httpContent; this.httpType = httpType; this.httpPath = httpPath; + this.parameters = parameters; } } } diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java Thu Apr 11 16:59:42 2013 +0200 @@ -20,7 +20,6 @@ import java.io.Closeable; import java.io.File; import java.io.IOException; -import java.net.URLClassLoader; import org.apidesign.vm4brwsr.Bck2Brwsr; /** An abstraction for executing tests in a Bck2Brwsr virtual machine. @@ -93,7 +92,7 @@ * @return interface that allows one to stop the server * @throws IOException if something goes wrong */ - public static Closeable showURL(URLClassLoader classes, String startpage) throws IOException { + public static Closeable showURL(ClassLoader classes, String startpage) throws IOException { Bck2BrwsrLauncher l = new Bck2BrwsrLauncher(null); l.addClassLoader(classes); l.showURL(startpage); diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java Thu Apr 11 16:59:42 2013 +0200 @@ -41,28 +41,78 @@ @JavaScriptBody(args = {"id", "attr"}, body = "return window.document.getElementById(id)[attr].toString();") private static native Object getAttr(String id, String attr); + @JavaScriptBody(args = {"elem", "attr"}, body = + "return elem[attr].toString();") + private static native Object getAttr(Object elem, String attr); @JavaScriptBody(args = {"id", "attr", "value"}, body = "window.document.getElementById(id)[attr] = value;") private static native void setAttr(String id, String attr, Object value); + @JavaScriptBody(args = {"elem", "attr", "value"}, body = + "elem[attr] = value;") + private static native void setAttr(Object id, String attr, Object value); @JavaScriptBody(args = {}, body = "return; window.close();") private static native void closeWindow(); + private static Object textArea; + private static Object statusArea; + private static void log(String newText) { - String id = "bck2brwsr.result"; + if (textArea == null) { + return; + } String attr = "value"; - setAttr(id, attr, getAttr(id, attr) + "\n" + newText); - setAttr(id, "scrollTop", getAttr(id, "scrollHeight")); + setAttr(textArea, attr, getAttr(textArea, attr) + "\n" + newText); + setAttr(textArea, "scrollTop", getAttr(textArea, "scrollHeight")); } - public static void execute() throws Exception { - String clazz = (String) getAttr("clazz", "value"); - String method = (String) getAttr("method", "value"); - Object res = invokeMethod(clazz, method); - setAttr("bck2brwsr.result", "value", res); + private static void beginTest(Case c) { + Object[] arr = new Object[2]; + beginTest(c.getClassName() + "." + c.getMethodName(), c, arr); + textArea = arr[0]; + statusArea = arr[1]; + } + + private static void finishTest(Case c, Object res) { + if ("null".equals(res)) { + setAttr(statusArea, "innerHTML", "Success"); + } else { + setAttr(statusArea, "innerHTML", "Result " + res); + } + statusArea = null; + textArea = null; } + @JavaScriptBody(args = { "test", "c", "arr" }, body = + "var ul = window.document.getElementById('bck2brwsr.result');\n" + + "var li = window.document.createElement('li');\n" + + "var span = window.document.createElement('span');" + + "span.innerHTML = test + ' - ';\n" + + "var details = window.document.createElement('a');\n" + + "details.innerHTML = 'Details';\n" + + "details.href = '#';\n" + + "var p = window.document.createElement('p');\n" + + "var status = window.document.createElement('a');\n" + + "status.innerHTML = 'running';" + + "details.onclick = function() { li.appendChild(p); li.removeChild(details); status.innerHTML = 'Run Again'; status.href = '#'; };\n" + + "status.onclick = function() { c.again__V_3Ljava_lang_Object_2(arr); }\n" + + "var pre = window.document.createElement('textarea');\n" + + "pre.cols = 100;" + + "pre.rows = 10;" + + "li.appendChild(span);\n" + + "li.appendChild(status);\n" + + "var span = window.document.createElement('span');" + + "span.innerHTML = ' ';\n" + + "li.appendChild(span);\n" + + "li.appendChild(details);\n" + + "p.appendChild(pre);\n" + + "ul.appendChild(li);\n" + + "arr[0] = pre;\n" + + "arr[1] = status;\n" + ) + private static native void beginTest(String test, Case c, Object[] arr); + @JavaScriptBody(args = { "url", "callback", "arr" }, body = "" + "var request = new XMLHttpRequest();\n" + "request.open('GET', url, true);\n" @@ -84,49 +134,53 @@ private static class Request implements Runnable { private final String[] arr = { null }; private final String url; + private Case c; + private int retries; private Request(String url) throws IOException { this.url = url; loadText(url, this, arr); } + private Request(String url, String u) throws IOException { + this.url = url; + loadText(u, this, arr); + } @Override public void run() { try { - String data = arr[0]; - log("\nGot \"" + data + "\""); + if (c == null) { + String data = arr[0]; + + if (data == null) { + log("Some error exiting"); + closeWindow(); + return; + } + + if (data.isEmpty()) { + log("No data, exiting"); + closeWindow(); + return; + } + + c = Case.parseData(data); + beginTest(c); + log("Got \"" + data + "\""); + } else { + log("Processing \"" + arr[0] + "\" for " + retries + " time"); + } + Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest(); + finishTest(c, result); - if (data == null) { - log("Some error exiting"); - closeWindow(); + String u = url + "?request=" + c.getRequestId() + "&result=" + result; + new Request(url, u); + } catch (Exception ex) { + if (ex instanceof InterruptedException) { + log("Re-scheduling in 100ms"); + schedule(this, 100); return; } - - if (data.isEmpty()) { - log("No data, exiting"); - closeWindow(); - return; - } - - Case c = Case.parseData(data); - if (c.getHtmlFragment() != null) { - setAttr("bck2brwsr.fragment", "innerHTML", c.getHtmlFragment()); - } - log("Invoking " + c.getClassName() + '.' + c.getMethodName() + " as request: " + c.getRequestId()); - - Object result = invokeMethod(c.getClassName(), c.getMethodName()); - - setAttr("bck2brwsr.fragment", "innerHTML", ""); - log("Result: " + result); - - result = encodeURL("" + result); - - log("Sending back: " + url + "?request=" + c.getRequestId() + "&result=" + result); - String u = url + "?request=" + c.getRequestId() + "&result=" + result; - - loadText(u, this, arr); - - } catch (Exception ex) { log(ex.getClass().getName() + ":" + ex.getMessage()); } } @@ -152,8 +206,10 @@ return sb.toString(); } - static String invoke(String clazz, String method) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException { - final Object r = invokeMethod(clazz, method); + static String invoke(String clazz, String method) throws + ClassNotFoundException, InvocationTargetException, IllegalAccessException, + InstantiationException, InterruptedException { + final Object r = new Case(null).invokeMethod(clazz, method); return r == null ? "null" : r.toString().toString(); } @@ -188,40 +244,17 @@ } } - private static Object invokeMethod(String clazz, String method) - throws ClassNotFoundException, InvocationTargetException, - SecurityException, IllegalAccessException, IllegalArgumentException, - InstantiationException { - Method found = null; - Class c = Class.forName(clazz); - for (Method m : c.getMethods()) { - if (m.getName().equals(method)) { - found = m; - } - } - Object res; - if (found != null) { - try { - if ((found.getModifiers() & Modifier.STATIC) != 0) { - res = found.invoke(null); - } else { - res = found.invoke(c.newInstance()); - } - } catch (Throwable ex) { - res = ex.getClass().getName() + ":" + ex.getMessage(); - } - } else { - res = "Can't find method " + method + " in " + clazz; - } - return res; - } - @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;") private static void turnAssetionStatusOn() { } + + @JavaScriptBody(args = {"r", "time"}, body = + "return window.setTimeout(function() { r.run__V(); }, time);") + private static native Object schedule(Runnable r, int time); private static final class Case { private final Object data; + private Object inst; private Case(Object data) { this.data = data; @@ -247,6 +280,69 @@ return value("html", data); } + void again(Object[] arr) { + try { + textArea = arr[0]; + statusArea = arr[1]; + setAttr(textArea, "value", ""); + runTest(); + } catch (Exception ex) { + log(ex.getClass().getName() + ":" + ex.getMessage()); + } + } + + private Object runTest() throws IllegalAccessException, + IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, + InvocationTargetException, InstantiationException, InterruptedException { + if (this.getHtmlFragment() != null) { + setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment()); + } + log("Invoking " + this.getClassName() + '.' + this.getMethodName() + " as request: " + this.getRequestId()); + Object result = invokeMethod(this.getClassName(), this.getMethodName()); + setAttr("bck2brwsr.fragment", "innerHTML", ""); + log("Result: " + result); + result = encodeURL("" + result); + log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result); + return result; + } + + private Object invokeMethod(String clazz, String method) + throws ClassNotFoundException, InvocationTargetException, + InterruptedException, IllegalAccessException, IllegalArgumentException, + InstantiationException { + Method found = null; + Class c = Class.forName(clazz); + for (Method m : c.getMethods()) { + if (m.getName().equals(method)) { + found = m; + } + } + Object res; + if (found != null) { + try { + if ((found.getModifiers() & Modifier.STATIC) != 0) { + res = found.invoke(null); + } else { + if (inst == null) { + inst = c.newInstance(); + } + res = found.invoke(inst); + } + } catch (Throwable ex) { + if (ex instanceof InvocationTargetException) { + ex = ((InvocationTargetException) ex).getTargetException(); + } + if (ex instanceof InterruptedException) { + throw (InterruptedException)ex; + } + res = ex.getClass().getName() + ":" + ex.getMessage(); + } + } else { + res = "Can't find method " + method + " in " + clazz; + } + return res; + } + @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');") private static native Object toJSON(String s); diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/launcher/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml --- a/rt/launcher/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/launcher/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml Thu Apr 11 16:59:42 2013 +0200 @@ -31,8 +31,8 @@

Bck2Brwsr Execution Harness

- +
    +
diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/pom.xml --- a/rt/mojo/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/mojo/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,13 +4,13 @@ org.apidesign.bck2brwsr rt - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr mojo - 0.5-SNAPSHOT + 0.6-SNAPSHOT maven-plugin - Bck2Brwsr Maven Project + Bck2Brwsr Maven Plugins http://maven.apache.org @@ -62,7 +62,7 @@ ${project.groupId} vm4brwsr - 0.5-SNAPSHOT + ${project.version} emul.mini @@ -81,11 +81,5 @@ launcher ${project.version} - - org.testng - testng - 6.5.2 - test - diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/META-INF/maven/archetype-metadata.xml --- a/rt/mojo/src/main/resources/META-INF/maven/archetype-metadata.xml Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ - - - - - - src/main/java - - **/App.java - - - - src/main/resources - - **/*.xhtml - **/*.html - - - - src/test/java - - **/*Test.java - - - - - - nbactions.xml - - - - - - bck2brwsr-assembly.xml - - - - \ No newline at end of file diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/archetype-resources/bck2brwsr-assembly.xml --- a/rt/mojo/src/main/resources/archetype-resources/bck2brwsr-assembly.xml Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ - - - - - bck2brwsr - - zip - - public_html - - - false - runtime - lib - - *:jar - *:rt - - - - false - provided - - *:js - - true - / - - - - - ${project.build.directory}/${project.build.finalName}.jar - / - - - ${project.build.directory}/classes/${package.replace('.','/')}/index.html - / - index.html - - - - \ No newline at end of file diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/archetype-resources/nbactions.xml --- a/rt/mojo/src/main/resources/archetype-resources/nbactions.xml Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ - - - - run - - process-classes - org.apidesign.bck2brwsr:mojo:0.5-SNAPSHOT:brwsr - - - diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/archetype-resources/pom.xml --- a/rt/mojo/src/main/resources/archetype-resources/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ - - - 4.0.0 - - ${groupId} - ${artifactId} - ${version} - jar - - ${artifactId} - - - - java.net - Java.net - https://maven.java.net/content/repositories/releases/ - - true - - - - netbeans - NetBeans - http://bits.netbeans.org/maven2/ - - - - - java.net - Java.net - https://maven.java.net/content/repositories/releases/ - - true - - - - - - UTF-8 - - - - - org.apidesign.bck2brwsr - mojo - 0.5-SNAPSHOT - - - - brwsr - - - - - ${package.replace('.','/')}/index.html - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.7 - 1.7 - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - true - lib/ - - - - - - maven-assembly-plugin - 2.4 - - - distro-assembly - package - - single - - - - bck2brwsr-assembly.xml - - - - - - - - - - - org.apidesign.bck2brwsr - emul - 0.5-SNAPSHOT - rt - - - org.apidesign.bck2brwsr - javaquery.api - 0.5-SNAPSHOT - - - org.testng - testng - 6.5.2 - test - - - org.apidesign.bck2brwsr - vm4brwsr - js - zip - 0.5-SNAPSHOT - provided - - - org.apidesign.bck2brwsr - vmtest - 0.5-SNAPSHOT - test - - - diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/archetype-resources/src/main/java/App.java --- a/rt/mojo/src/main/resources/archetype-resources/src/main/java/App.java Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -package ${package}; - -import org.apidesign.bck2brwsr.htmlpage.api.*; -import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; -import org.apidesign.bck2brwsr.htmlpage.api.Page; -import org.apidesign.bck2brwsr.htmlpage.api.Property; -import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; - -/** Edit the index.xhtml file. Use 'id' to name certain HTML elements. - * Use this class to define behavior of the elements. - */ -@Page(xhtml="index.html", className="Index", properties={ - @Property(name="name", type=String.class) -}) -public class App { - static { - Index model = new Index(); - model.setName("World"); - model.applyBindings(); - } - - @On(event = CLICK, id="hello") - static void hello(Index m) { - GraphicsContext g = m.CANVAS.getContext(); - g.clearRect(0, 0, 1000, 1000); - g.setFont("italic 40px Calibri"); - g.fillText(m.getHelloMessage(), 10, 40); - } - - @ComputedProperty - static String helloMessage(String name) { - return "Hello " + name + "!"; - } -} diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/archetype-resources/src/main/resources/index.html --- a/rt/mojo/src/main/resources/archetype-resources/src/main/resources/index.html Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - - - - - Bck2Brwsr's Hello World - - -

Loading Bck2Brwsr's Hello World...

- Your name: - -

- - -

- - - - - diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/archetype-resources/src/test/java/AppTest.java --- a/rt/mojo/src/main/resources/archetype-resources/src/test/java/AppTest.java Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -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 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java --- a/rt/mojo/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -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 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java --- a/rt/mojo/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -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 60d9ea48ec99 -r fe7ff18eae8d rt/mojo/src/test/java/org/apidesign/bck2brwsr/mojo/ArchetypeVersionTest.java --- a/rt/mojo/src/test/java/org/apidesign/bck2brwsr/mojo/ArchetypeVersionTest.java Mon Mar 25 13:29:42 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +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.mojo; - -import java.net.URL; -import javax.xml.XMLConstants; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathFactory; -import org.testng.annotations.Test; -import org.xml.sax.InputSource; -import static org.testng.Assert.*; -import org.testng.annotations.BeforeClass; -import org.w3c.dom.Document; -import org.w3c.dom.NodeList; - -/** - * - * @author Jaroslav Tulach - */ -public class ArchetypeVersionTest { - private String version; - - public ArchetypeVersionTest() { - } - - @BeforeClass public void readCurrentVersion() throws Exception { - final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); - URL u = l.getResource("META-INF/maven/org.apidesign.bck2brwsr/mojo/plugin-help.xml"); - assertNotNull(u, "Own pom found"); - - final XPathFactory fact = XPathFactory.newInstance(); - fact.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - - XPathExpression xp = fact.newXPath().compile("plugin/version/text()"); - version = xp.evaluate(new InputSource(u.openStream())); - - assertFalse(version.isEmpty(), "There should be some version string"); - } - - - @Test public void testComparePomDepsVersions() throws Exception { - final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); - URL r = l.getResource("archetype-resources/pom.xml"); - assertNotNull(r, "Archetype pom found"); - - final XPathFactory fact = XPathFactory.newInstance(); - XPathExpression xp2 = fact.newXPath().compile( - "//version[../groupId/text() = 'org.apidesign.bck2brwsr']/text()" - ); - - Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); - NodeList arch = (NodeList) xp2.evaluate(dom, XPathConstants.NODESET); - - if (arch.getLength() < 3) { - fail("There should be at least three dependencies to bck2brwsr APIs: " + arch.getLength()); - } - - for (int i = 0; i < arch.getLength(); i++) { - assertEquals(arch.item(i).getTextContent(), version, i + "th dependency needs to be on latest version of bck2brwsr"); - } - } - - @Test public void testNbActions() throws Exception { - final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); - URL r = l.getResource("archetype-resources/nbactions.xml"); - assertNotNull(r, "Archetype nb file found"); - - final XPathFactory fact = XPathFactory.newInstance(); - XPathExpression xp2 = fact.newXPath().compile( - "//goal/text()" - ); - - Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); - NodeList goals = (NodeList) xp2.evaluate(dom, XPathConstants.NODESET); - - for (int i = 0; i < goals.getLength(); i++) { - String s = goals.item(i).getTextContent(); - if (s.contains("bck2brwsr")) { - String[] arr = s.split(":"); - assertEquals(arr.length, 4, "Three :"); - assertEquals(arr[2], version, "Proper version is used"); - } - } - } -} diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/pom.xml --- a/rt/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -3,18 +3,19 @@ 4.0.0 org.apidesign.bck2brwsr rt - 0.5-SNAPSHOT + 0.6-SNAPSHOT pom Bck2Brwsr Runtime org.apidesign bck2brwsr - 0.5-SNAPSHOT + 0.6-SNAPSHOT core emul launcher + archetype mojo vm vmtest diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vm/pom.xml --- a/rt/vm/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vm/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -3,12 +3,12 @@ org.apidesign.bck2brwsr rt - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr vm4brwsr - 0.5-SNAPSHOT + 0.6-SNAPSHOT jar Virtual Machine for Browser @@ -87,7 +87,7 @@ java -cp - + org.apidesign.vm4brwsr.Main --obfuscatelevel MINIMAL diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Thu Apr 11 16:59:42 2013 +0200 @@ -805,7 +805,7 @@ } case opc_ldc_w: case opc_ldc2_w: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); i += 2; String v = encodeConstant(indx); int type = VarType.fromConstantType(jc.getTag(indx)); @@ -847,56 +847,56 @@ "==", topMostLabel); break; case opc_ifeq: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); emitIf(out, "if (@1 == 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifne: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); emitIf(out, "if (@1 != 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_iflt: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); emitIf(out, "if (@1 < 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifle: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); emitIf(out, "if (@1 <= 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifgt: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); emitIf(out, "if (@1 > 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifge: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); emitIf(out, "if (@1 >= 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifnonnull: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); emitIf(out, "if (@1 !== null) ", smapper.popA(), i, indx, topMostLabel); i += 2; break; } case opc_ifnull: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); emitIf(out, "if (@1 === null) ", smapper.popA(), i, indx, topMostLabel); i += 2; @@ -923,7 +923,7 @@ ">=", topMostLabel); break; case opc_goto: { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); goTo(out, i, indx, topMostLabel); i += 2; break; @@ -950,7 +950,7 @@ i = invokeStaticMethod(byteCodes, i, smapper, true); break; case opc_new: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String ci = jc.getClassName(indx); emit(out, "var @1 = new @2;", smapper.pushA(), accessClass(ci.replace('/', '_'))); @@ -963,13 +963,13 @@ generateNewArray(atype, smapper); break; case opc_anewarray: { - int type = readIntArg(byteCodes, i); + int type = readUShortArg(byteCodes, i); i += 2; generateANewArray(type, smapper); break; } case opc_multianewarray: { - int type = readIntArg(byteCodes, i); + int type = readUShortArg(byteCodes, i); i += 2; i = generateMultiANewArray(type, byteCodes, i, smapper); break; @@ -1185,11 +1185,11 @@ case opc_sipush: emit(out, "var @1 = @2;", smapper.pushI(), - Integer.toString(readIntArg(byteCodes, i))); + Integer.toString(readShortArg(byteCodes, i))); i += 2; break; case opc_getfield: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); final String mangleClass = mangleSig(fi[0]); @@ -1202,7 +1202,7 @@ break; } case opc_putfield: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); final String mangleClass = mangleSig(fi[0]); @@ -1216,7 +1216,7 @@ break; } case opc_getstatic: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); emit(out, "var @1 = @2(false)._@3();", @@ -1227,7 +1227,7 @@ break; } case opc_putstatic: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); emit(out, "@1(false)._@2(@3);", @@ -1238,13 +1238,13 @@ break; } case opc_checkcast: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); generateCheckcast(indx, smapper); i += 2; break; } case opc_instanceof: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); generateInstanceOf(indx, smapper); i += 2; break; @@ -1296,7 +1296,7 @@ } private int generateIf(byte[] byteCodes, int i, final Variable v2, final Variable v1, final String test, int topMostLabel) throws IOException { - int indx = i + readIntArg(byteCodes, i); + int indx = i + readShortArg(byteCodes, i); out.append("if (").append(v1) .append(' ').append(test).append(' ') .append(v2).append(") "); @@ -1304,11 +1304,6 @@ return i + 2; } - private int readIntArg(byte[] byteCodes, int offsetInstruction) { - final int indxHi = byteCodes[offsetInstruction + 1] << 8; - final int indxLo = byteCodes[offsetInstruction + 2]; - return (indxHi & 0xffffff00) | (indxLo & 0xff); - } private int readInt4(byte[] byteCodes, int offset) { final int d = byteCodes[offset + 0] << 24; final int c = byteCodes[offset + 1] << 16; @@ -1316,18 +1311,25 @@ final int a = byteCodes[offset + 3]; return (d & 0xff000000) | (c & 0xff0000) | (b & 0xff00) | (a & 0xff); } - private int readUByte(byte[] byteCodes, int offset) { + private static int readUByte(byte[] byteCodes, int offset) { return byteCodes[offset] & 0xff; } - private int readUShort(byte[] byteCodes, int offset) { + private static int readUShort(byte[] byteCodes, int offset) { return ((byteCodes[offset] & 0xff) << 8) | (byteCodes[offset + 1] & 0xff); } + private static int readUShortArg(byte[] byteCodes, int offsetInstruction) { + return readUShort(byteCodes, offsetInstruction + 1); + } - private int readShort(byte[] byteCodes, int offset) { - return (byteCodes[offset] << 8) - | (byteCodes[offset + 1] & 0xff); + private static int readShort(byte[] byteCodes, int offset) { + int signed = byteCodes[offset]; + byte b0 = (byte)signed; + return (b0 << 8) | (byteCodes[offset + 1] & 0xff); + } + private static int readShortArg(byte[] byteCodes, int offsetInstruction) { + return readShort(byteCodes, offsetInstruction + 1); } private static void countArgs(String descriptor, char[] returnType, StringBuilder sig, StringBuilder cnt) { @@ -1455,7 +1457,7 @@ private int invokeStaticMethod(byte[] byteCodes, int i, final StackMapper mapper, boolean isStatic) throws IOException { - int methodIndex = readIntArg(byteCodes, i); + int methodIndex = readUShortArg(byteCodes, i); String[] mi = jc.getFieldInfoName(methodIndex); char[] returnType = { 'V' }; StringBuilder cnt = new StringBuilder(); @@ -1500,7 +1502,7 @@ } private int invokeVirtualMethod(byte[] byteCodes, int i, final StackMapper mapper) throws IOException { - int methodIndex = readIntArg(byteCodes, i); + int methodIndex = readUShortArg(byteCodes, i); String[] mi = jc.getFieldInfoName(methodIndex); char[] returnType = { 'V' }; StringBuilder cnt = new StringBuilder(); @@ -1938,7 +1940,7 @@ final String type = jc.getClassName(indx); if (!type.startsWith("[")) { emit(out, - "if (@1 !== null && !@1.$instOf_@2) throw {};", + "if (@1 !== null && !@1.$instOf_@2) throw vm.java_lang_ClassCastException(true);", smapper.getA(0), type.replace('/', '_')); } else { emit(out, "vm.java_lang_Class(false).forName__Ljava_lang_Class_2Ljava_lang_String_2('@2').cast__Ljava_lang_Object_2Ljava_lang_Object_2(@1);", diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -207,5 +207,12 @@ true ); } + + @Test public void valueOfEnum() throws Exception { + assertExec("can get value of enum", Classes.class, + "valueEnum__Ljava_lang_String_2Ljava_lang_String_2", + "TWO", "TWO" + ); + } } diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Thu Apr 11 16:59:42 2013 +0200 @@ -230,4 +230,7 @@ return Application.class.isAssignableFrom(MyApplication.class); } + public static String valueEnum(String v) { + return ClassesMarker.E.valueOf(v).toString(); + } } diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java Thu Apr 11 16:59:42 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.vm4brwsr; import java.io.UnsupportedEncodingException; +import org.apidesign.bck2brwsr.core.JavaScriptBody; /** * @@ -129,4 +130,20 @@ public String toString() { return HELLO + cnt; } + + @JavaScriptBody(args = {}, body = "return [1, 2];") + private static native Object crtarr(); + @JavaScriptBody(args = { "o" }, body = "return o.toString();") + private static native String toStrng(Object o); + + public static String toStringArray(boolean fakeArr, boolean toString) { + final Object arr = fakeArr ? crtarr() : new Object[2]; + final String whole = toString ? arr.toString() : toStrng(arr); + int zav = whole.indexOf('@'); + if (zav <= 0) { + zav = whole.length(); + } + return whole.substring(0, zav).toString().toString(); + } + } diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -194,6 +194,34 @@ } + @Test public void toStringOnJSArray() throws Exception { + String exp = StringSample.toStringArray(false, true); + + assertExec( + "Treated as Java Object array", + StringSample.class, "toStringArray__Ljava_lang_String_2ZZ", + exp, true, true + ); + } + + @Test public void toStringOnRealArray() throws Exception { + String exp = StringSample.toStringArray(false, true); + + assertExec( + "Is Java Object array", + StringSample.class, "toStringArray__Ljava_lang_String_2ZZ", + exp, false, true + ); + } + + @Test public void valueOfOnJSArray() throws Exception { + assertExec( + "Treated as classical JavaScript array", + StringSample.class, "toStringArray__Ljava_lang_String_2ZZ", + "1,2", true, false + ); + } + private static TestVM code; @BeforeClass diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -40,6 +40,14 @@ compareCode("org/apidesign/vm4brwsr/Classes.class"); } + @Test public void compareGeneratedCodeForToolkitClass() throws Exception { + String genCode = compareCode("org/apidesign/vm4brwsr/Bck2BrwsrToolkit.class"); + int indx = genCode.indexOf("gt = 65604"); + if (indx >= 0) { + fail("Goto to an invalid label:\n...." + genCode.substring(indx - 30, indx + 30) + "...."); + } + } + @BeforeClass public static void compileTheCode() throws Exception { code = TestVM.compileClass("org/apidesign/vm4brwsr/VMinVM"); @@ -49,7 +57,7 @@ code = null; } - private void compareCode(final String nm) throws Exception, IOException { + private String compareCode(final String nm) throws Exception, IOException { byte[] arr = BytesLoader.readClass(nm); String ret1 = VMinVM.toJavaScript(arr); @@ -88,5 +96,7 @@ msg.append(code.toString()); fail(msg.toString()); } + + return ret1; } } diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vm/src/test/resources/org/apidesign/vm4brwsr/Bck2BrwsrToolkit.class Binary file rt/vm/src/test/resources/org/apidesign/vm4brwsr/Bck2BrwsrToolkit.class has changed diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/pom.xml --- a/rt/vmtest/pom.xml Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/pom.xml Thu Apr 11 16:59:42 2013 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr rt - 0.5-SNAPSHOT + 0.6-SNAPSHOT org.apidesign.bck2brwsr vmtest - 0.5-SNAPSHOT + 0.6-SNAPSHOT VM Testing APIs http://bck2brwsr.apidesign.org diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/BrwsrTest.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/BrwsrTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/BrwsrTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -29,6 +29,10 @@ * The browser to is by default executed via {@link java.awt.Desktop#browse(java.net.URI)}, * but one can change that by specifying -Dvmtest.brwsrs=firefox,google-chrome * property. + *

+ * If the annotated method throws {@link InterruptedException}, it will return + * the processing to the browser and after 100ms, called again. This is useful + * for testing asynchronous communication, etc. * * @author Jaroslav Tulach */ diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java Thu Apr 11 16:59:42 2013 +0200 @@ -53,5 +53,10 @@ String resource() default ""; /** mime type of the resource */ String mimeType(); + /** query parameters. Can be referenced from the {@link #content} as + * $0, $1, etc. The values will be extracted + * from URL parameters of the request. + */ + String[] parameters() default {}; } } diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java Thu Apr 11 16:59:42 2013 +0200 @@ -65,17 +65,17 @@ for (Http.Resource r : http) { if (!r.content().isEmpty()) { InputStream is = new ByteArrayInputStream(r.content().getBytes("UTF-8")); - c.addHttpResource(r.path(), r.mimeType(), is); + c.addHttpResource(r.path(), r.mimeType(), r.parameters(), is); } else { InputStream is = m.getDeclaringClass().getResourceAsStream(r.resource()); - c.addHttpResource(r.path(), r.mimeType(), is); + c.addHttpResource(r.path(), r.mimeType(), r.parameters(), is); } } } String res = c.invoke(); value = res; if (fail) { - int idx = res.indexOf(':'); + int idx = res == null ? -1 : res.indexOf(':'); if (idx >= 0) { Class thrwbl = null; try { diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ByteArithmeticTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ByteArithmeticTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ByteArithmeticTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -17,6 +17,7 @@ */ package org.apidesign.bck2brwsr.tck; +import org.apidesign.bck2brwsr.core.JavaScriptBody; import org.apidesign.bck2brwsr.vmtest.Compare; import org.apidesign.bck2brwsr.vmtest.VMTest; import org.testng.annotations.Factory; @@ -94,6 +95,50 @@ @Compare public byte divisionReminder() { return mod((byte)1, (byte)2); } + + private static int readShort(byte[] byteCodes, int offset) { + int signed = byteCodes[offset]; + byte b0 = (byte)signed; + return (b0 << 8) | (byteCodes[offset + 1] & 0xff); + } + + private static int readShortArg(byte[] byteCodes, int offsetInstruction) { + return readShort(byteCodes, offsetInstruction + 1); + } + + @Compare public int readIntArgs255and156() { + final byte[] arr = new byte[] { (byte)0, (byte)255, (byte)156 }; + + assert arr[1] == -1 : "First byte: " + arr[1]; + assert arr[2] == -100 : "Second byte: " + arr[2]; + final int ret = readShortArg(arr, 0); + assert ret < 65000: "Value: " + ret; + return ret; + } + + @JavaScriptBody(args = { "arr" }, body = "arr[1] = 255; arr[2] = 156; return arr;") + private static byte[] fill255and156(byte[] arr) { + arr[1] = (byte)255; + arr[2] = (byte)156; + return arr; + } + + @Compare public int readIntArgs255and156JSArray() { + final byte[] arr = fill255and156(new byte[] { 0, 0, 0 }); + + final int ret = readShortArg(arr, 0); + assert ret < 65000: "Value: " + ret; + return ret; + } + + @Compare public int readIntArgsMinus1andMinus100() { + final byte[] arr = new byte[] { (byte)0, (byte)-1, (byte)-100 }; + + assert arr[1] == -1 : "First byte: " + arr[1]; + assert arr[2] == -100 : "Second byte: " + arr[2]; + + return readShortArg(arr, 0); + } @Factory public static Object[] create() { diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -149,6 +149,18 @@ public int stringToBytesLenght() throws UnsupportedEncodingException { return "\u017dlu\u0165ou\u010dk\u00fd k\u016f\u0148".getBytes("utf8").length; } + + @Compare public String replaceSeq() { + return "Hello World.".replace(".", "!"); + } + @Compare public String replaceSeqAll() { + return "Hello World! Hello World.".replace("World", "Jarda"); + } + @Compare public String replaceSeqAA() { + String res = "aaa".replace("aa", "b"); + assert res.equals("ba") : "Expecting ba: " + res; + return res; + } @Factory public static Object[] create() { diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -18,6 +18,8 @@ package org.apidesign.bck2brwsr.tck; import java.lang.reflect.Array; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; import org.apidesign.bck2brwsr.vmtest.Compare; import org.apidesign.bck2brwsr.vmtest.VMTest; import org.testng.annotations.Factory; @@ -127,6 +129,30 @@ return Array.newInstance(int.class, 3, 3, 3).getClass().getName(); } + @JavaScriptBody(args = {}, body = "return [1, 2];") + private static native Object crtarr(); + + @JavaScriptBody(args = {}, body = "return new Object();") + private static native Object newobj(); + + @BrwsrTest + public static void toStringArray() { + final Object arr = crtarr(); + final Object real = new Object[2]; + assert arr instanceof Object[] : "Any array is Java array: " + arr; + assert arr.getClass() == real.getClass() : "Same classes " + arr + " and " + real.getClass(); + final String str = arr.toString(); + assert str != null; + assert str.startsWith("[Ljava.lang.Object;@") : str; + } + + @BrwsrTest + public static void objectToString() { + String s = newobj().toString(); + assert s != null : "Some string computed"; + assert s.startsWith("java.lang.Object@") : "Regular object toString(): " + s; + } + @Factory public static Object[] create() { diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -53,6 +53,22 @@ return Runnable.class.isInterface(); } + @Compare public boolean isAssignableToPrimitiveType() { + return boolean.class.isAssignableFrom(Runnable.class); + } + + @Compare public boolean isAssignableFromPrimitiveType() { + return Runnable.class.isAssignableFrom(boolean.class); + } + + @Compare public boolean isAssignableLongFromInt() { + return long.class.isAssignableFrom(int.class); + } + + @Compare public boolean isAssignableIntFromLong() { + return int.class.isAssignableFrom(long.class); + } + @Compare public String isRunnableHasRunMethod() throws NoSuchMethodException { return Runnable.class.getMethod("run").getName(); } @@ -87,6 +103,20 @@ StaticUse.class.getMethod("instanceMethod").invoke(null); return "should not happen"; } + + @Compare public String classCastException() { + try { + Integer i = (Integer)StaticUseSub.getNonNull(); + return "" + i.intValue(); + } catch (ClassCastException ex) { + return ex.getClass().getName(); + } + } + + @Compare public String methodThatThrowsException() throws Exception { + StaticUse.class.getMethod("instanceMethod").invoke(new StaticUse()); + return "should not happen"; + } @Compare public Object voidReturnType() throws Exception { return StaticUse.class.getMethod("instanceMethod").getReturnType(); diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java Mon Mar 25 13:29:42 2013 +0100 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java Thu Apr 11 16:59:42 2013 +0200 @@ -30,6 +30,7 @@ } public void instanceMethod() { + throw new IllegalStateException(); } public static int plus(int a, int b) { diff -r 60d9ea48ec99 -r fe7ff18eae8d rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CallMeTwiceTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CallMeTwiceTest.java Thu Apr 11 16:59:42 2013 +0200 @@ -0,0 +1,43 @@ +/** + * 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.vmtest.impl; + +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class CallMeTwiceTest { + int cnt; + + @BrwsrTest public void callMeTwice() throws InterruptedException { + if (cnt++ == 0) { + throw new InterruptedException(); + } + int prevCnt = cnt; + cnt = 0; + assert prevCnt == 2 : "We need to receive two calls " + prevCnt; + } + + @Factory public static Object[] create() { + return VMTest.create(CallMeTwiceTest.class); + } +}