Merging the array/complex type knockout mapping into default branch for version 0.5
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 25 Mar 2013 18:36:50 +0100
changeset 89163c6bddd814e
parent 883 8c14a9f0c232
parent 890 c8810910c311
child 892 16fd25f3a75d
Merging the array/complex type knockout mapping into default branch for version 0.5
     1.1 --- a/javaquery/api/pom.xml	Mon Mar 25 13:33:03 2013 +0100
     1.2 +++ b/javaquery/api/pom.xml	Mon Mar 25 18:36:50 2013 +0100
     1.3 @@ -18,8 +18,8 @@
     1.4                  <artifactId>maven-compiler-plugin</artifactId>
     1.5                  <version>2.3.2</version>
     1.6                  <configuration>
     1.7 -                    <source>1.6</source>
     1.8 -                    <target>1.6</target>
     1.9 +                    <source>1.7</source>
    1.10 +                    <target>1.7</target>
    1.11                  </configuration>
    1.12              </plugin>
    1.13              <plugin>
     2.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java	Mon Mar 25 13:33:03 2013 +0100
     2.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java	Mon Mar 25 18:36:50 2013 +0100
     2.3 @@ -43,7 +43,8 @@
     2.4      }
     2.5      
     2.6      @JavaScriptBody(args = { "object", "property" },
     2.7 -        body = "var p = object[property]; return p ? p : null;"
     2.8 +        body = "if (property === null) return object;\n"
     2.9 +        + "var p = object[property]; return p ? p : null;"
    2.10      )
    2.11      private static Object getProperty(Object object, String property) {
    2.12          return null;
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/KOList.java	Mon Mar 25 18:36:50 2013 +0100
     3.3 @@ -0,0 +1,113 @@
     3.4 +/**
     3.5 + * Back 2 Browser Bytecode Translator
     3.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     3.7 + *
     3.8 + * This program is free software: you can redistribute it and/or modify
     3.9 + * it under the terms of the GNU General Public License as published by
    3.10 + * the Free Software Foundation, version 2 of the License.
    3.11 + *
    3.12 + * This program is distributed in the hope that it will be useful,
    3.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.15 + * GNU General Public License for more details.
    3.16 + *
    3.17 + * You should have received a copy of the GNU General Public License
    3.18 + * along with this program. Look for COPYING file in the top folder.
    3.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    3.20 + */
    3.21 +package org.apidesign.bck2brwsr.htmlpage;
    3.22 +
    3.23 +import java.util.ArrayList;
    3.24 +import java.util.Collection;
    3.25 +import org.apidesign.bck2brwsr.core.JavaScriptOnly;
    3.26 +
    3.27 +/**
    3.28 + *
    3.29 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    3.30 + */
    3.31 +public final class KOList<T> extends ArrayList<T> {
    3.32 +    private final String name;
    3.33 +    private final String[] deps;
    3.34 +    private Knockout model;
    3.35 +
    3.36 +    public KOList(String name, String... deps) {
    3.37 +        this.name = name;
    3.38 +        this.deps = deps;
    3.39 +    }
    3.40 +    
    3.41 +    public void assign(Knockout model) {
    3.42 +        this.model = model;
    3.43 +    }
    3.44 +
    3.45 +    @Override
    3.46 +    public boolean add(T e) {
    3.47 +        boolean ret = super.add(e);
    3.48 +        notifyChange();
    3.49 +        return ret;
    3.50 +    }
    3.51 +
    3.52 +    @Override
    3.53 +    public boolean remove(Object o) {
    3.54 +        boolean ret = super.remove(o);
    3.55 +        notifyChange();
    3.56 +        return ret;
    3.57 +    }
    3.58 +
    3.59 +    @Override
    3.60 +    public void clear() {
    3.61 +        super.clear();
    3.62 +        notifyChange();
    3.63 +    }
    3.64 +
    3.65 +    @Override
    3.66 +    public boolean removeAll(Collection<?> c) {
    3.67 +        boolean ret = super.removeAll(c);
    3.68 +        notifyChange();
    3.69 +        return ret;
    3.70 +    }
    3.71 +
    3.72 +    @Override
    3.73 +    public boolean retainAll(Collection<?> c) {
    3.74 +        boolean ret = super.retainAll(c);
    3.75 +        notifyChange();
    3.76 +        return ret;
    3.77 +    }
    3.78 +
    3.79 +    @Override
    3.80 +    public T set(int index, T element) {
    3.81 +        T ret = super.set(index, element);
    3.82 +        notifyChange();
    3.83 +        return ret;
    3.84 +    }
    3.85 +
    3.86 +    @Override
    3.87 +    public void add(int index, T element) {
    3.88 +        super.add(index, element);
    3.89 +        notifyChange();
    3.90 +    }
    3.91 +
    3.92 +    @Override
    3.93 +    public T remove(int index) {
    3.94 +        T ret = super.remove(index);
    3.95 +        notifyChange();
    3.96 +        return ret;
    3.97 +    }
    3.98 +    
    3.99 +    
   3.100 +    
   3.101 +    @JavaScriptOnly(name = "koArray", value = "function() { return this.toArray___3Ljava_lang_Object_2(); }")
   3.102 +    private static native int koArray();
   3.103 +
   3.104 +    private void notifyChange() {
   3.105 +        Knockout m = model;
   3.106 +        if (m == null) {
   3.107 +            return;
   3.108 +        }
   3.109 +        m.valueHasMutated(name);
   3.110 +        for (String dependant : deps) {
   3.111 +            m.valueHasMutated(dependant);
   3.112 +        }
   3.113 +    }
   3.114 +    
   3.115 +    
   3.116 +}
     4.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java	Mon Mar 25 13:33:03 2013 +0100
     4.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java	Mon Mar 25 18:36:50 2013 +0100
     4.3 @@ -18,6 +18,7 @@
     4.4  package org.apidesign.bck2brwsr.htmlpage;
     4.5  
     4.6  import java.lang.reflect.Method;
     4.7 +import java.util.List;
     4.8  import org.apidesign.bck2brwsr.core.ExtraJavaScript;
     4.9  import org.apidesign.bck2brwsr.core.JavaScriptBody;
    4.10  
    4.11 @@ -29,12 +30,13 @@
    4.12  public class Knockout {
    4.13      /** used by tests */
    4.14      static Knockout next;
    4.15 -    
    4.16 +
    4.17      Knockout() {
    4.18      }
    4.19      
    4.20      public static <M> Knockout applyBindings(
    4.21 -        Class<M> modelClass, M model, String[] propsGettersAndSetters
    4.22 +        Class<M> modelClass, M model, String[] propsGettersAndSetters,
    4.23 +        String[] methodsAndSignatures
    4.24      ) {
    4.25          Knockout bindings = next;
    4.26          next = null;
    4.27 @@ -47,12 +49,18 @@
    4.28                  bind(bindings, model, propsGettersAndSetters[i],
    4.29                      propsGettersAndSetters[i + 1],
    4.30                      propsGettersAndSetters[i + 2],
    4.31 -                    getter.getReturnType().isPrimitive()
    4.32 +                    getter.getReturnType().isPrimitive(),
    4.33 +                    List.class.isAssignableFrom(getter.getReturnType())
    4.34                  );
    4.35              } catch (NoSuchMethodException ex) {
    4.36                  throw new IllegalStateException(ex.getMessage());
    4.37              }
    4.38          }
    4.39 +        for (int i = 0; i < methodsAndSignatures.length; i += 2) {
    4.40 +            expose(
    4.41 +                bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1]
    4.42 +            );
    4.43 +        }
    4.44          applyBindings(bindings);
    4.45          return bindings;
    4.46      }
    4.47 @@ -68,10 +76,11 @@
    4.48      public static void triggerEvent(String id, String ev) {
    4.49      }
    4.50      
    4.51 -    @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive" }, body =
    4.52 +    @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive", "array" }, body =
    4.53            "var bnd = {\n"
    4.54          + "  'read': function() {\n"
    4.55          + "    var v = model[getter]();\n"
    4.56 +        + "    if (array) v = v.koArray();\n"
    4.57          + "    return v;\n"
    4.58          + "  },\n"
    4.59          + "  'owner': bindings\n"
    4.60 @@ -84,7 +93,15 @@
    4.61          + "bindings[prop] = ko['computed'](bnd);"
    4.62      )
    4.63      private static void bind(
    4.64 -        Object bindings, Object model, String prop, String getter, String setter, boolean primitive
    4.65 +        Object bindings, Object model, String prop, String getter, String setter, boolean primitive, boolean array
    4.66 +    ) {
    4.67 +    }
    4.68 +
    4.69 +    @JavaScriptBody(args = { "bindings", "model", "prop", "sig" }, body = 
    4.70 +        "bindings[prop] = function(data, ev) { model[sig](data, ev); };"
    4.71 +    )
    4.72 +    private static void expose(
    4.73 +        Object bindings, Object model, String prop, String sig
    4.74      ) {
    4.75      }
    4.76      
     5.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Mon Mar 25 13:33:03 2013 +0100
     5.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Mon Mar 25 18:36:50 2013 +0100
     5.3 @@ -20,6 +20,7 @@
     5.4  import java.io.IOException;
     5.5  import java.io.InputStream;
     5.6  import java.io.OutputStreamWriter;
     5.7 +import java.io.StringWriter;
     5.8  import java.io.Writer;
     5.9  import java.util.ArrayList;
    5.10  import java.util.Collection;
    5.11 @@ -47,11 +48,14 @@
    5.12  import javax.lang.model.type.MirroredTypeException;
    5.13  import javax.lang.model.type.TypeKind;
    5.14  import javax.lang.model.type.TypeMirror;
    5.15 +import javax.lang.model.util.Types;
    5.16  import javax.tools.Diagnostic;
    5.17  import javax.tools.FileObject;
    5.18  import javax.tools.StandardLocation;
    5.19  import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
    5.20 +import org.apidesign.bck2brwsr.htmlpage.api.Model;
    5.21  import org.apidesign.bck2brwsr.htmlpage.api.On;
    5.22 +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
    5.23  import org.apidesign.bck2brwsr.htmlpage.api.Page;
    5.24  import org.apidesign.bck2brwsr.htmlpage.api.Property;
    5.25  import org.openide.util.lookup.ServiceProvider;
    5.26 @@ -63,89 +67,26 @@
    5.27   */
    5.28  @ServiceProvider(service=Processor.class)
    5.29  @SupportedAnnotationTypes({
    5.30 +    "org.apidesign.bck2brwsr.htmlpage.api.Model",
    5.31      "org.apidesign.bck2brwsr.htmlpage.api.Page",
    5.32 +    "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
    5.33      "org.apidesign.bck2brwsr.htmlpage.api.On"
    5.34  })
    5.35  public final class PageProcessor extends AbstractProcessor {
    5.36      @Override
    5.37      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    5.38 -        for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
    5.39 -            Page p = e.getAnnotation(Page.class);
    5.40 -            if (p == null) {
    5.41 -                continue;
    5.42 -            }
    5.43 -            PackageElement pe = (PackageElement)e.getEnclosingElement();
    5.44 -            String pkg = pe.getQualifiedName().toString();
    5.45 -            
    5.46 -            ProcessPage pp;
    5.47 -            try {
    5.48 -                InputStream is = openStream(pkg, p.xhtml());
    5.49 -                pp = ProcessPage.readPage(is);
    5.50 -                is.close();
    5.51 -            } catch (IOException iOException) {
    5.52 -                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
    5.53 -                return false;
    5.54 -            }
    5.55 -            Writer w;
    5.56 -            String className = p.className();
    5.57 -            if (className.isEmpty()) {
    5.58 -                int indx = p.xhtml().indexOf('.');
    5.59 -                className = p.xhtml().substring(0, indx);
    5.60 -            }
    5.61 -            try {
    5.62 -                FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
    5.63 -                w = new OutputStreamWriter(java.openOutputStream());
    5.64 -                try {
    5.65 -                    w.append("package " + pkg + ";\n");
    5.66 -                    w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
    5.67 -                    w.append("final class ").append(className).append(" {\n");
    5.68 -                    w.append("  private boolean locked;\n");
    5.69 -                    if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
    5.70 -                        return false;
    5.71 -                    }
    5.72 -                    for (String id : pp.ids()) {
    5.73 -                        String tag = pp.tagNameForId(id);
    5.74 -                        String type = type(tag);
    5.75 -                        w.append("  ").append("public final ").
    5.76 -                            append(type).append(' ').append(cnstnt(id)).append(" = new ").
    5.77 -                            append(type).append("(\"").append(id).append("\");\n");
    5.78 -                    }
    5.79 -                    List<String> propsGetSet = new ArrayList<String>();
    5.80 -                    Map<String,Collection<String>> propsDeps = new HashMap<String, Collection<String>>();
    5.81 -                    generateComputedProperties(w, e.getEnclosedElements(), propsGetSet, propsDeps);
    5.82 -                    generateProperties(w, p.properties(), propsGetSet, propsDeps);
    5.83 -                    w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
    5.84 -                    if (!propsGetSet.isEmpty()) {
    5.85 -                        w.write("public " + className + " applyBindings() {\n");
    5.86 -                        w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
    5.87 -                        w.write(className + ".class, this, ");
    5.88 -                        w.write("new String[] {\n");
    5.89 -                        String sep = "";
    5.90 -                        for (String n : propsGetSet) {
    5.91 -                            w.write(sep);
    5.92 -                            if (n == null) {
    5.93 -                                w.write("    null");
    5.94 -                            } else {
    5.95 -                                w.write("    \"" + n + "\"");
    5.96 -                            }
    5.97 -                            sep = ",\n";
    5.98 -                        }
    5.99 -                        w.write("\n  });\n  return this;\n}\n");
   5.100 -                        
   5.101 -                        w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
   5.102 -                        w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
   5.103 -                        w.write("}\n");
   5.104 -                    }
   5.105 -                    w.append("}\n");
   5.106 -                } finally {
   5.107 -                    w.close();
   5.108 -                }
   5.109 -            } catch (IOException ex) {
   5.110 -                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   5.111 -                return false;
   5.112 +        boolean ok = true;
   5.113 +        for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
   5.114 +            if (!processModel(e)) {
   5.115 +                ok = false;
   5.116              }
   5.117          }
   5.118 -        return true;
   5.119 +        for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
   5.120 +            if (!processPage(e)) {
   5.121 +                ok = false;
   5.122 +            }
   5.123 +        }
   5.124 +        return ok;
   5.125      }
   5.126  
   5.127      private InputStream openStream(String pkg, String name) throws IOException {
   5.128 @@ -157,6 +98,145 @@
   5.129              return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
   5.130          }
   5.131      }
   5.132 +    
   5.133 +    private boolean processModel(Element e) {
   5.134 +        boolean ok = true;
   5.135 +        Model m = e.getAnnotation(Model.class);
   5.136 +        if (m == null) {
   5.137 +            return true;
   5.138 +        }
   5.139 +        String pkg = findPkgName(e);
   5.140 +        Writer w;
   5.141 +        String className = m.className();
   5.142 +        try {
   5.143 +            StringWriter body = new StringWriter();
   5.144 +            List<String> propsGetSet = new ArrayList<>();
   5.145 +            Map<String, Collection<String>> propsDeps = new HashMap<>();
   5.146 +            if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   5.147 +                ok = false;
   5.148 +            }
   5.149 +            if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps)) {
   5.150 +                ok = false;
   5.151 +            }
   5.152 +            FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   5.153 +            w = new OutputStreamWriter(java.openOutputStream());
   5.154 +            try {
   5.155 +                w.append("package " + pkg + ";\n");
   5.156 +                w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   5.157 +                w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   5.158 +                w.append("final class ").append(className).append(" {\n");
   5.159 +                w.append("  private Object json;\n");
   5.160 +                w.append("  private boolean locked;\n");
   5.161 +                w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   5.162 +                w.append(body.toString());
   5.163 +                w.append("}\n");
   5.164 +            } finally {
   5.165 +                w.close();
   5.166 +            }
   5.167 +        } catch (IOException ex) {
   5.168 +            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   5.169 +            return false;
   5.170 +        }
   5.171 +        return ok;
   5.172 +    }
   5.173 +    
   5.174 +    private boolean processPage(Element e) {
   5.175 +        boolean ok = true;
   5.176 +        Page p = e.getAnnotation(Page.class);
   5.177 +        if (p == null) {
   5.178 +            return true;
   5.179 +        }
   5.180 +        String pkg = findPkgName(e);
   5.181 +
   5.182 +        ProcessPage pp;
   5.183 +        try (InputStream is = openStream(pkg, p.xhtml())) {
   5.184 +            pp = ProcessPage.readPage(is);
   5.185 +            is.close();
   5.186 +        } catch (IOException iOException) {
   5.187 +            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
   5.188 +            ok = false;
   5.189 +            pp = null;
   5.190 +        }
   5.191 +        Writer w;
   5.192 +        String className = p.className();
   5.193 +        if (className.isEmpty()) {
   5.194 +            int indx = p.xhtml().indexOf('.');
   5.195 +            className = p.xhtml().substring(0, indx);
   5.196 +        }
   5.197 +        try {
   5.198 +            StringWriter body = new StringWriter();
   5.199 +            List<String> propsGetSet = new ArrayList<>();
   5.200 +            List<String> functions = new ArrayList<>();
   5.201 +            Map<String, Collection<String>> propsDeps = new HashMap<>();
   5.202 +            if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   5.203 +                ok = false;
   5.204 +            }
   5.205 +            if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps)) {
   5.206 +                ok = false;
   5.207 +            }
   5.208 +            if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   5.209 +                ok = false;
   5.210 +            }
   5.211 +            
   5.212 +            FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   5.213 +            w = new OutputStreamWriter(java.openOutputStream());
   5.214 +            try {
   5.215 +                w.append("package " + pkg + ";\n");
   5.216 +                w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   5.217 +                w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   5.218 +                w.append("final class ").append(className).append(" {\n");
   5.219 +                w.append("  private boolean locked;\n");
   5.220 +                if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
   5.221 +                    ok = false;
   5.222 +                } else {
   5.223 +                    for (String id : pp.ids()) {
   5.224 +                        String tag = pp.tagNameForId(id);
   5.225 +                        String type = type(tag);
   5.226 +                        w.append("  ").append("public final ").
   5.227 +                            append(type).append(' ').append(cnstnt(id)).append(" = new ").
   5.228 +                            append(type).append("(\"").append(id).append("\");\n");
   5.229 +                    }
   5.230 +                }
   5.231 +                w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   5.232 +                w.append(body.toString());
   5.233 +                if (!propsGetSet.isEmpty()) {
   5.234 +                    w.write("public " + className + " applyBindings() {\n");
   5.235 +                    w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
   5.236 +                    w.write(className + ".class, this, ");
   5.237 +                    w.write("new String[] {\n");
   5.238 +                    String sep = "";
   5.239 +                    for (String n : propsGetSet) {
   5.240 +                        w.write(sep);
   5.241 +                        if (n == null) {
   5.242 +                            w.write("    null");
   5.243 +                        } else {
   5.244 +                            w.write("    \"" + n + "\"");
   5.245 +                        }
   5.246 +                        sep = ",\n";
   5.247 +                    }
   5.248 +                    w.write("\n  }, new String[] {\n");
   5.249 +                    sep = "";
   5.250 +                    for (String n : functions) {
   5.251 +                        w.write(sep);
   5.252 +                        w.write(n);
   5.253 +                        sep = ",\n";
   5.254 +                    }
   5.255 +                    w.write("\n  });\n  return this;\n}\n");
   5.256 +
   5.257 +                    w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
   5.258 +                    w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
   5.259 +                    w.write("}\n");
   5.260 +                }
   5.261 +                w.append("}\n");
   5.262 +            } finally {
   5.263 +                w.close();
   5.264 +            }
   5.265 +        } catch (IOException ex) {
   5.266 +            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   5.267 +            return false;
   5.268 +        }
   5.269 +        return ok;
   5.270 +    }
   5.271  
   5.272      private static String type(String tag) {
   5.273          if (tag.equals("title")) {
   5.274 @@ -184,6 +264,7 @@
   5.275      private boolean initializeOnClick(
   5.276          String className, TypeElement type, Writer w, ProcessPage pp
   5.277      ) throws IOException {
   5.278 +        boolean ok = true;
   5.279          TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   5.280          { //for (Element clazz : pe.getEnclosedElements()) {
   5.281            //  if (clazz.getKind() != ElementKind.CLASS) {
   5.282 @@ -196,58 +277,27 @@
   5.283                  On oc = method.getAnnotation(On.class);
   5.284                  if (oc != null) {
   5.285                      for (String id : oc.id()) {
   5.286 +                        if (pp == null) {
   5.287 +                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   5.288 +                            ok = false;
   5.289 +                            continue;
   5.290 +                        }
   5.291                          if (pp.tagNameForId(id) == null) {
   5.292                              processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   5.293 -                            return false;
   5.294 +                            ok = false;
   5.295 +                            continue;
   5.296                          }
   5.297                          ExecutableElement ee = (ExecutableElement)method;
   5.298 -                        StringBuilder params = new StringBuilder();
   5.299 -                        {
   5.300 -                            boolean first = true;
   5.301 -                            for (VariableElement ve : ee.getParameters()) {
   5.302 -                                if (!first) {
   5.303 -                                    params.append(", ");
   5.304 -                                }
   5.305 -                                first = false;
   5.306 -                                if (ve.asType() == stringType) {
   5.307 -                                    if (ve.getSimpleName().contentEquals("id")) {
   5.308 -                                        params.append('"').append(id).append('"');
   5.309 -                                        continue;
   5.310 -                                    }
   5.311 -                                    params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(ev, \"");
   5.312 -                                    params.append(ve.getSimpleName().toString());
   5.313 -                                    params.append("\")");
   5.314 -                                    continue;
   5.315 -                                }
   5.316 -                                if (processingEnv.getTypeUtils().getPrimitiveType(TypeKind.DOUBLE) == ve.asType()) {
   5.317 -                                    params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(ev, \"");
   5.318 -                                    params.append(ve.getSimpleName().toString());
   5.319 -                                    params.append("\")");
   5.320 -                                    continue;
   5.321 -                                }
   5.322 -                                String rn = ve.asType().toString();
   5.323 -                                int last = rn.lastIndexOf('.');
   5.324 -                                if (last >= 0) {
   5.325 -                                    rn = rn.substring(last + 1);
   5.326 -                                }
   5.327 -                                if (rn.equals(className)) {
   5.328 -                                    params.append(className).append(".this");
   5.329 -                                    continue;
   5.330 -                                }
   5.331 -                                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   5.332 -                                    "@On method can only accept String named 'id' or " + className + " arguments",
   5.333 -                                    ee
   5.334 -                                );
   5.335 -                                return false;
   5.336 -                            }
   5.337 -                        }
   5.338 +                        CharSequence params = wrapParams(ee, id, className, "ev", null);
   5.339                          if (!ee.getModifiers().contains(Modifier.STATIC)) {
   5.340                              processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   5.341 -                            return false;
   5.342 +                            ok = false;
   5.343 +                            continue;
   5.344                          }
   5.345                          if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   5.346                              processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   5.347 -                            return false;
   5.348 +                            ok = false;
   5.349 +                            continue;
   5.350                          }
   5.351                          w.append("  OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
   5.352                              append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
   5.353 @@ -278,7 +328,7 @@
   5.354              
   5.355  
   5.356          }
   5.357 -        return true;
   5.358 +        return ok;
   5.359      }
   5.360  
   5.361      @Override
   5.362 @@ -292,8 +342,7 @@
   5.363          
   5.364          Element cls = findClass(element);
   5.365          Page p = cls.getAnnotation(Page.class);
   5.366 -        PackageElement pe = (PackageElement) cls.getEnclosingElement();
   5.367 -        String pkg = pe.getQualifiedName().toString();
   5.368 +        String pkg = findPkgName(cls);
   5.369          ProcessPage pp;
   5.370          try {
   5.371              InputStream is = openStream(pkg, p.xhtml());
   5.372 @@ -303,7 +352,7 @@
   5.373              return Collections.emptyList();
   5.374          }
   5.375          
   5.376 -        List<Completion> cc = new ArrayList<Completion>();
   5.377 +        List<Completion> cc = new ArrayList<>();
   5.378          userText = userText.substring(1);
   5.379          for (String id : pp.ids()) {
   5.380              if (id.startsWith(userText)) {
   5.381 @@ -324,44 +373,70 @@
   5.382          return e.getEnclosingElement();
   5.383      }
   5.384  
   5.385 -    private static void generateProperties(
   5.386 -        Writer w, Property[] properties, Collection<String> props,
   5.387 -        Map<String,Collection<String>> deps
   5.388 +    private boolean generateProperties(
   5.389 +        Element where,
   5.390 +        Writer w, Property[] properties,
   5.391 +        Collection<String> props, Map<String,Collection<String>> deps
   5.392      ) throws IOException {
   5.393 +        boolean ok = true;
   5.394          for (Property p : properties) {
   5.395 -            final String tn = typeName(p);
   5.396 -            String[] gs = toGetSet(p.name(), tn);
   5.397 +            final String tn;
   5.398 +            tn = typeName(where, p);
   5.399 +            String[] gs = toGetSet(p.name(), tn, p.array());
   5.400  
   5.401 -            w.write("private " + tn + " prop_" + p.name() + ";\n");
   5.402 -            w.write("public " + tn + " " + gs[0] + "() {\n");
   5.403 -            w.write("  if (locked) throw new IllegalStateException();\n");
   5.404 -            w.write("  return prop_" + p.name() + ";\n");
   5.405 -            w.write("}\n");
   5.406 -            w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   5.407 -            w.write("  if (locked) throw new IllegalStateException();\n");
   5.408 -            w.write("  prop_" + p.name() + " = v;\n");
   5.409 -            w.write("  if (ko != null) {\n");
   5.410 -            w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   5.411 -            final Collection<String> dependants = deps.get(p.name());
   5.412 -            if (dependants != null) {
   5.413 -                for (String depProp : dependants) {
   5.414 -                    w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   5.415 +            if (p.array()) {
   5.416 +                w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
   5.417 +                    + p.name() + "\"");
   5.418 +                final Collection<String> dependants = deps.get(p.name());
   5.419 +                if (dependants != null) {
   5.420 +                    for (String depProp : dependants) {
   5.421 +                        w.write(", ");
   5.422 +                        w.write('\"');
   5.423 +                        w.write(depProp);
   5.424 +                        w.write('\"');
   5.425 +                    }
   5.426                  }
   5.427 +                w.write(");\n");
   5.428 +                w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   5.429 +                w.write("  if (locked) throw new IllegalStateException();\n");
   5.430 +                w.write("  prop_" + p.name() + ".assign(ko);\n");
   5.431 +                w.write("  return prop_" + p.name() + ";\n");
   5.432 +                w.write("}\n");
   5.433 +            } else {
   5.434 +                w.write("private " + tn + " prop_" + p.name() + ";\n");
   5.435 +                w.write("public " + tn + " " + gs[0] + "() {\n");
   5.436 +                w.write("  if (locked) throw new IllegalStateException();\n");
   5.437 +                w.write("  return prop_" + p.name() + ";\n");
   5.438 +                w.write("}\n");
   5.439 +                w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   5.440 +                w.write("  if (locked) throw new IllegalStateException();\n");
   5.441 +                w.write("  prop_" + p.name() + " = v;\n");
   5.442 +                w.write("  if (ko != null) {\n");
   5.443 +                w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   5.444 +                final Collection<String> dependants = deps.get(p.name());
   5.445 +                if (dependants != null) {
   5.446 +                    for (String depProp : dependants) {
   5.447 +                        w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   5.448 +                    }
   5.449 +                }
   5.450 +                w.write("  }\n");
   5.451 +                w.write("}\n");
   5.452              }
   5.453 -            w.write("  }\n");
   5.454 -            w.write("}\n");
   5.455              
   5.456              props.add(p.name());
   5.457              props.add(gs[2]);
   5.458              props.add(gs[3]);
   5.459              props.add(gs[0]);
   5.460          }
   5.461 +        return ok;
   5.462      }
   5.463  
   5.464      private boolean generateComputedProperties(
   5.465 -        Writer w, Collection<? extends Element> arr, Collection<String> props,
   5.466 +        Writer w, Property[] fixedProps,
   5.467 +        Collection<? extends Element> arr, Collection<String> props,
   5.468          Map<String,Collection<String>> deps
   5.469      ) throws IOException {
   5.470 +        boolean ok = true;
   5.471          for (Element e : arr) {
   5.472              if (e.getKind() != ElementKind.METHOD) {
   5.473                  continue;
   5.474 @@ -370,23 +445,36 @@
   5.475                  continue;
   5.476              }
   5.477              ExecutableElement ee = (ExecutableElement)e;
   5.478 -            final String tn = ee.getReturnType().toString();
   5.479 +            final TypeMirror rt = ee.getReturnType();
   5.480 +            final Types tu = processingEnv.getTypeUtils();
   5.481 +            TypeMirror ert = tu.erasure(rt);
   5.482 +            String tn = ert.toString();
   5.483 +            boolean array = false;
   5.484 +            if (tn.equals("java.util.List")) {
   5.485 +                array = true;
   5.486 +            }
   5.487 +            
   5.488              final String sn = ee.getSimpleName().toString();
   5.489 -            String[] gs = toGetSet(sn, tn);
   5.490 +            String[] gs = toGetSet(sn, tn, array);
   5.491              
   5.492              w.write("public " + tn + " " + gs[0] + "() {\n");
   5.493              w.write("  if (locked) throw new IllegalStateException();\n");
   5.494              int arg = 0;
   5.495              for (VariableElement pe : ee.getParameters()) {
   5.496                  final String dn = pe.getSimpleName().toString();
   5.497 +                
   5.498 +                if (!verifyPropName(pe, dn, fixedProps)) {
   5.499 +                    ok = false;
   5.500 +                }
   5.501 +                
   5.502                  final String dt = pe.asType().toString();
   5.503 -                String[] call = toGetSet(dn, dt);
   5.504 +                String[] call = toGetSet(dn, dt, false);
   5.505                  w.write("  " + dt + " arg" + (++arg) + " = ");
   5.506                  w.write(call[0] + "();\n");
   5.507                  
   5.508                  Collection<String> depends = deps.get(dn);
   5.509                  if (depends == null) {
   5.510 -                    depends = new LinkedHashSet<String>();
   5.511 +                    depends = new LinkedHashSet<>();
   5.512                      deps.put(dn, depends);
   5.513                  }
   5.514                  depends.add(sn);
   5.515 @@ -405,17 +493,17 @@
   5.516              w.write("    locked = false;\n");
   5.517              w.write("  }\n");
   5.518              w.write("}\n");
   5.519 -            
   5.520 +
   5.521              props.add(e.getSimpleName().toString());
   5.522              props.add(gs[2]);
   5.523              props.add(null);
   5.524              props.add(gs[0]);
   5.525          }
   5.526          
   5.527 -        return true;
   5.528 +        return ok;
   5.529      }
   5.530  
   5.531 -    private static String[] toGetSet(String name, String type) {
   5.532 +    private static String[] toGetSet(String name, String type, boolean array) {
   5.533          String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
   5.534          String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
   5.535          if ("int".equals(type)) {
   5.536 @@ -430,6 +518,14 @@
   5.537              bck2brwsrType = "Z";
   5.538          }
   5.539          final String nu = n.replace('.', '_');
   5.540 +        if (array) {
   5.541 +            return new String[] { 
   5.542 +                "get" + n,
   5.543 +                null,
   5.544 +                "get" + nu + "__Ljava_util_List_2",
   5.545 +                null
   5.546 +            };
   5.547 +        }
   5.548          return new String[]{
   5.549              pref + n, 
   5.550              "set" + n, 
   5.551 @@ -438,11 +534,196 @@
   5.552          };
   5.553      }
   5.554  
   5.555 -    private static String typeName(Property p) {
   5.556 +    private String typeName(Element where, Property p) {
   5.557 +        String ret;
   5.558 +        boolean isModel = false;
   5.559          try {
   5.560 -            return p.type().getName();
   5.561 +            ret = p.type().getName();
   5.562          } catch (MirroredTypeException ex) {
   5.563 -            return ex.getTypeMirror().toString();
   5.564 +            TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror());
   5.565 +            final Element e = processingEnv.getTypeUtils().asElement(tm);
   5.566 +            final Model m = e == null ? null : e.getAnnotation(Model.class);
   5.567 +            if (m != null) {
   5.568 +                ret = findPkgName(e) + '.' + m.className();
   5.569 +                isModel = true;
   5.570 +            } else {
   5.571 +                ret = tm.toString();
   5.572 +            }
   5.573 +        }
   5.574 +        if (p.array()) {
   5.575 +            String bt = findBoxedType(ret);
   5.576 +            if (bt != null) {
   5.577 +                return bt;
   5.578 +            }
   5.579 +        }
   5.580 +        if (!isModel && !"java.lang.String".equals(ret)) {
   5.581 +            String bt = findBoxedType(ret);
   5.582 +            if (bt == null) {
   5.583 +                processingEnv.getMessager().printMessage(
   5.584 +                    Diagnostic.Kind.ERROR, 
   5.585 +                    "Only primitive types supported in the mapping. Not " + ret,
   5.586 +                    where
   5.587 +                );
   5.588 +            }
   5.589 +        }
   5.590 +        return ret;
   5.591 +    }
   5.592 +    
   5.593 +    private static String findBoxedType(String ret) {
   5.594 +        if (ret.equals("boolean")) {
   5.595 +            return Boolean.class.getName();
   5.596 +        }
   5.597 +        if (ret.equals("byte")) {
   5.598 +            return Byte.class.getName();
   5.599 +        }
   5.600 +        if (ret.equals("short")) {
   5.601 +            return Short.class.getName();
   5.602 +        }
   5.603 +        if (ret.equals("char")) {
   5.604 +            return Character.class.getName();
   5.605 +        }
   5.606 +        if (ret.equals("int")) {
   5.607 +            return Integer.class.getName();
   5.608 +        }
   5.609 +        if (ret.equals("long")) {
   5.610 +            return Long.class.getName();
   5.611 +        }
   5.612 +        if (ret.equals("float")) {
   5.613 +            return Float.class.getName();
   5.614 +        }
   5.615 +        if (ret.equals("double")) {
   5.616 +            return Double.class.getName();
   5.617 +        }
   5.618 +        return null;
   5.619 +    }
   5.620 +
   5.621 +    private boolean verifyPropName(Element e, String propName, Property[] existingProps) {
   5.622 +        StringBuilder sb = new StringBuilder();
   5.623 +        String sep = "";
   5.624 +        for (Property property : existingProps) {
   5.625 +            if (property.name().equals(propName)) {
   5.626 +                return true;
   5.627 +            }
   5.628 +            sb.append(sep);
   5.629 +            sb.append('"');
   5.630 +            sb.append(property.name());
   5.631 +            sb.append('"');
   5.632 +            sep = ", ";
   5.633 +        }
   5.634 +        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
   5.635 +            propName + " is not one of known properties: " + sb
   5.636 +            , e
   5.637 +        );
   5.638 +        return false;
   5.639 +    }
   5.640 +
   5.641 +    private static String findPkgName(Element e) {
   5.642 +        for (;;) {
   5.643 +            if (e.getKind() == ElementKind.PACKAGE) {
   5.644 +                return ((PackageElement)e).getQualifiedName().toString();
   5.645 +            }
   5.646 +            e = e.getEnclosingElement();
   5.647          }
   5.648      }
   5.649 +
   5.650 +    private boolean generateFunctions(
   5.651 +        Element clazz, StringWriter body, String className, 
   5.652 +        List<? extends Element> enclosedElements, List<String> functions
   5.653 +    ) {
   5.654 +        for (Element m : enclosedElements) {
   5.655 +            if (m.getKind() != ElementKind.METHOD) {
   5.656 +                continue;
   5.657 +            }
   5.658 +            ExecutableElement e = (ExecutableElement)m;
   5.659 +            OnFunction onF = e.getAnnotation(OnFunction.class);
   5.660 +            if (onF == null) {
   5.661 +                continue;
   5.662 +            }
   5.663 +            if (!e.getModifiers().contains(Modifier.STATIC)) {
   5.664 +                processingEnv.getMessager().printMessage(
   5.665 +                    Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e
   5.666 +                );
   5.667 +                return false;
   5.668 +            }
   5.669 +            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   5.670 +                processingEnv.getMessager().printMessage(
   5.671 +                    Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e
   5.672 +                );
   5.673 +                return false;
   5.674 +            }
   5.675 +            if (e.getReturnType().getKind() != TypeKind.VOID) {
   5.676 +                processingEnv.getMessager().printMessage(
   5.677 +                    Diagnostic.Kind.ERROR, "@OnFunction method should return void", e
   5.678 +                );
   5.679 +                return false;
   5.680 +            }
   5.681 +            String n = e.getSimpleName().toString();
   5.682 +            body.append("void ").append(n).append("(Object data, Object ev) {\n");
   5.683 +            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   5.684 +            body.append(wrapParams(e, null, className, "ev", "data"));
   5.685 +            body.append(");\n");
   5.686 +            body.append("}\n");
   5.687 +            
   5.688 +            functions.add('\"' + n + '\"');
   5.689 +            functions.add('\"' + n + "__VLjava_lang_Object_2Ljava_lang_Object_2" + '\"');
   5.690 +        }
   5.691 +        return true;
   5.692 +    }
   5.693 +
   5.694 +    private CharSequence wrapParams(
   5.695 +        ExecutableElement ee, String id, String className, String evName, String dataName
   5.696 +    ) {
   5.697 +        TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   5.698 +        StringBuilder params = new StringBuilder();
   5.699 +        boolean first = true;
   5.700 +        for (VariableElement ve : ee.getParameters()) {
   5.701 +            if (!first) {
   5.702 +                params.append(", ");
   5.703 +            }
   5.704 +            first = false;
   5.705 +            String toCall = null;
   5.706 +            if (ve.asType() == stringType) {
   5.707 +                if (ve.getSimpleName().contentEquals("id")) {
   5.708 +                    params.append('"').append(id).append('"');
   5.709 +                    continue;
   5.710 +                }
   5.711 +                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString";
   5.712 +            }
   5.713 +            if (ve.asType().getKind() == TypeKind.DOUBLE) {
   5.714 +                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble";
   5.715 +            }
   5.716 +            if (ve.asType().getKind() == TypeKind.INT) {
   5.717 +                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt";
   5.718 +            }
   5.719 +
   5.720 +            if (toCall != null) {
   5.721 +                params.append(toCall).append('(');
   5.722 +                if (dataName != null && ve.getSimpleName().contentEquals("data")) {
   5.723 +                    params.append(dataName);
   5.724 +                    params.append(", null");
   5.725 +                } else {
   5.726 +                    params.append(evName);
   5.727 +                    params.append(", \"");
   5.728 +                    params.append(ve.getSimpleName().toString());
   5.729 +                    params.append("\"");
   5.730 +                }
   5.731 +                params.append(")");
   5.732 +                continue;
   5.733 +            }
   5.734 +            String rn = ve.asType().toString();
   5.735 +            int last = rn.lastIndexOf('.');
   5.736 +            if (last >= 0) {
   5.737 +                rn = rn.substring(last + 1);
   5.738 +            }
   5.739 +            if (rn.equals(className)) {
   5.740 +                params.append(className).append(".this");
   5.741 +                continue;
   5.742 +            }
   5.743 +            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   5.744 +                "@On method can only accept String named 'id' or " + className + " arguments",
   5.745 +                ee
   5.746 +            );
   5.747 +        }
   5.748 +        return params;
   5.749 +    }
   5.750  }
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Model.java	Mon Mar 25 18:36:50 2013 +0100
     6.3 @@ -0,0 +1,43 @@
     6.4 +/**
     6.5 + * Back 2 Browser Bytecode Translator
     6.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     6.7 + *
     6.8 + * This program is free software: you can redistribute it and/or modify
     6.9 + * it under the terms of the GNU General Public License as published by
    6.10 + * the Free Software Foundation, version 2 of the License.
    6.11 + *
    6.12 + * This program is distributed in the hope that it will be useful,
    6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    6.15 + * GNU General Public License for more details.
    6.16 + *
    6.17 + * You should have received a copy of the GNU General Public License
    6.18 + * along with this program. Look for COPYING file in the top folder.
    6.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    6.20 + */
    6.21 +package org.apidesign.bck2brwsr.htmlpage.api;
    6.22 +
    6.23 +import java.lang.annotation.ElementType;
    6.24 +import java.lang.annotation.Retention;
    6.25 +import java.lang.annotation.RetentionPolicy;
    6.26 +import java.lang.annotation.Target;
    6.27 +
    6.28 +/** Defines a model class named {@link #className()} which contains
    6.29 + * defined {@link #properties()}. This class can have methods 
    6.30 + * annotated by {@link ComputedProperty} which define derived
    6.31 + * properties in the model class.
    6.32 + * <p>
    6.33 + * The {@link #className() generated class} will have methods
    6.34 + * to convert the object <code>toJSON</code> and <code>fromJSON</code>.
    6.35 + *
    6.36 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    6.37 + */
    6.38 +@Retention(RetentionPolicy.SOURCE)
    6.39 +@Target(ElementType.TYPE)
    6.40 +public @interface Model {
    6.41 +    /** Name of the model class */
    6.42 +    String className();
    6.43 +    /** List of properties in the model.
    6.44 +     */
    6.45 +    Property[] properties();
    6.46 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnFunction.java	Mon Mar 25 18:36:50 2013 +0100
     7.3 @@ -0,0 +1,34 @@
     7.4 +/**
     7.5 + * Back 2 Browser Bytecode Translator
     7.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     7.7 + *
     7.8 + * This program is free software: you can redistribute it and/or modify
     7.9 + * it under the terms of the GNU General Public License as published by
    7.10 + * the Free Software Foundation, version 2 of the License.
    7.11 + *
    7.12 + * This program is distributed in the hope that it will be useful,
    7.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    7.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    7.15 + * GNU General Public License for more details.
    7.16 + *
    7.17 + * You should have received a copy of the GNU General Public License
    7.18 + * along with this program. Look for COPYING file in the top folder.
    7.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    7.20 + */
    7.21 +package org.apidesign.bck2brwsr.htmlpage.api;
    7.22 +
    7.23 +import java.lang.annotation.ElementType;
    7.24 +import java.lang.annotation.Retention;
    7.25 +import java.lang.annotation.RetentionPolicy;
    7.26 +import java.lang.annotation.Target;
    7.27 +
    7.28 +/** Methods in class annotated by {@link Model} or {@link Page} can be 
    7.29 + * annotated by this annotation to signal that they should be available
    7.30 + * as functions to users of the model classes.
    7.31 + *
    7.32 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    7.33 + */
    7.34 +@Target(ElementType.METHOD)
    7.35 +@Retention(RetentionPolicy.SOURCE)
    7.36 +public @interface OnFunction {
    7.37 +}
     8.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java	Mon Mar 25 13:33:03 2013 +0100
     8.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java	Mon Mar 25 18:36:50 2013 +0100
     8.3 @@ -20,15 +20,36 @@
     8.4  import java.lang.annotation.Retention;
     8.5  import java.lang.annotation.RetentionPolicy;
     8.6  import java.lang.annotation.Target;
     8.7 +import java.util.List;
     8.8  
     8.9 -/** Represents a property in a generated model of an HTML
    8.10 - * {@link Page}.
    8.11 +/** Represents a property. Either in a generated model of an HTML
    8.12 + * {@link Page} or in a class defined by {@link Model}.
    8.13   *
    8.14   * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.15   */
    8.16  @Retention(RetentionPolicy.SOURCE)
    8.17  @Target({})
    8.18  public @interface Property {
    8.19 +    /** Name of the property. Will be used to define proper getter and setter
    8.20 +     * in the associated class.
    8.21 +     * 
    8.22 +     * @return valid java identifier
    8.23 +     */
    8.24      String name();
    8.25 +    
    8.26 +    /** Type of the property. Can either be primitive type (like <code>int.class</code>,
    8.27 +     * <code>double.class</code>, etc.), {@link String} or complex model
    8.28 +     * class (defined by {@link Model} property).
    8.29 +     * 
    8.30 +     * @return the class of the property
    8.31 +     */
    8.32      Class<?> type();
    8.33 +    
    8.34 +    /** Is this property an array of the {@link #type()} or a single value?
    8.35 +     * If the property is an array, only its getter (returning mutable {@link List} of
    8.36 +     * the boxed {@link #type()}).
    8.37 +     * 
    8.38 +     * @return true, if this is supposed to be an array of values.
    8.39 +     */
    8.40 +    boolean array() default false;
    8.41  }
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Compile.java	Mon Mar 25 18:36:50 2013 +0100
     9.3 @@ -0,0 +1,203 @@
     9.4 +/**
     9.5 + * Back 2 Browser Bytecode Translator
     9.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     9.7 + *
     9.8 + * This program is free software: you can redistribute it and/or modify
     9.9 + * it under the terms of the GNU General Public License as published by
    9.10 + * the Free Software Foundation, version 2 of the License.
    9.11 + *
    9.12 + * This program is distributed in the hope that it will be useful,
    9.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    9.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    9.15 + * GNU General Public License for more details.
    9.16 + *
    9.17 + * You should have received a copy of the GNU General Public License
    9.18 + * along with this program. Look for COPYING file in the top folder.
    9.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    9.20 + */
    9.21 +package org.apidesign.bck2brwsr.htmlpage;
    9.22 +
    9.23 +import java.io.ByteArrayInputStream;
    9.24 +import java.io.ByteArrayOutputStream;
    9.25 +import java.io.IOException;
    9.26 +import java.io.InputStream;
    9.27 +import java.io.OutputStream;
    9.28 +import java.net.URI;
    9.29 +import java.net.URISyntaxException;
    9.30 +import java.util.ArrayList;
    9.31 +import java.util.Arrays;
    9.32 +import java.util.HashMap;
    9.33 +import java.util.List;
    9.34 +import java.util.Map;
    9.35 +import java.util.regex.Matcher;
    9.36 +import java.util.regex.Pattern;
    9.37 +import javax.tools.Diagnostic;
    9.38 +import javax.tools.DiagnosticListener;
    9.39 +import javax.tools.FileObject;
    9.40 +import javax.tools.ForwardingJavaFileManager;
    9.41 +import javax.tools.JavaFileManager;
    9.42 +import javax.tools.JavaFileObject;
    9.43 +import javax.tools.JavaFileObject.Kind;
    9.44 +import javax.tools.SimpleJavaFileObject;
    9.45 +import javax.tools.StandardJavaFileManager;
    9.46 +import javax.tools.StandardLocation;
    9.47 +import javax.tools.ToolProvider;
    9.48 +
    9.49 +/**
    9.50 + *
    9.51 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    9.52 + */
    9.53 +final class Compile implements DiagnosticListener<JavaFileObject> {
    9.54 +    private final List<Diagnostic<? extends JavaFileObject>> errors = new ArrayList<>();
    9.55 +    private final Map<String, byte[]> classes;
    9.56 +    private final String pkg;
    9.57 +    private final String cls;
    9.58 +    private final String html;
    9.59 +
    9.60 +    private Compile(String html, String code) throws IOException {
    9.61 +        this.pkg = findPkg(code);
    9.62 +        this.cls = findCls(code);
    9.63 +        this.html = html;
    9.64 +        classes = compile(html, code);
    9.65 +    }
    9.66 +
    9.67 +    /** Performs compilation of given HTML page and associated Java code
    9.68 +     */
    9.69 +    public static Compile create(String html, String code) throws IOException {
    9.70 +        return new Compile(html, code);
    9.71 +    }
    9.72 +    
    9.73 +    /** Checks for given class among compiled resources */
    9.74 +    public byte[] get(String res) {
    9.75 +        return classes.get(res);
    9.76 +    }
    9.77 +    
    9.78 +    /** Obtains errors created during compilation.
    9.79 +     */
    9.80 +    public List<Diagnostic<? extends JavaFileObject>> getErrors() {
    9.81 +        List<Diagnostic<? extends JavaFileObject>> err = new ArrayList<>();
    9.82 +        for (Diagnostic<? extends JavaFileObject> diagnostic : errors) {
    9.83 +            if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
    9.84 +                err.add(diagnostic);
    9.85 +            }
    9.86 +        }
    9.87 +        return err;
    9.88 +    }
    9.89 +    
    9.90 +    private Map<String, byte[]> compile(final String html, final String code) throws IOException {
    9.91 +        StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null);
    9.92 +
    9.93 +        final Map<String, ByteArrayOutputStream> class2BAOS = new HashMap<>();
    9.94 +
    9.95 +        JavaFileObject file = new SimpleJavaFileObject(URI.create("mem://mem"), Kind.SOURCE) {
    9.96 +            @Override
    9.97 +            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
    9.98 +                return code;
    9.99 +            }
   9.100 +        };
   9.101 +        final JavaFileObject htmlFile = new SimpleJavaFileObject(URI.create("mem://mem2"), Kind.OTHER) {
   9.102 +            @Override
   9.103 +            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
   9.104 +                return html;
   9.105 +            }
   9.106 +
   9.107 +            @Override
   9.108 +            public InputStream openInputStream() throws IOException {
   9.109 +                return new ByteArrayInputStream(html.getBytes());
   9.110 +            }
   9.111 +        };
   9.112 +        
   9.113 +        final URI scratch;
   9.114 +        try {
   9.115 +            scratch = new URI("mem://mem3");
   9.116 +        } catch (URISyntaxException ex) {
   9.117 +            throw new IOException(ex);
   9.118 +        }
   9.119 +        
   9.120 +        JavaFileManager jfm = new ForwardingJavaFileManager<JavaFileManager>(sjfm) {
   9.121 +            @Override
   9.122 +            public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
   9.123 +                if (kind  == Kind.CLASS) {
   9.124 +                    final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
   9.125 +
   9.126 +                    class2BAOS.put(className.replace('.', '/') + ".class", buffer);
   9.127 +                    return new SimpleJavaFileObject(sibling.toUri(), kind) {
   9.128 +                        @Override
   9.129 +                        public OutputStream openOutputStream() throws IOException {
   9.130 +                            return buffer;
   9.131 +                        }
   9.132 +                    };
   9.133 +                }
   9.134 +                
   9.135 +                if (kind == Kind.SOURCE) {
   9.136 +                    return new SimpleJavaFileObject(scratch/*sibling.toUri()*/, kind) {
   9.137 +                        private final ByteArrayOutputStream data = new ByteArrayOutputStream();
   9.138 +                        @Override
   9.139 +                        public OutputStream openOutputStream() throws IOException {
   9.140 +                            return data;
   9.141 +                        }
   9.142 +
   9.143 +                        @Override
   9.144 +                        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
   9.145 +                            data.close();
   9.146 +                            return new String(data.toByteArray());
   9.147 +                        }
   9.148 +                    };
   9.149 +                }
   9.150 +                
   9.151 +                throw new IllegalStateException();
   9.152 +            }
   9.153 +
   9.154 +            @Override
   9.155 +            public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
   9.156 +                if (location == StandardLocation.SOURCE_PATH) {
   9.157 +                    if (packageName.equals(pkg)) {
   9.158 +                        return htmlFile;
   9.159 +                    }
   9.160 +                }
   9.161 +                
   9.162 +                return null;
   9.163 +            }
   9.164 +            
   9.165 +        };
   9.166 +
   9.167 +        ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", "1.7", "-target", "1.7"), null, Arrays.asList(file)).call();
   9.168 +
   9.169 +        Map<String, byte[]> result = new HashMap<>();
   9.170 +
   9.171 +        for (Map.Entry<String, ByteArrayOutputStream> e : class2BAOS.entrySet()) {
   9.172 +            result.put(e.getKey(), e.getValue().toByteArray());
   9.173 +        }
   9.174 +
   9.175 +        return result;
   9.176 +    }
   9.177 +
   9.178 +
   9.179 +    @Override
   9.180 +    public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
   9.181 +        errors.add(diagnostic);
   9.182 +    }
   9.183 +    private static String findPkg(String java) throws IOException {
   9.184 +        Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE);
   9.185 +        Matcher m = p.matcher(java);
   9.186 +        if (!m.find()) {
   9.187 +            throw new IOException("Can't find package declaration in the java file");
   9.188 +        }
   9.189 +        String pkg = m.group(1);
   9.190 +        return pkg;
   9.191 +    }
   9.192 +    private static String findCls(String java) throws IOException {
   9.193 +        Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE);
   9.194 +        Matcher m = p.matcher(java);
   9.195 +        if (!m.find()) {
   9.196 +            throw new IOException("Can't find package declaration in the java file");
   9.197 +        }
   9.198 +        String cls = m.group(1);
   9.199 +        return cls;
   9.200 +    }
   9.201 +
   9.202 +    String getHtml() {
   9.203 +        String fqn = "'" + pkg + '.' + cls + "'";
   9.204 +        return html.replace("'${fqn}'", fqn);
   9.205 +    }
   9.206 +}
    10.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java	Mon Mar 25 13:33:03 2013 +0100
    10.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java	Mon Mar 25 18:36:50 2013 +0100
    10.3 @@ -17,8 +17,11 @@
    10.4   */
    10.5  package org.apidesign.bck2brwsr.htmlpage;
    10.6  
    10.7 +import java.util.List;
    10.8 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
    10.9  import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
   10.10  import org.apidesign.bck2brwsr.htmlpage.api.OnEvent;
   10.11 +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
   10.12  import org.apidesign.bck2brwsr.htmlpage.api.Page;
   10.13  import org.apidesign.bck2brwsr.htmlpage.api.Property;
   10.14  import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
   10.15 @@ -31,7 +34,9 @@
   10.16   * @author Jaroslav Tulach <jtulach@netbeans.org>
   10.17   */
   10.18  @Page(xhtml="Knockout.xhtml", className="KnockoutModel", properties={
   10.19 -    @Property(name="name", type=String.class)
   10.20 +    @Property(name="name", type=String.class),
   10.21 +    @Property(name="results", type=String.class, array = true),
   10.22 +    @Property(name="callbackCount", type=int.class)
   10.23  }) 
   10.24  public class KnockoutTest {
   10.25      
   10.26 @@ -50,13 +55,82 @@
   10.27          assert "Jardo".equals(m.getName()) : "Name property updated: " + m.getName();
   10.28      }
   10.29      
   10.30 +    @HtmlFragment(
   10.31 +        "<ul id='ul' data-bind='foreach: results'>\n"
   10.32 +        + "  <li data-bind='text: $data, click: $root.call'/>\n"
   10.33 +        + "</ul>\n"
   10.34 +    )
   10.35 +    @BrwsrTest public void displayContentOfArray() {
   10.36 +        KnockoutModel m = new KnockoutModel();
   10.37 +        m.getResults().add("Ahoj");
   10.38 +        m.applyBindings();
   10.39 +        
   10.40 +        int cnt = countChildren("ul");
   10.41 +        assert cnt == 1 : "One child, but was " + cnt;
   10.42 +        
   10.43 +        m.getResults().add("Hi");
   10.44 +
   10.45 +        cnt = countChildren("ul");
   10.46 +        assert cnt == 2 : "Two children now, but was " + cnt;
   10.47 +        
   10.48 +        triggerChildClick("ul", 1);
   10.49 +        
   10.50 +        assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount();
   10.51 +        assert "Hi".equals(m.getName()) : "We got callback from 2nd child " + m.getName();
   10.52 +    }
   10.53 +    
   10.54 +    @HtmlFragment(
   10.55 +        "<ul id='ul' data-bind='foreach: cmpResults'>\n"
   10.56 +        + "  <li><b data-bind='text: $data'></b></li>\n"
   10.57 +        + "</ul>\n"
   10.58 +    )
   10.59 +    @BrwsrTest public void displayContentOfDerivedArray() {
   10.60 +        KnockoutModel m = new KnockoutModel();
   10.61 +        m.getResults().add("Ahoj");
   10.62 +        m.applyBindings();
   10.63 +        
   10.64 +        int cnt = countChildren("ul");
   10.65 +        assert cnt == 1 : "One child, but was " + cnt;
   10.66 +        
   10.67 +        m.getResults().add("hello");
   10.68 +
   10.69 +        cnt = countChildren("ul");
   10.70 +        assert cnt == 2 : "Two children now, but was " + cnt;
   10.71 +    }
   10.72 +    
   10.73 +    @OnFunction
   10.74 +    static void call(KnockoutModel m, String data) {
   10.75 +        m.setName(data);
   10.76 +        m.setCallbackCount(m.getCallbackCount() + 1);
   10.77 +    }
   10.78 +    
   10.79      @ComputedProperty
   10.80      static String helloMessage(String name) {
   10.81          return "Hello " + name + "!";
   10.82      }
   10.83      
   10.84 +    @ComputedProperty
   10.85 +    static List<String> cmpResults(List<String> results) {
   10.86 +        return results;
   10.87 +    }
   10.88 +    
   10.89      @Factory
   10.90      public static Object[] create() {
   10.91          return VMTest.create(KnockoutTest.class);
   10.92      }
   10.93 +    
   10.94 +    @JavaScriptBody(args = { "id" }, body = 
   10.95 +          "var e = window.document.getElementById(id);\n "
   10.96 +        + "if (typeof e === 'undefined') return -2;\n "
   10.97 +        + "return e.children.length;\n "
   10.98 +    )
   10.99 +    private static native int countChildren(String id);
  10.100 +
  10.101 +    @JavaScriptBody(args = { "id", "pos" }, body = 
  10.102 +          "var e = window.document.getElementById(id);\n "
  10.103 +        + "var ev = window.document.createEvent('MouseEvents');\n "
  10.104 +        + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n "
  10.105 +        + "e.children[pos].dispatchEvent(ev);\n "
  10.106 +    )
  10.107 +    private static native void triggerChildClick(String id, int pos);
  10.108  }
    11.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Mon Mar 25 13:33:03 2013 +0100
    11.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Mon Mar 25 18:36:50 2013 +0100
    11.3 @@ -18,8 +18,12 @@
    11.4  package org.apidesign.bck2brwsr.htmlpage;
    11.5  
    11.6  import java.util.ArrayList;
    11.7 +import java.util.Collections;
    11.8 +import java.util.Iterator;
    11.9  import java.util.List;
   11.10 +import java.util.ListIterator;
   11.11  import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
   11.12 +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
   11.13  import org.apidesign.bck2brwsr.htmlpage.api.Page;
   11.14  import org.apidesign.bck2brwsr.htmlpage.api.Property;
   11.15  import static org.testng.Assert.*;
   11.16 @@ -30,17 +34,21 @@
   11.17   *
   11.18   * @author Jaroslav Tulach <jtulach@netbeans.org>
   11.19   */
   11.20 -@Page(xhtml = "Empty.html", className = "Model", properties = {
   11.21 +@Page(xhtml = "Empty.html", className = "Modelik", properties = {
   11.22      @Property(name = "value", type = int.class),
   11.23 -    @Property(name = "unrelated", type = long.class)
   11.24 +    @Property(name = "count", type = int.class),
   11.25 +    @Property(name = "unrelated", type = long.class),
   11.26 +    @Property(name = "names", type = String.class, array = true),
   11.27 +    @Property(name = "values", type = int.class, array = true),
   11.28 +    @Property(name = "people", type = PersonImpl.class, array = true)
   11.29  })
   11.30  public class ModelTest {
   11.31 -    private Model model;
   11.32 -    private static Model leakedModel;
   11.33 +    private Modelik model;
   11.34 +    private static Modelik leakedModel;
   11.35      
   11.36      @BeforeMethod
   11.37      public void createModel() {
   11.38 -        model = new Model();
   11.39 +        model = new Modelik();
   11.40      }
   11.41      
   11.42      @Test public void classGeneratedWithSetterGetter() {
   11.43 @@ -53,6 +61,75 @@
   11.44          assertEquals(16, model.getPowerValue());
   11.45      }
   11.46      
   11.47 +    @Test public void arrayIsMutable() {
   11.48 +        assertEquals(model.getNames().size(), 0, "Is empty");
   11.49 +        model.getNames().add("Jarda");
   11.50 +        assertEquals(model.getNames().size(), 1, "One element");
   11.51 +    }
   11.52 +    
   11.53 +    @Test public void arrayChangesNotified() {
   11.54 +        MockKnockout my = new MockKnockout();
   11.55 +        MockKnockout.next = my;
   11.56 +        
   11.57 +        model.applyBindings();
   11.58 +        
   11.59 +        model.getNames().add("Hello");
   11.60 +        
   11.61 +        assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
   11.62 +        assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
   11.63 +
   11.64 +        my.mutated.clear();
   11.65 +        
   11.66 +        Iterator<String> it = model.getNames().iterator();
   11.67 +        assertEquals(it.next(), "Hello");
   11.68 +        it.remove();
   11.69 +        
   11.70 +        assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
   11.71 +        assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
   11.72 +
   11.73 +        my.mutated.clear();
   11.74 +        
   11.75 +        ListIterator<String> lit = model.getNames().listIterator();
   11.76 +        lit.add("Jarda");
   11.77 +        
   11.78 +        assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
   11.79 +        assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
   11.80 +    }
   11.81 +    
   11.82 +    @Test public void autoboxedArray() {
   11.83 +        MockKnockout my = new MockKnockout();
   11.84 +        MockKnockout.next = my;
   11.85 +        
   11.86 +        model.applyBindings();
   11.87 +        
   11.88 +        model.getValues().add(10);
   11.89 +        
   11.90 +        assertEquals(model.getValues().get(0), Integer.valueOf(10), "Really ten");
   11.91 +    }
   11.92 +
   11.93 +    @Test public void derivedArrayProp() {
   11.94 +        MockKnockout my = new MockKnockout();
   11.95 +        MockKnockout.next = my;
   11.96 +        
   11.97 +        model.applyBindings();
   11.98 +        
   11.99 +        model.setCount(10);
  11.100 +        
  11.101 +        List<String> arr = model.getRepeat();
  11.102 +        assertEquals(arr.size(), 10, "Ten items: " + arr);
  11.103 +        
  11.104 +        my.mutated.clear();
  11.105 +        
  11.106 +        model.setCount(5);
  11.107 +        
  11.108 +        arr = model.getRepeat();
  11.109 +        assertEquals(arr.size(), 5, "Five items: " + arr);
  11.110 +
  11.111 +        assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated);
  11.112 +        assertTrue(my.mutated.contains("repeat"), "Array is in there: " + my.mutated);
  11.113 +        assertTrue(my.mutated.contains("count"), "Count is in there: " + my.mutated);
  11.114 +    }
  11.115 +    
  11.116      @Test public void derivedPropertiesAreNotified() {
  11.117          MockKnockout my = new MockKnockout();
  11.118          MockKnockout.next = my;
  11.119 @@ -92,6 +169,10 @@
  11.120          }
  11.121      }
  11.122      
  11.123 +    @OnFunction 
  11.124 +    static void doSomething() {
  11.125 +    }
  11.126 +    
  11.127      @ComputedProperty
  11.128      static int powerValue(int value) {
  11.129          return value * value;
  11.130 @@ -108,12 +189,27 @@
  11.131          return "Not allowed callback!";
  11.132      }
  11.133      
  11.134 +    @ComputedProperty
  11.135 +    static List<String> repeat(int count) {
  11.136 +        return Collections.nCopies(count, "Hello");
  11.137 +    }
  11.138 +    
  11.139      static class MockKnockout extends Knockout {
  11.140 -        List<String> mutated = new ArrayList<String>();
  11.141 +        List<String> mutated = new ArrayList<>();
  11.142          
  11.143          @Override
  11.144          public void valueHasMutated(String prop) {
  11.145              mutated.add(prop);
  11.146          }
  11.147      }
  11.148 +    
  11.149 +    public @Test void hasPersonPropertyAndComputedFullName() {
  11.150 +        List<Person> arr = model.getPeople();
  11.151 +        assertEquals(arr.size(), 0, "By default empty");
  11.152 +        Person p = null;
  11.153 +        if (p != null) {
  11.154 +            String fullNameGenerated = p.getFullName();
  11.155 +            assertNotNull(fullNameGenerated);
  11.156 +        }
  11.157 +    }
  11.158  }
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageTest.java	Mon Mar 25 18:36:50 2013 +0100
    12.3 @@ -0,0 +1,50 @@
    12.4 +/**
    12.5 + * Back 2 Browser Bytecode Translator
    12.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    12.7 + *
    12.8 + * This program is free software: you can redistribute it and/or modify
    12.9 + * it under the terms of the GNU General Public License as published by
   12.10 + * the Free Software Foundation, version 2 of the License.
   12.11 + *
   12.12 + * This program is distributed in the hope that it will be useful,
   12.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   12.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12.15 + * GNU General Public License for more details.
   12.16 + *
   12.17 + * You should have received a copy of the GNU General Public License
   12.18 + * along with this program. Look for COPYING file in the top folder.
   12.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
   12.20 + */
   12.21 +package org.apidesign.bck2brwsr.htmlpage;
   12.22 +
   12.23 +import java.io.IOException;
   12.24 +import java.util.Locale;
   12.25 +import static org.testng.Assert.*;
   12.26 +import org.testng.annotations.Test;
   12.27 +
   12.28 +/** Verify errors emitted by the processor.
   12.29 + *
   12.30 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   12.31 + */
   12.32 +public class PageTest {
   12.33 +    @Test public void verifyWrongType() throws IOException {
   12.34 +        String html = "<html><body>"
   12.35 +            + "</body></html>";
   12.36 +        String code = "package x.y.z;\n"
   12.37 +            + "import org.apidesign.bck2brwsr.htmlpage.api.*;\n"
   12.38 +            + "@Page(xhtml=\"index.xhtml\", className=\"Model\", properties={\n"
   12.39 +            + "  @Property(name=\"prop\", type=Runnable.class)\n"
   12.40 +            + "})\n"
   12.41 +            + "class X {\n"
   12.42 +            + "}\n";
   12.43 +        
   12.44 +        Compile c = Compile.create(html, code);
   12.45 +        assertEquals(c.getErrors().size(), 1, "One error: " + c.getErrors());
   12.46 +        
   12.47 +        String msg = c.getErrors().get(0).getMessage(Locale.ENGLISH);
   12.48 +        if (!msg.contains("Runnable")) {
   12.49 +            fail("Should contain warning about Runnable: " + msg);
   12.50 +        }
   12.51 +    }
   12.52 +    
   12.53 +}
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java	Mon Mar 25 18:36:50 2013 +0100
    13.3 @@ -0,0 +1,43 @@
    13.4 +/**
    13.5 + * Back 2 Browser Bytecode Translator
    13.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    13.7 + *
    13.8 + * This program is free software: you can redistribute it and/or modify
    13.9 + * it under the terms of the GNU General Public License as published by
   13.10 + * the Free Software Foundation, version 2 of the License.
   13.11 + *
   13.12 + * This program is distributed in the hope that it will be useful,
   13.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   13.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13.15 + * GNU General Public License for more details.
   13.16 + *
   13.17 + * You should have received a copy of the GNU General Public License
   13.18 + * along with this program. Look for COPYING file in the top folder.
   13.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
   13.20 + */
   13.21 +package org.apidesign.bck2brwsr.htmlpage;
   13.22 +
   13.23 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
   13.24 +import org.apidesign.bck2brwsr.htmlpage.api.Model;
   13.25 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
   13.26 +
   13.27 +/**
   13.28 + *
   13.29 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   13.30 + */
   13.31 +@Model(className = "Person", properties = {
   13.32 +    @Property(name = "firstName", type = String.class),
   13.33 +    @Property(name = "lastName", type = String.class),
   13.34 +    @Property(name = "male", type = boolean.class)
   13.35 +})
   13.36 +final class PersonImpl {
   13.37 +    @ComputedProperty 
   13.38 +    public static String fullName(String firstName, String lastName) {
   13.39 +        return firstName + " " + lastName;
   13.40 +    }
   13.41 +    
   13.42 +    @ComputedProperty
   13.43 +    public static String sex(boolean male) {
   13.44 +        return male ? "Male" : "Female";
   13.45 +    }
   13.46 +}
    14.1 --- a/javaquery/demo-calculator-dynamic/pom.xml	Mon Mar 25 13:33:03 2013 +0100
    14.2 +++ b/javaquery/demo-calculator-dynamic/pom.xml	Mon Mar 25 18:36:50 2013 +0100
    14.3 @@ -27,8 +27,7 @@
    14.4                      </execution>
    14.5                  </executions>
    14.6                  <configuration>
    14.7 -                    <directory>${project.build.directory}/${project.build.finalName}-bck2brwsr/public_html</directory>
    14.8 -                    <startpage>index.xhtml</startpage>
    14.9 +                    <startpage>org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml</startpage>
   14.10                  </configuration>
   14.11              </plugin>
   14.12           <plugin>
    15.1 --- a/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java	Mon Mar 25 13:33:03 2013 +0100
    15.2 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java	Mon Mar 25 18:36:50 2013 +0100
    15.3 @@ -17,9 +17,11 @@
    15.4   */
    15.5  package org.apidesign.bck2brwsr.demo.calc;
    15.6  
    15.7 +import java.util.List;
    15.8  import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
    15.9  import org.apidesign.bck2brwsr.htmlpage.api.On;
   15.10  import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;
   15.11 +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
   15.12  import org.apidesign.bck2brwsr.htmlpage.api.Page;
   15.13  import org.apidesign.bck2brwsr.htmlpage.api.Property;
   15.14  
   15.15 @@ -33,11 +35,12 @@
   15.16      @Property(name = "memory", type = double.class),
   15.17      @Property(name = "display", type = double.class),
   15.18      @Property(name = "operation", type = String.class),
   15.19 -    @Property(name = "hover", type = boolean.class)
   15.20 +    @Property(name = "hover", type = boolean.class),
   15.21 +    @Property(name = "history", type = double.class, array = true)
   15.22  })
   15.23  public class Calc {
   15.24      static {
   15.25 -        new Calculator().applyBindings();
   15.26 +        new Calculator().applyBindings().setOperation("plus");
   15.27      }
   15.28      
   15.29      @On(event = CLICK, id="clear")
   15.30 @@ -65,14 +68,28 @@
   15.31      
   15.32      @On(event = CLICK, id="result")
   15.33      static void computeTheValue(Calculator c) {
   15.34 -        c.setDisplay(compute(
   15.35 +        final double newValue = compute(
   15.36              c.getOperation(), 
   15.37              c.getMemory(), 
   15.38              c.getDisplay()
   15.39 -        ));
   15.40 +        );
   15.41 +        c.setDisplay(newValue);
   15.42 +        if (!c.getHistory().contains(newValue)) {
   15.43 +            c.getHistory().add(newValue);
   15.44 +        }
   15.45          c.setMemory(0);
   15.46      }
   15.47      
   15.48 +    @OnFunction
   15.49 +    static void recoverMemory(Calculator c, double data) {
   15.50 +        c.setDisplay(data);
   15.51 +    }
   15.52 +
   15.53 +    @OnFunction
   15.54 +    static void removeMemory(Calculator c, double data) {
   15.55 +        c.getHistory().remove(data);
   15.56 +    }
   15.57 +    
   15.58      private static double compute(String op, double memory, double display) {
   15.59          switch (op) {
   15.60              case "plus": return memory + display;
   15.61 @@ -109,4 +126,9 @@
   15.62          }
   15.63          return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display);
   15.64      }
   15.65 +    
   15.66 +    @ComputedProperty
   15.67 +    static boolean emptyHistory(List<?> history) {
   15.68 +        return history.isEmpty();
   15.69 +    }
   15.70  }
    16.1 --- a/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml	Mon Mar 25 13:33:03 2013 +0100
    16.2 +++ b/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml	Mon Mar 25 18:36:50 2013 +0100
    16.3 @@ -78,6 +78,17 @@
    16.4          </table>
    16.5          <div data-bind="text: displayPreview"></div>
    16.6          
    16.7 +        <h4>Previous Results</h4>
    16.8 +        
    16.9 +        <div data-bind="if: emptyHistory">No results yet.</div>
   16.10 +        <ul data-bind="foreach: history">
   16.11 +            <li>
   16.12 +                <span data-bind="text: $data"/> -
   16.13 +                <a href="#" data-bind="click: $root.recoverMemory">Use</a>
   16.14 +                <a href="#" data-bind="click: $root.removeMemory">Remove</a>
   16.15 +            </li>
   16.16 +        </ul>
   16.17 +        
   16.18          <script src="bck2brwsr.js"></script>
   16.19          <script type="text/javascript">
   16.20              var vm = bck2brwsr('demo.calculator-0.5-SNAPSHOT.jar');
   16.21 @@ -85,75 +96,5 @@
   16.22          </script>
   16.23          
   16.24          <hr/>
   16.25 -    <pre>
   16.26 -    <span class="keyword-directive">package</span> org.apidesign.bck2brwsr.mavenhtml;
   16.27 -
   16.28 -    <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.OnClick;
   16.29 -    <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.Page;
   16.30 -
   16.31 -    <span class="comment">/**</span> <span class="comment">HTML5</span><span class="comment"> &amp; </span><span class="comment">Java</span> <span class="comment">demo</span> <span class="comment">showing</span> <span class="comment">the</span> <span class="comment">power</span> <span class="comment">of</span> <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
   16.32 -    <span class="comment"> * </span><span class="comment">as</span> <span class="comment">well</span> <span class="comment">as</span> <span class="comment">other</span> <span class="comment">goodies</span><span class="comment">, including type-safe association between</span>
   16.33 -    <span class="comment"> * </span><span class="comment">an XHTML page and Java.</span>
   16.34 -    <span class="comment"> * </span>
   16.35 -    <span class="comment"> * </span><span class="ST1">@author</span> <span class="comment">Jaroslav</span> <span class="comment">Tulach</span> <span class="ST0">&lt;jaroslav.tulach@apidesign.org&gt;</span>
   16.36 -     <span class="comment">*/</span>
   16.37 -    @Page(xhtml=<span class="string">&quot;</span><span class="string">Calculator.xhtml</span><span class="string">&quot;</span>)
   16.38 -    <span class="keyword-directive">public</span> <span class="keyword-directive">class</span> App {
   16.39 -        <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> memory;
   16.40 -        <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> String operation;
   16.41 -
   16.42 -        @OnClick(id=<span class="string">&quot;</span><span class="string">clear</span><span class="string">&quot;</span>)
   16.43 -        <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> clear() {
   16.44 -            memory = <span class="number">0</span>;
   16.45 -            operation = <span class="keyword-directive">null</span>;
   16.46 -            Calculator.DISPLAY.setValue(<span class="string">&quot;</span><span class="string">0</span><span class="string">&quot;</span>);
   16.47 -        }
   16.48 -
   16.49 -        @OnClick(id= { <span class="string">&quot;</span><span class="string">plus</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">minus</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">mul</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">div</span><span class="string">&quot;</span> })
   16.50 -        <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> applyOp(String op) {
   16.51 -            memory = getValue();
   16.52 -            operation = op;
   16.53 -            Calculator.DISPLAY.setValue(<span class="string">&quot;</span><span class="string">0</span><span class="string">&quot;</span>);
   16.54 -        }
   16.55 -
   16.56 -        @OnClick(id=<span class="string">&quot;</span><span class="string">result</span><span class="string">&quot;</span>)
   16.57 -        <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> computeTheValue() {
   16.58 -            <span class="keyword-directive">switch</span> (operation) {
   16.59 -                <span class="keyword-directive">case</span> <span class="string">&quot;</span><span class="string">plus</span><span class="string">&quot;</span>: setValue(memory + getValue()); <span class="keyword-directive">break</span>;
   16.60 -                <span class="keyword-directive">case</span> <span class="string">&quot;</span><span class="string">minus</span><span class="string">&quot;</span>: setValue(memory - getValue()); <span class="keyword-directive">break</span>;
   16.61 -                <span class="keyword-directive">case</span> <span class="string">&quot;</span><span class="string">mul</span><span class="string">&quot;</span>: setValue(memory * getValue()); <span class="keyword-directive">break</span>;
   16.62 -                <span class="keyword-directive">case</span> <span class="string">&quot;</span><span class="string">div</span><span class="string">&quot;</span>: setValue(memory / getValue()); <span class="keyword-directive">break</span>;
   16.63 -                <span class="keyword-directive">default</span>: <span class="keyword-directive">throw</span> <span class="keyword-directive">new</span> IllegalStateException(operation);
   16.64 -            }
   16.65 -        }
   16.66 -
   16.67 -        @OnClick(id={<span class="string">&quot;</span><span class="string">n0</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n1</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n2</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n3</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n4</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n5</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n6</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n7</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n8</span><span class="string">&quot;</span>, <span class="string">&quot;</span><span class="string">n9</span><span class="string">&quot;</span>}) 
   16.68 -        <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> addDigit(String digit) {
   16.69 -            digit = digit.substring(<span class="number">1</span>);
   16.70 -            String v = Calculator.DISPLAY.getValue();
   16.71 -            <span class="keyword-directive">if</span> (getValue() == <span class="number">0.0</span>) {
   16.72 -                Calculator.DISPLAY.setValue(digit);
   16.73 -            } <span class="keyword-directive">else</span> {
   16.74 -                Calculator.DISPLAY.setValue(v + digit);
   16.75 -            }
   16.76 -        }
   16.77 -
   16.78 -        <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> setValue(<span class="keyword-directive">double</span> v) {
   16.79 -            StringBuilder sb = <span class="keyword-directive">new</span> StringBuilder();
   16.80 -            sb.append(v);
   16.81 -            Calculator.DISPLAY.setValue(sb.toString());
   16.82 -        }
   16.83 -
   16.84 -        <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> getValue() {
   16.85 -            <span class="keyword-directive">try</span> {
   16.86 -                <span class="keyword-directive">return</span> Double.parseDouble(Calculator.DISPLAY.getValue());
   16.87 -            } <span class="keyword-directive">catch</span> (NumberFormatException ex) {
   16.88 -                Calculator.DISPLAY.setValue(<span class="string">&quot;</span><span class="string">err</span><span class="string">&quot;</span>);
   16.89 -                <span class="keyword-directive">return</span> <span class="number">0.0</span>;
   16.90 -            }
   16.91 -        }
   16.92 -    }
   16.93 -
   16.94 -    </pre>
   16.95      </body>
   16.96  </html>
    17.1 --- a/javaquery/demo-calculator/pom.xml	Mon Mar 25 13:33:03 2013 +0100
    17.2 +++ b/javaquery/demo-calculator/pom.xml	Mon Mar 25 18:36:50 2013 +0100
    17.3 @@ -12,6 +12,7 @@
    17.4  
    17.5    <properties>
    17.6      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    17.7 +    <bck2brwsr.obfuscationlevel>FULL</bck2brwsr.obfuscationlevel>
    17.8    </properties>
    17.9    <build>
   17.10        <plugins>
   17.11 @@ -31,7 +32,7 @@
   17.12                      <directory>${project.build.directory}/${project.build.finalName}-bck2brwsr/public_html/</directory>
   17.13                      <startpage>index.xhtml</startpage>
   17.14                      <javascript>${project.build.directory}/bck2brwsr.js</javascript>
   17.15 -                    <obfuscation>FULL</obfuscation>
   17.16 +                    <obfuscation>${bck2brwsr.obfuscationlevel}</obfuscation>
   17.17                  </configuration>
   17.18              </plugin>
   17.19           <plugin>
    18.1 --- a/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java	Mon Mar 25 13:33:03 2013 +0100
    18.2 +++ b/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java	Mon Mar 25 18:36:50 2013 +0100
    18.3 @@ -17,9 +17,11 @@
    18.4   */
    18.5  package org.apidesign.bck2brwsr.demo.calc.staticcompilation;
    18.6  
    18.7 +import java.util.List;
    18.8  import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
    18.9  import org.apidesign.bck2brwsr.htmlpage.api.On;
   18.10  import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;
   18.11 +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
   18.12  import org.apidesign.bck2brwsr.htmlpage.api.Page;
   18.13  import org.apidesign.bck2brwsr.htmlpage.api.Property;
   18.14  
   18.15 @@ -33,11 +35,12 @@
   18.16      @Property(name = "memory", type = double.class),
   18.17      @Property(name = "display", type = double.class),
   18.18      @Property(name = "operation", type = String.class),
   18.19 -    @Property(name = "hover", type = boolean.class)
   18.20 +    @Property(name = "hover", type = boolean.class),
   18.21 +    @Property(name = "history", type = double.class, array = true)
   18.22  })
   18.23  public class Calc {
   18.24      static {
   18.25 -        new Calculator().applyBindings();
   18.26 +        new Calculator().applyBindings().setOperation("plus");
   18.27      }
   18.28      
   18.29      @On(event = CLICK, id="clear")
   18.30 @@ -65,14 +68,28 @@
   18.31      
   18.32      @On(event = CLICK, id="result")
   18.33      static void computeTheValue(Calculator c) {
   18.34 -        c.setDisplay(compute(
   18.35 +        final double newValue = compute(
   18.36              c.getOperation(), 
   18.37              c.getMemory(), 
   18.38              c.getDisplay()
   18.39 -        ));
   18.40 +        );
   18.41 +        c.setDisplay(newValue);
   18.42 +        if (!c.getHistory().contains(newValue)) {
   18.43 +            c.getHistory().add(newValue);
   18.44 +        }
   18.45          c.setMemory(0);
   18.46      }
   18.47      
   18.48 +    @OnFunction
   18.49 +    static void recoverMemory(Calculator c, double data) {
   18.50 +        c.setDisplay(data);
   18.51 +    }
   18.52 +
   18.53 +    @OnFunction
   18.54 +    static void removeMemory(Calculator c, double data) {
   18.55 +        c.getHistory().remove(data);
   18.56 +    }
   18.57 +    
   18.58      private static double compute(String op, double memory, double display) {
   18.59          switch (op) {
   18.60              case "plus": return memory + display;
   18.61 @@ -109,4 +126,9 @@
   18.62          }
   18.63          return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display);
   18.64      }
   18.65 +    
   18.66 +    @ComputedProperty
   18.67 +    static boolean emptyHistory(List<?> history) {
   18.68 +        return history.isEmpty();
   18.69 +    }
   18.70  }
    19.1 --- a/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml	Mon Mar 25 13:33:03 2013 +0100
    19.2 +++ b/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml	Mon Mar 25 18:36:50 2013 +0100
    19.3 @@ -76,6 +76,18 @@
    19.4                  </tr>
    19.5              </tbody>
    19.6          </table>
    19.7 +        
    19.8 +        <h4>Previous Results</h4>
    19.9 +        
   19.10 +        <div data-bind="if: emptyHistory">No results yet.</div>
   19.11 +        <ul data-bind="foreach: history">
   19.12 +            <li>
   19.13 +                <span data-bind="text: $data"/> -
   19.14 +                <a href="#" data-bind="click: $root.recoverMemory">Use</a>
   19.15 +                <a href="#" data-bind="click: $root.removeMemory">Remove</a>
   19.16 +            </li>
   19.17 +        </ul>
   19.18 +        
   19.19          <div data-bind="text: displayPreview"></div>
   19.20          <script src="bck2brwsr.js"/>
   19.21          <script>
    20.1 --- a/rt/emul/mini/src/main/java/java/lang/Class.java	Mon Mar 25 13:33:03 2013 +0100
    20.2 +++ b/rt/emul/mini/src/main/java/java/lang/Class.java	Mon Mar 25 18:36:50 2013 +0100
    20.3 @@ -402,8 +402,15 @@
    20.4              }
    20.5              return cmpType != null && getComponentType().isAssignableFrom(cmpType);
    20.6          }
    20.7 -        String prop = "$instOf_" + getName().replace('.', '_');
    20.8 -        return hasCnstrProperty(cls, prop);
    20.9 +        if (isPrimitive()) {
   20.10 +            return false;
   20.11 +        } else {
   20.12 +            if (cls.isPrimitive()) {
   20.13 +                return false;
   20.14 +            }
   20.15 +            String prop = "$instOf_" + getName().replace('.', '_');
   20.16 +            return hasCnstrProperty(cls, prop);
   20.17 +        }
   20.18      }
   20.19  
   20.20      @JavaScriptBody(args = { "who", "prop" }, body = 
    21.1 --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java	Mon Mar 25 13:33:03 2013 +0100
    21.2 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java	Mon Mar 25 18:36:50 2013 +0100
    21.3 @@ -317,9 +317,13 @@
    21.4                  int cnt = is.read() - '0';
    21.5                  if (cnt == 'U' - '0') {
    21.6                      os.write(baseURL.getBytes("UTF-8"));
    21.7 -                }
    21.8 -                if (cnt >= 0 && cnt < params.length) {
    21.9 -                    os.write(params[cnt].getBytes("UTF-8"));
   21.10 +                } else {
   21.11 +                    if (cnt >= 0 && cnt < params.length) {
   21.12 +                        os.write(params[cnt].getBytes("UTF-8"));
   21.13 +                    } else {
   21.14 +                        os.write('$');
   21.15 +                        os.write(cnt + '0');
   21.16 +                    }
   21.17                  }
   21.18              } else {
   21.19                  os.write(ch);
    22.1 --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java	Mon Mar 25 13:33:03 2013 +0100
    22.2 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java	Mon Mar 25 18:36:50 2013 +0100
    22.3 @@ -53,6 +53,22 @@
    22.4          return Runnable.class.isInterface();
    22.5      }
    22.6  
    22.7 +    @Compare public boolean isAssignableToPrimitiveType() {
    22.8 +        return boolean.class.isAssignableFrom(Runnable.class);
    22.9 +    }
   22.10 +
   22.11 +    @Compare public boolean isAssignableFromPrimitiveType() {
   22.12 +        return Runnable.class.isAssignableFrom(boolean.class);
   22.13 +    }
   22.14 +
   22.15 +    @Compare public boolean isAssignableLongFromInt() {
   22.16 +        return long.class.isAssignableFrom(int.class);
   22.17 +    }
   22.18 +
   22.19 +    @Compare public boolean isAssignableIntFromLong() {
   22.20 +        return int.class.isAssignableFrom(long.class);
   22.21 +    }
   22.22 +
   22.23      @Compare public String isRunnableHasRunMethod() throws NoSuchMethodException {
   22.24          return Runnable.class.getMethod("run").getName();
   22.25      }