Is model branch in good shape for merging? I think so.
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 08 Apr 2013 19:33:08 +0200
changeset 958c75bd6823179
parent 922 2fb3e929962f
parent 957 022f62873be6
child 965 5c7cdd2b3f8f
Is model branch in good shape for merging? I think so.
rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java
     1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java	Wed Apr 03 13:43:22 2013 +0200
     1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java	Mon Apr 08 19:33:08 2013 +0200
     1.3 @@ -50,6 +50,22 @@
     1.4          throw new IllegalStateException("Value " + ret + " is not of type " + modelClass);
     1.5      }
     1.6      
     1.7 +    public static String toJSON(Object value) {
     1.8 +        if (value == null) {
     1.9 +            return "null";
    1.10 +        }
    1.11 +        if (value instanceof String) {
    1.12 +            return '"' + 
    1.13 +                ((String)value).
    1.14 +                    replace("\"", "\\\"").
    1.15 +                    replace("\n", "\\n").
    1.16 +                    replace("\r", "\\r").
    1.17 +                    replace("\t", "\\t")
    1.18 +                + '"';
    1.19 +        }
    1.20 +        return value.toString();
    1.21 +    }
    1.22 +    
    1.23      @JavaScriptBody(args = { "object", "property" },
    1.24          body = "if (property === null) return object;\n"
    1.25          + "var p = object[property]; return p ? p : null;"
    1.26 @@ -57,4 +73,78 @@
    1.27      private static Object getProperty(Object object, String property) {
    1.28          return null;
    1.29      }
    1.30 +    
    1.31 +    public static String createJSONP(Object[] jsonResult, Runnable whenDone) {
    1.32 +        int h = whenDone.hashCode();
    1.33 +        String name;
    1.34 +        for (;;) {
    1.35 +            name = "jsonp" + Integer.toHexString(h);
    1.36 +            if (defineIfUnused(name, jsonResult, whenDone)) {
    1.37 +                return name;
    1.38 +            }
    1.39 +            h++;
    1.40 +        }
    1.41 +    }
    1.42 +
    1.43 +    @JavaScriptBody(args = { "name", "arr", "run" }, body = 
    1.44 +        "if (window[name]) return false;\n "
    1.45 +      + "window[name] = function(data) {\n "
    1.46 +      + "  arr[0] = data;\n"
    1.47 +      + "  run.run__V();\n"
    1.48 +      + "  delete window[name];\n"
    1.49 +      + "};"
    1.50 +      + "return true;\n"
    1.51 +    )
    1.52 +    private static boolean defineIfUnused(String name, Object[] arr, Runnable run) {
    1.53 +        return true;
    1.54 +    }
    1.55 +    
    1.56 +    @JavaScriptBody(args = { "url", "arr", "callback" }, body = ""
    1.57 +        + "var request = new XMLHttpRequest();\n"
    1.58 +        + "request.open('GET', url, true);\n"
    1.59 +        + "request.setRequestHeader('Content-Type', 'application/json; charset=utf-8');\n"
    1.60 +        + "request.onreadystatechange = function() {\n"
    1.61 +        + "  if (this.readyState!==4) return;\n"
    1.62 +        + "  try {\n"
    1.63 +        + "    arr[0] = eval('(' + this.response + ')');\n"
    1.64 +        + "  } catch (error) {;\n"
    1.65 +        + "    throw 'Cannot parse' + error + ':' + this.response;\n"
    1.66 +        + "  };\n"
    1.67 +        + "  callback.run__V();\n"
    1.68 +        + "};"
    1.69 +        + "request.send();"
    1.70 +    )
    1.71 +    private static void loadJSON(
    1.72 +        String url, Object[] jsonResult, Runnable whenDone
    1.73 +    ) {
    1.74 +    }
    1.75 +    
    1.76 +    public static void loadJSON(
    1.77 +        String url, Object[] jsonResult, Runnable whenDone, String jsonp
    1.78 +    ) {
    1.79 +        if (jsonp == null) {
    1.80 +            loadJSON(url, jsonResult, whenDone);
    1.81 +        } else {
    1.82 +            loadJSONP(url, jsonp);
    1.83 +        }
    1.84 +    }
    1.85 +    
    1.86 +    @JavaScriptBody(args = { "url", "jsonp" }, body = 
    1.87 +        "var scrpt = window.document.createElement('script');\n "
    1.88 +        + "scrpt.setAttribute('src', url);\n "
    1.89 +        + "scrpt.setAttribute('id', jsonp);\n "
    1.90 +        + "scrpt.setAttribute('type', 'text/javascript');\n "
    1.91 +        + "var body = document.getElementsByTagName('body')[0];\n "
    1.92 +        + "body.appendChild(scrpt);\n"
    1.93 +    )
    1.94 +    private static void loadJSONP(String url, String jsonp) {
    1.95 +        
    1.96 +    }
    1.97 +    
    1.98 +    public static void extractJSON(Object jsonObject, String[] props, Object[] values) {
    1.99 +        for (int i = 0; i < props.length; i++) {
   1.100 +            values[i] = getProperty(jsonObject, props[i]);
   1.101 +        }
   1.102 +    }
   1.103 +    
   1.104  }
     2.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/KOList.java	Wed Apr 03 13:43:22 2013 +0200
     2.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/KOList.java	Mon Apr 08 19:33:08 2013 +0200
     2.3 @@ -19,6 +19,7 @@
     2.4  
     2.5  import java.util.ArrayList;
     2.6  import java.util.Collection;
     2.7 +import java.util.Iterator;
     2.8  import org.apidesign.bck2brwsr.core.JavaScriptOnly;
     2.9  
    2.10  /**
    2.11 @@ -29,6 +30,7 @@
    2.12      private final String name;
    2.13      private final String[] deps;
    2.14      private Knockout model;
    2.15 +    private Runnable onchange;
    2.16  
    2.17      public KOList(String name, String... deps) {
    2.18          this.name = name;
    2.19 @@ -36,7 +38,18 @@
    2.20      }
    2.21      
    2.22      public void assign(Knockout model) {
    2.23 -        this.model = model;
    2.24 +        if (this.model != model) {
    2.25 +            this.model = model;
    2.26 +            notifyChange();
    2.27 +        }
    2.28 +    }
    2.29 +    
    2.30 +    public KOList<T> onChange(Runnable r) {
    2.31 +        if (this.onchange != null) {
    2.32 +            throw new IllegalStateException();
    2.33 +        }
    2.34 +        this.onchange = r;
    2.35 +        return this;
    2.36      }
    2.37  
    2.38      @Override
    2.39 @@ -47,6 +60,20 @@
    2.40      }
    2.41  
    2.42      @Override
    2.43 +    public boolean addAll(Collection<? extends T> c) {
    2.44 +        boolean ret = super.addAll(c);
    2.45 +        notifyChange();
    2.46 +        return ret;
    2.47 +    }
    2.48 +
    2.49 +    @Override
    2.50 +    public boolean addAll(int index, Collection<? extends T> c) {
    2.51 +        boolean ret = super.addAll(index, c);
    2.52 +        notifyChange();
    2.53 +        return ret;
    2.54 +    }
    2.55 +
    2.56 +    @Override
    2.57      public boolean remove(Object o) {
    2.58          boolean ret = super.remove(o);
    2.59          notifyChange();
    2.60 @@ -92,7 +119,25 @@
    2.61          notifyChange();
    2.62          return ret;
    2.63      }
    2.64 -    
    2.65 +
    2.66 +    @Override
    2.67 +    public String toString() {
    2.68 +        Iterator<T> it = iterator();
    2.69 +        if (!it.hasNext()) {
    2.70 +            return "[]";
    2.71 +        }
    2.72 +        String sep = "";
    2.73 +        StringBuilder sb = new StringBuilder();
    2.74 +        sb.append('[');
    2.75 +        while (it.hasNext()) {
    2.76 +            T t = it.next();
    2.77 +            sb.append(sep);
    2.78 +            sb.append(ConvertTypes.toJSON(t));
    2.79 +            sep = ",";
    2.80 +        }
    2.81 +        sb.append(']');
    2.82 +        return sb.toString();
    2.83 +    }
    2.84      
    2.85      
    2.86      @JavaScriptOnly(name = "koArray", value = "function() { return this.toArray___3Ljava_lang_Object_2(); }")
    2.87 @@ -100,14 +145,23 @@
    2.88  
    2.89      private void notifyChange() {
    2.90          Knockout m = model;
    2.91 -        if (m == null) {
    2.92 -            return;
    2.93 +        if (m != null) {
    2.94 +            m.valueHasMutated(name);
    2.95 +            for (String dependant : deps) {
    2.96 +                m.valueHasMutated(dependant);
    2.97 +            }
    2.98          }
    2.99 -        m.valueHasMutated(name);
   2.100 -        for (String dependant : deps) {
   2.101 -            m.valueHasMutated(dependant);
   2.102 +        Runnable r = onchange;
   2.103 +        if (r != null) {
   2.104 +            r.run();
   2.105          }
   2.106      }
   2.107 -    
   2.108 +
   2.109 +    @Override
   2.110 +    public KOList clone() {
   2.111 +        KOList ko = (KOList)super.clone();
   2.112 +        ko.model = null;
   2.113 +        return ko;
   2.114 +    }
   2.115      
   2.116  }
     3.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Wed Apr 03 13:43:22 2013 +0200
     3.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Mon Apr 08 19:33:08 2013 +0200
     3.3 @@ -26,6 +26,7 @@
     3.4  import java.util.Collection;
     3.5  import java.util.Collections;
     3.6  import java.util.HashMap;
     3.7 +import java.util.HashSet;
     3.8  import java.util.LinkedHashSet;
     3.9  import java.util.List;
    3.10  import java.util.Map;
    3.11 @@ -34,6 +35,7 @@
    3.12  import javax.annotation.processing.AbstractProcessor;
    3.13  import javax.annotation.processing.Completion;
    3.14  import javax.annotation.processing.Completions;
    3.15 +import javax.annotation.processing.Messager;
    3.16  import javax.annotation.processing.Processor;
    3.17  import javax.annotation.processing.RoundEnvironment;
    3.18  import javax.annotation.processing.SupportedAnnotationTypes;
    3.19 @@ -45,9 +47,11 @@
    3.20  import javax.lang.model.element.PackageElement;
    3.21  import javax.lang.model.element.TypeElement;
    3.22  import javax.lang.model.element.VariableElement;
    3.23 +import javax.lang.model.type.ArrayType;
    3.24  import javax.lang.model.type.MirroredTypeException;
    3.25  import javax.lang.model.type.TypeKind;
    3.26  import javax.lang.model.type.TypeMirror;
    3.27 +import javax.lang.model.util.Elements;
    3.28  import javax.lang.model.util.Types;
    3.29  import javax.tools.Diagnostic;
    3.30  import javax.tools.FileObject;
    3.31 @@ -56,6 +60,8 @@
    3.32  import org.apidesign.bck2brwsr.htmlpage.api.Model;
    3.33  import org.apidesign.bck2brwsr.htmlpage.api.On;
    3.34  import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
    3.35 +import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange;
    3.36 +import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
    3.37  import org.apidesign.bck2brwsr.htmlpage.api.Page;
    3.38  import org.apidesign.bck2brwsr.htmlpage.api.Property;
    3.39  import org.openide.util.lookup.ServiceProvider;
    3.40 @@ -70,6 +76,8 @@
    3.41      "org.apidesign.bck2brwsr.htmlpage.api.Model",
    3.42      "org.apidesign.bck2brwsr.htmlpage.api.Page",
    3.43      "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
    3.44 +    "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
    3.45 +    "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange",
    3.46      "org.apidesign.bck2brwsr.htmlpage.api.On"
    3.47  })
    3.48  public final class PageProcessor extends AbstractProcessor {
    3.49 @@ -102,6 +110,10 @@
    3.50              return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
    3.51          }
    3.52      }
    3.53 +
    3.54 +    private  Messager err() {
    3.55 +        return processingEnv.getMessager();
    3.56 +    }
    3.57      
    3.58      private boolean processModel(Element e) {
    3.59          boolean ok = true;
    3.60 @@ -112,15 +124,20 @@
    3.61          String pkg = findPkgName(e);
    3.62          Writer w;
    3.63          String className = m.className();
    3.64 +        models.put(e, className);
    3.65          try {
    3.66              StringWriter body = new StringWriter();
    3.67              List<String> propsGetSet = new ArrayList<>();
    3.68              List<String> functions = new ArrayList<>();
    3.69              Map<String, Collection<String>> propsDeps = new HashMap<>();
    3.70 +            Map<String, Collection<String>> functionDeps = new HashMap<>();
    3.71              if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
    3.72                  ok = false;
    3.73              }
    3.74 -            if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps)) {
    3.75 +            if (!generateOnChange(e, propsDeps, m.properties(), className, functionDeps)) {
    3.76 +                ok = false;
    3.77 +            }
    3.78 +            if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps, functionDeps)) {
    3.79                  ok = false;
    3.80              }
    3.81              if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
    3.82 @@ -133,25 +150,94 @@
    3.83                  w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
    3.84                  w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
    3.85                  w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
    3.86 -                w.append("final class ").append(className).append(" {\n");
    3.87 -                w.append("  private Object json;\n");
    3.88 +                w.append("final class ").append(className).append(" implements Cloneable {\n");
    3.89                  w.append("  private boolean locked;\n");
    3.90                  w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
    3.91                  w.append(body.toString());
    3.92 -                w.append("  private static Class<" + e.getSimpleName() + "> modelFor() { return null; }\n");
    3.93 +                w.append("  private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
    3.94                  w.append("  public ").append(className).append("() {\n");
    3.95 +                w.append("    intKnckt();\n");
    3.96 +                w.append("  };\n");
    3.97 +                w.append("  private void intKnckt() {\n");
    3.98                  w.append("    ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
    3.99                  writeStringArray(propsGetSet, w);
   3.100                  w.append(", ");
   3.101                  writeStringArray(functions, w);
   3.102                  w.append("    );\n");
   3.103                  w.append("  };\n");
   3.104 +                w.append("  ").append(className).append("(Object json) {\n");
   3.105 +                int values = 0;
   3.106 +                for (int i = 0; i < propsGetSet.size(); i += 4) {
   3.107 +                    Property p = findProperty(m.properties(), propsGetSet.get(i));
   3.108 +                    if (p == null) {
   3.109 +                        continue;
   3.110 +                    }
   3.111 +                    values++;
   3.112 +                }
   3.113 +                w.append("    Object[] ret = new Object[" + values + "];\n");
   3.114 +                w.append("    org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
   3.115 +                for (int i = 0; i < propsGetSet.size(); i += 4) {
   3.116 +                    Property p = findProperty(m.properties(), propsGetSet.get(i));
   3.117 +                    if (p == null) {
   3.118 +                        continue;
   3.119 +                    }
   3.120 +                    w.append("      \"").append(propsGetSet.get(i)).append("\",\n");
   3.121 +                }
   3.122 +                w.append("    }, ret);\n");
   3.123 +                for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
   3.124 +                    final String pn = propsGetSet.get(i);
   3.125 +                    Property p = findProperty(m.properties(), pn);
   3.126 +                    if (p == null) {
   3.127 +                        continue;
   3.128 +                    }
   3.129 +                    boolean[] isModel = { false };
   3.130 +                    boolean[] isEnum = { false };
   3.131 +                    String type = checkType(m.properties()[prop++], isModel, isEnum);
   3.132 +                    if (isEnum[0]) {
   3.133 +//                        w.append(type).append(".valueOf((String)");
   3.134 +//                        close = true;
   3.135 +                        w.append("    this.prop_").append(pn);
   3.136 +                        w.append(" = null;\n");
   3.137 +                    } else if (p.array()) {
   3.138 +                        w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
   3.139 +                        w.append("  for (Object e : ((Object[])ret[" + cnt + "])) {\n");
   3.140 +                        if (isModel[0]) {
   3.141 +                            w.append("    this.prop_").append(pn).append(".add(new ");
   3.142 +                            w.append(type).append("(e));\n");
   3.143 +                        } else {
   3.144 +                            if (isPrimitive(type)) {
   3.145 +                                w.append("    this.prop_").append(pn).append(".add(((Number)e).");
   3.146 +                                w.append(type).append("Value());\n");
   3.147 +                            } else {
   3.148 +                                w.append("    this.prop_").append(pn).append(".add((");
   3.149 +                                w.append(type).append(")e);\n");
   3.150 +                            }
   3.151 +                        }
   3.152 +                        w.append("  }\n");
   3.153 +                        w.append("}\n");
   3.154 +                    } else {
   3.155 +                        if (isPrimitive(type)) {
   3.156 +                            w.append("    this.prop_").append(pn);
   3.157 +                            w.append(" = ((Number)").append("ret[" + cnt + "]).");
   3.158 +                            w.append(type).append("Value();\n");
   3.159 +                        } else {
   3.160 +                            w.append("    this.prop_").append(pn);
   3.161 +                            w.append(" = (").append(type).append(')');
   3.162 +                            w.append("ret[" + cnt + "];\n");
   3.163 +                        }
   3.164 +                    }
   3.165 +                    cnt++;
   3.166 +                }
   3.167 +                w.append("    intKnckt();\n");
   3.168 +                w.append("  };\n");
   3.169 +                writeToString(m.properties(), w);
   3.170 +                writeClone(className, m.properties(), w);
   3.171                  w.append("}\n");
   3.172              } finally {
   3.173                  w.close();
   3.174              }
   3.175          } catch (IOException ex) {
   3.176 -            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   3.177 +            err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   3.178              return false;
   3.179          }
   3.180          return ok;
   3.181 @@ -170,7 +256,7 @@
   3.182              pp = ProcessPage.readPage(is);
   3.183              is.close();
   3.184          } catch (IOException iOException) {
   3.185 -            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
   3.186 +            err().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
   3.187              ok = false;
   3.188              pp = null;
   3.189          }
   3.190 @@ -185,15 +271,22 @@
   3.191              List<String> propsGetSet = new ArrayList<>();
   3.192              List<String> functions = new ArrayList<>();
   3.193              Map<String, Collection<String>> propsDeps = new HashMap<>();
   3.194 +            Map<String, Collection<String>> functionDeps = new HashMap<>();
   3.195              if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   3.196                  ok = false;
   3.197              }
   3.198 -            if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps)) {
   3.199 +            if (!generateOnChange(e, propsDeps, p.properties(), className, functionDeps)) {
   3.200 +                ok = false;
   3.201 +            }
   3.202 +            if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps, functionDeps)) {
   3.203                  ok = false;
   3.204              }
   3.205              if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   3.206                  ok = false;
   3.207              }
   3.208 +            if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) {
   3.209 +                ok = false;
   3.210 +            }
   3.211              
   3.212              FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   3.213              w = new OutputStreamWriter(java.openOutputStream());
   3.214 @@ -206,7 +299,7 @@
   3.215                  if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
   3.216                      ok = false;
   3.217                  } else {
   3.218 -                    for (String id : pp.ids()) {
   3.219 +                    if (pp != null) for (String id : pp.ids()) {
   3.220                          String tag = pp.tagNameForId(id);
   3.221                          String type = type(tag);
   3.222                          w.append("  ").append("public final ").
   3.223 @@ -234,7 +327,7 @@
   3.224                  w.close();
   3.225              }
   3.226          } catch (IOException ex) {
   3.227 -            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   3.228 +            err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   3.229              return false;
   3.230          }
   3.231          return ok;
   3.232 @@ -280,24 +373,24 @@
   3.233                  if (oc != null) {
   3.234                      for (String id : oc.id()) {
   3.235                          if (pp == null) {
   3.236 -                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   3.237 +                            err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   3.238                              ok = false;
   3.239                              continue;
   3.240                          }
   3.241                          if (pp.tagNameForId(id) == null) {
   3.242 -                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   3.243 +                            err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   3.244                              ok = false;
   3.245                              continue;
   3.246                          }
   3.247                          ExecutableElement ee = (ExecutableElement)method;
   3.248                          CharSequence params = wrapParams(ee, id, className, "ev", null);
   3.249                          if (!ee.getModifiers().contains(Modifier.STATIC)) {
   3.250 -                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   3.251 +                            err().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   3.252                              ok = false;
   3.253                              continue;
   3.254                          }
   3.255                          if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   3.256 -                            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   3.257 +                            err().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   3.258                              ok = false;
   3.259                              continue;
   3.260                          }
   3.261 @@ -378,7 +471,9 @@
   3.262      private boolean generateProperties(
   3.263          Element where,
   3.264          Writer w, Property[] properties,
   3.265 -        Collection<String> props, Map<String,Collection<String>> deps
   3.266 +        Collection<String> props, 
   3.267 +        Map<String,Collection<String>> deps,
   3.268 +        Map<String,Collection<String>> functionDeps
   3.269      ) throws IOException {
   3.270          boolean ok = true;
   3.271          for (Property p : properties) {
   3.272 @@ -389,7 +484,7 @@
   3.273              if (p.array()) {
   3.274                  w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
   3.275                      + p.name() + "\"");
   3.276 -                final Collection<String> dependants = deps.get(p.name());
   3.277 +                Collection<String> dependants = deps.get(p.name());
   3.278                  if (dependants != null) {
   3.279                      for (String depProp : dependants) {
   3.280                          w.write(", ");
   3.281 @@ -398,7 +493,18 @@
   3.282                          w.write('\"');
   3.283                      }
   3.284                  }
   3.285 -                w.write(");\n");
   3.286 +                w.write(")");
   3.287 +                
   3.288 +                dependants = functionDeps.get(p.name());
   3.289 +                if (dependants != null) {
   3.290 +                    w.write(".onChange(new Runnable() { public void run() {\n");
   3.291 +                    for (String call : dependants) {
   3.292 +                        w.append(call);
   3.293 +                    }
   3.294 +                    w.write("}})");
   3.295 +                }
   3.296 +                w.write(";\n");
   3.297 +                
   3.298                  w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   3.299                  w.write("  if (locked) throw new IllegalStateException();\n");
   3.300                  w.write("  prop_" + p.name() + ".assign(ko);\n");
   3.301 @@ -415,13 +521,19 @@
   3.302                  w.write("  prop_" + p.name() + " = v;\n");
   3.303                  w.write("  if (ko != null) {\n");
   3.304                  w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   3.305 -                final Collection<String> dependants = deps.get(p.name());
   3.306 +                Collection<String> dependants = deps.get(p.name());
   3.307                  if (dependants != null) {
   3.308                      for (String depProp : dependants) {
   3.309                          w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   3.310                      }
   3.311                  }
   3.312                  w.write("  }\n");
   3.313 +                dependants = functionDeps.get(p.name());
   3.314 +                if (dependants != null) {
   3.315 +                    for (String call : dependants) {
   3.316 +                        w.append(call);
   3.317 +                    }
   3.318 +                }
   3.319                  w.write("}\n");
   3.320              }
   3.321              
   3.322 @@ -450,7 +562,7 @@
   3.323              final TypeMirror rt = ee.getReturnType();
   3.324              final Types tu = processingEnv.getTypeUtils();
   3.325              TypeMirror ert = tu.erasure(rt);
   3.326 -            String tn = ert.toString();
   3.327 +            String tn = fqn(ert, ee);
   3.328              boolean array = false;
   3.329              if (tn.equals("java.util.List")) {
   3.330                  array = true;
   3.331 @@ -469,7 +581,7 @@
   3.332                      ok = false;
   3.333                  }
   3.334                  
   3.335 -                final String dt = pe.asType().toString();
   3.336 +                final String dt = fqn(pe.asType(), ee);
   3.337                  String[] call = toGetSet(dn, dt, false);
   3.338                  w.write("  " + dt + " arg" + (++arg) + " = ");
   3.339                  w.write(call[0] + "();\n");
   3.340 @@ -483,7 +595,7 @@
   3.341              }
   3.342              w.write("  try {\n");
   3.343              w.write("    locked = true;\n");
   3.344 -            w.write("    return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "(");
   3.345 +            w.write("    return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
   3.346              String sep = "";
   3.347              for (int i = 1; i <= arg; i++) {
   3.348                  w.write(sep);
   3.349 @@ -538,35 +650,19 @@
   3.350  
   3.351      private String typeName(Element where, Property p) {
   3.352          String ret;
   3.353 -        boolean isModel = false;
   3.354 -        boolean isEnum = false;
   3.355 -        try {
   3.356 -            ret = p.type().getName();
   3.357 -        } catch (MirroredTypeException ex) {
   3.358 -            TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror());
   3.359 -            final Element e = processingEnv.getTypeUtils().asElement(tm);
   3.360 -            final Model m = e == null ? null : e.getAnnotation(Model.class);
   3.361 -            if (m != null) {
   3.362 -                ret = findPkgName(e) + '.' + m.className();
   3.363 -                isModel = true;
   3.364 -                models.put(e, m.className());
   3.365 -            } else {
   3.366 -                ret = tm.toString();
   3.367 -            }
   3.368 -            TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
   3.369 -            enm = processingEnv.getTypeUtils().erasure(enm);
   3.370 -            isEnum = processingEnv.getTypeUtils().isSubtype(tm, enm);
   3.371 -        }
   3.372 +        boolean[] isModel = { false };
   3.373 +        boolean[] isEnum = { false };
   3.374 +        ret = checkType(p, isModel, isEnum);
   3.375          if (p.array()) {
   3.376              String bt = findBoxedType(ret);
   3.377              if (bt != null) {
   3.378                  return bt;
   3.379              }
   3.380          }
   3.381 -        if (!isModel && !"java.lang.String".equals(ret) && !isEnum) {
   3.382 +        if (!isModel[0] && !"java.lang.String".equals(ret) && !isEnum[0]) {
   3.383              String bt = findBoxedType(ret);
   3.384              if (bt == null) {
   3.385 -                processingEnv.getMessager().printMessage(
   3.386 +                err().printMessage(
   3.387                      Diagnostic.Kind.ERROR, 
   3.388                      "Only primitive types supported in the mapping. Not " + ret,
   3.389                      where
   3.390 @@ -617,7 +713,7 @@
   3.391              sb.append('"');
   3.392              sep = ", ";
   3.393          }
   3.394 -        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
   3.395 +        err().printMessage(Diagnostic.Kind.ERROR,
   3.396              propName + " is not one of known properties: " + sb
   3.397              , e
   3.398          );
   3.399 @@ -647,25 +743,25 @@
   3.400                  continue;
   3.401              }
   3.402              if (!e.getModifiers().contains(Modifier.STATIC)) {
   3.403 -                processingEnv.getMessager().printMessage(
   3.404 +                err().printMessage(
   3.405                      Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e
   3.406                  );
   3.407                  return false;
   3.408              }
   3.409              if (e.getModifiers().contains(Modifier.PRIVATE)) {
   3.410 -                processingEnv.getMessager().printMessage(
   3.411 +                err().printMessage(
   3.412                      Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e
   3.413                  );
   3.414                  return false;
   3.415              }
   3.416              if (e.getReturnType().getKind() != TypeKind.VOID) {
   3.417 -                processingEnv.getMessager().printMessage(
   3.418 +                err().printMessage(
   3.419                      Diagnostic.Kind.ERROR, "@OnFunction method should return void", e
   3.420                  );
   3.421                  return false;
   3.422              }
   3.423              String n = e.getSimpleName().toString();
   3.424 -            body.append("void ").append(n).append("(Object data, Object ev) {\n");
   3.425 +            body.append("private void ").append(n).append("(Object data, Object ev) {\n");
   3.426              body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   3.427              body.append(wrapParams(e, null, className, "ev", "data"));
   3.428              body.append(");\n");
   3.429 @@ -677,6 +773,205 @@
   3.430          return true;
   3.431      }
   3.432  
   3.433 +    private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
   3.434 +        Property[] properties, String className, 
   3.435 +        Map<String, Collection<String>> functionDeps
   3.436 +    ) {
   3.437 +        for (Element m : clazz.getEnclosedElements()) {
   3.438 +            if (m.getKind() != ElementKind.METHOD) {
   3.439 +                continue;
   3.440 +            }
   3.441 +            ExecutableElement e = (ExecutableElement) m;
   3.442 +            OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
   3.443 +            if (onPC == null) {
   3.444 +                continue;
   3.445 +            }
   3.446 +            for (String pn : onPC.value()) {
   3.447 +                if (findProperty(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   3.448 +                    err().printMessage(Diagnostic.Kind.ERROR, "No property named '" + pn + "' in the model");
   3.449 +                    return false;
   3.450 +                }
   3.451 +            }
   3.452 +            if (!e.getModifiers().contains(Modifier.STATIC)) {
   3.453 +                err().printMessage(
   3.454 +                    Diagnostic.Kind.ERROR, "@OnPropertyChange method needs to be static", e);
   3.455 +                return false;
   3.456 +            }
   3.457 +            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   3.458 +                err().printMessage(
   3.459 +                    Diagnostic.Kind.ERROR, "@OnPropertyChange method cannot be private", e);
   3.460 +                return false;
   3.461 +            }
   3.462 +            if (e.getReturnType().getKind() != TypeKind.VOID) {
   3.463 +                err().printMessage(
   3.464 +                    Diagnostic.Kind.ERROR, "@OnPropertyChange method should return void", e);
   3.465 +                return false;
   3.466 +            }
   3.467 +            String n = e.getSimpleName().toString();
   3.468 +            
   3.469 +            
   3.470 +            for (String pn : onPC.value()) {
   3.471 +                StringBuilder call = new StringBuilder();
   3.472 +                call.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   3.473 +                call.append(wrapPropName(e, className, "name", pn));
   3.474 +                call.append(");\n");
   3.475 +                
   3.476 +                Collection<String> change = functionDeps.get(pn);
   3.477 +                if (change == null) {
   3.478 +                    change = new ArrayList<>();
   3.479 +                    functionDeps.put(pn, change);
   3.480 +                }
   3.481 +                change.add(call.toString());
   3.482 +                for (String dpn : findDerivedFrom(propDeps, pn)) {
   3.483 +                    change = functionDeps.get(dpn);
   3.484 +                    if (change == null) {
   3.485 +                        change = new ArrayList<>();
   3.486 +                        functionDeps.put(dpn, change);
   3.487 +                    }
   3.488 +                    change.add(call.toString());
   3.489 +                }
   3.490 +            }
   3.491 +        }
   3.492 +        return true;
   3.493 +    }
   3.494 +    
   3.495 +    private boolean generateReceive(
   3.496 +        Element clazz, StringWriter body, String className, 
   3.497 +        List<? extends Element> enclosedElements, List<String> functions
   3.498 +    ) {
   3.499 +        for (Element m : enclosedElements) {
   3.500 +            if (m.getKind() != ElementKind.METHOD) {
   3.501 +                continue;
   3.502 +            }
   3.503 +            ExecutableElement e = (ExecutableElement)m;
   3.504 +            OnReceive onR = e.getAnnotation(OnReceive.class);
   3.505 +            if (onR == null) {
   3.506 +                continue;
   3.507 +            }
   3.508 +            if (!e.getModifiers().contains(Modifier.STATIC)) {
   3.509 +                err().printMessage(
   3.510 +                    Diagnostic.Kind.ERROR, "@OnReceive method needs to be static", e
   3.511 +                );
   3.512 +                return false;
   3.513 +            }
   3.514 +            if (e.getModifiers().contains(Modifier.PRIVATE)) {
   3.515 +                err().printMessage(
   3.516 +                    Diagnostic.Kind.ERROR, "@OnReceive method cannot be private", e
   3.517 +                );
   3.518 +                return false;
   3.519 +            }
   3.520 +            if (e.getReturnType().getKind() != TypeKind.VOID) {
   3.521 +                err().printMessage(
   3.522 +                    Diagnostic.Kind.ERROR, "@OnReceive method should return void", e
   3.523 +                );
   3.524 +                return false;
   3.525 +            }
   3.526 +            String modelClass = null;
   3.527 +            boolean expectsList = false;
   3.528 +            List<String> args = new ArrayList<>();
   3.529 +            {
   3.530 +                for (VariableElement ve : e.getParameters()) {
   3.531 +                    TypeMirror modelType = null;
   3.532 +                    if (ve.asType().toString().equals(className)) {
   3.533 +                        args.add(className + ".this");
   3.534 +                    } else if (isModel(ve.asType())) {
   3.535 +                        modelType = ve.asType();
   3.536 +                    } else if (ve.asType().getKind() == TypeKind.ARRAY) {
   3.537 +                        modelType = ((ArrayType)ve.asType()).getComponentType();
   3.538 +                        expectsList = true;
   3.539 +                    }
   3.540 +                    if (modelType != null) {
   3.541 +                        if (modelClass != null) {
   3.542 +                            err().printMessage(Diagnostic.Kind.ERROR, "There can be only one model class among arguments", e);
   3.543 +                        } else {
   3.544 +                            modelClass = modelType.toString();
   3.545 +                            if (expectsList) {
   3.546 +                                args.add("arr");
   3.547 +                            } else {
   3.548 +                                args.add("arr[0]");
   3.549 +                            }
   3.550 +                        }
   3.551 +                    }
   3.552 +                }
   3.553 +            }
   3.554 +            if (modelClass == null) {
   3.555 +                err().printMessage(Diagnostic.Kind.ERROR, "The method needs to have one @Model class as parameter", e);
   3.556 +            }
   3.557 +            String n = e.getSimpleName().toString();
   3.558 +            body.append("public void ").append(n).append("(");
   3.559 +            StringBuilder assembleURL = new StringBuilder();
   3.560 +            String jsonpVarName = null;
   3.561 +            {
   3.562 +                String sep = "";
   3.563 +                boolean skipJSONP = onR.jsonp().isEmpty();
   3.564 +                for (String p : findParamNames(e, onR.url(), assembleURL)) {
   3.565 +                    if (!skipJSONP && p.equals(onR.jsonp())) {
   3.566 +                        skipJSONP = true;
   3.567 +                        jsonpVarName = p;
   3.568 +                        continue;
   3.569 +                    }
   3.570 +                    body.append(sep);
   3.571 +                    body.append("String ").append(p);
   3.572 +                    sep = ", ";
   3.573 +                }
   3.574 +                if (!skipJSONP) {
   3.575 +                    err().printMessage(Diagnostic.Kind.ERROR, 
   3.576 +                        "Name of jsonp attribute ('" + onR.jsonp() + 
   3.577 +                        "') is not used in url attribute '" + onR.url() + "'"
   3.578 +                    );
   3.579 +                }
   3.580 +            }
   3.581 +            body.append(") {\n");
   3.582 +            body.append("  final Object[] result = { null };\n");
   3.583 +            body.append(
   3.584 +                "  class ProcessResult implements Runnable {\n" +
   3.585 +                "    @Override\n" +
   3.586 +                "    public void run() {\n" +
   3.587 +                "      Object value = result[0];\n");
   3.588 +            body.append(
   3.589 +                "      " + modelClass + "[] arr;\n");
   3.590 +            body.append(
   3.591 +                "      if (value instanceof Object[]) {\n" +
   3.592 +                "        Object[] data = ((Object[])value);\n" +
   3.593 +                "        arr = new " + modelClass + "[data.length];\n" +
   3.594 +                "        for (int i = 0; i < data.length; i++) {\n" +
   3.595 +                "          arr[i] = new " + modelClass + "(data[i]);\n" +
   3.596 +                "        }\n" +
   3.597 +                "      } else {\n" +
   3.598 +                "        arr = new " + modelClass + "[1];\n" +
   3.599 +                "        arr[0] = new " + modelClass + "(value);\n" +
   3.600 +                "      }\n"
   3.601 +            );
   3.602 +            {
   3.603 +                body.append(clazz.getSimpleName()).append(".").append(n).append("(");
   3.604 +                String sep = "";
   3.605 +                for (String arg : args) {
   3.606 +                    body.append(sep);
   3.607 +                    body.append(arg);
   3.608 +                    sep = ", ";
   3.609 +                }
   3.610 +                body.append(");\n");
   3.611 +            }
   3.612 +            body.append(
   3.613 +                "    }\n" +
   3.614 +                "  }\n"
   3.615 +            );
   3.616 +            body.append("  ProcessResult pr = new ProcessResult();\n");
   3.617 +            if (jsonpVarName != null) {
   3.618 +                body.append("  String ").append(jsonpVarName).
   3.619 +                    append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n");
   3.620 +            }
   3.621 +            body.append("  org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n      ");
   3.622 +            body.append(assembleURL);
   3.623 +            body.append(", result, pr, ").append(jsonpVarName).append("\n  );\n");
   3.624 +//            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   3.625 +//            body.append(wrapParams(e, null, className, "ev", "data"));
   3.626 +//            body.append(");\n");
   3.627 +            body.append("}\n");
   3.628 +        }
   3.629 +        return true;
   3.630 +    }
   3.631 +
   3.632      private CharSequence wrapParams(
   3.633          ExecutableElement ee, String id, String className, String evName, String dataName
   3.634      ) {
   3.635 @@ -712,6 +1007,14 @@
   3.636                      params.append(dataName);
   3.637                      params.append(", null");
   3.638                  } else {
   3.639 +                    if (evName == null) {
   3.640 +                        final StringBuilder sb = new StringBuilder();
   3.641 +                        sb.append("Unexpected string parameter name.");
   3.642 +                        if (dataName != null) {
   3.643 +                            sb.append(" Try \"").append(dataName).append("\"");
   3.644 +                        }
   3.645 +                        err().printMessage(Diagnostic.Kind.ERROR, sb.toString(), ee);
   3.646 +                    }
   3.647                      params.append(evName);
   3.648                      params.append(", \"");
   3.649                      params.append(ve.getSimpleName().toString());
   3.650 @@ -720,7 +1023,7 @@
   3.651                  params.append(")");
   3.652                  continue;
   3.653              }
   3.654 -            String rn = ve.asType().toString();
   3.655 +            String rn = fqn(ve.asType(), ee);
   3.656              int last = rn.lastIndexOf('.');
   3.657              if (last >= 0) {
   3.658                  rn = rn.substring(last + 1);
   3.659 @@ -729,7 +1032,7 @@
   3.660                  params.append(className).append(".this");
   3.661                  continue;
   3.662              }
   3.663 -            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   3.664 +            err().printMessage(Diagnostic.Kind.ERROR, 
   3.665                  "@On method can only accept String named 'id' or " + className + " arguments",
   3.666                  ee
   3.667              );
   3.668 @@ -737,6 +1040,42 @@
   3.669          return params;
   3.670      }
   3.671      
   3.672 +    
   3.673 +    private CharSequence wrapPropName(
   3.674 +        ExecutableElement ee, String className, String propName, String propValue
   3.675 +    ) {
   3.676 +        TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   3.677 +        StringBuilder params = new StringBuilder();
   3.678 +        boolean first = true;
   3.679 +        for (VariableElement ve : ee.getParameters()) {
   3.680 +            if (!first) {
   3.681 +                params.append(", ");
   3.682 +            }
   3.683 +            first = false;
   3.684 +            if (ve.asType() == stringType) {
   3.685 +                if (propName != null && ve.getSimpleName().contentEquals(propName)) {
   3.686 +                    params.append('"').append(propValue).append('"');
   3.687 +                } else {
   3.688 +                    err().printMessage(Diagnostic.Kind.ERROR, "Unexpected string parameter name. Try \"" + propName + "\".");
   3.689 +                }
   3.690 +                continue;
   3.691 +            }
   3.692 +            String rn = fqn(ve.asType(), ee);
   3.693 +            int last = rn.lastIndexOf('.');
   3.694 +            if (last >= 0) {
   3.695 +                rn = rn.substring(last + 1);
   3.696 +            }
   3.697 +            if (rn.equals(className)) {
   3.698 +                params.append(className).append(".this");
   3.699 +                continue;
   3.700 +            }
   3.701 +            err().printMessage(Diagnostic.Kind.ERROR,
   3.702 +                "@OnPropertyChange method can only accept String or " + className + " arguments",
   3.703 +                ee);
   3.704 +        }
   3.705 +        return params;
   3.706 +    }
   3.707 +    
   3.708      private boolean isModel(TypeMirror tm) {
   3.709          final Element e = processingEnv.getTypeUtils().asElement(tm);
   3.710          if (e == null) {
   3.711 @@ -767,4 +1106,141 @@
   3.712          }
   3.713          w.write("\n  }");
   3.714      }
   3.715 +    
   3.716 +    private void writeToString(Property[] props, Writer w) throws IOException {
   3.717 +        w.write("  public String toString() {\n");
   3.718 +        w.write("    StringBuilder sb = new StringBuilder();\n");
   3.719 +        w.write("    sb.append('{');\n");
   3.720 +        String sep = "";
   3.721 +        for (Property p : props) {
   3.722 +            w.write(sep);
   3.723 +            w.append("    sb.append(\"" + p.name() + ": \");\n");
   3.724 +            w.append("    sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
   3.725 +            w.append(p.name()).append("));\n");
   3.726 +            sep =    "    sb.append(',');\n";
   3.727 +        }
   3.728 +        w.write("    sb.append('}');\n");
   3.729 +        w.write("    return sb.toString();\n");
   3.730 +        w.write("  }\n");
   3.731 +    }
   3.732 +    private void writeClone(String className, Property[] props, Writer w) throws IOException {
   3.733 +        w.write("  public " + className + " clone() {\n");
   3.734 +        w.write("    " + className + " ret = new " + className + "();\n");
   3.735 +        for (Property p : props) {
   3.736 +            if (!p.array()) {
   3.737 +                boolean isModel[] = { false };
   3.738 +                boolean isEnum[] = { false };
   3.739 +                checkType(p, isModel, isEnum);
   3.740 +                if (!isModel[0]) {
   3.741 +                    w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
   3.742 +                    continue;
   3.743 +                }
   3.744 +                w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
   3.745 +            } else {
   3.746 +                w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
   3.747 +            }
   3.748 +        }
   3.749 +        
   3.750 +        w.write("    return ret;\n");
   3.751 +        w.write("  }\n");
   3.752 +    }
   3.753 +
   3.754 +    private String inPckName(Element e) {
   3.755 +        StringBuilder sb = new StringBuilder();
   3.756 +        while (e.getKind() != ElementKind.PACKAGE) {
   3.757 +            if (sb.length() == 0) {
   3.758 +                sb.append(e.getSimpleName());
   3.759 +            } else {
   3.760 +                sb.insert(0, '.');
   3.761 +                sb.insert(0, e.getSimpleName());
   3.762 +            }
   3.763 +            e = e.getEnclosingElement();
   3.764 +        }
   3.765 +        return sb.toString();
   3.766 +    }
   3.767 +
   3.768 +    private String fqn(TypeMirror pt, Element relative) {
   3.769 +        if (pt.getKind() == TypeKind.ERROR) {
   3.770 +            final Elements eu = processingEnv.getElementUtils();
   3.771 +            PackageElement pckg = eu.getPackageOf(relative);
   3.772 +            return pckg.getQualifiedName() + "." + pt.toString();
   3.773 +        }
   3.774 +        return pt.toString();
   3.775 +    }
   3.776 +
   3.777 +    private String checkType(Property p, boolean[] isModel, boolean[] isEnum) {
   3.778 +        String ret;
   3.779 +        try {
   3.780 +            ret = p.type().getName();
   3.781 +        } catch (MirroredTypeException ex) {
   3.782 +            TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror());
   3.783 +            final Element e = processingEnv.getTypeUtils().asElement(tm);
   3.784 +            final Model m = e == null ? null : e.getAnnotation(Model.class);
   3.785 +            if (m != null) {
   3.786 +                ret = findPkgName(e) + '.' + m.className();
   3.787 +                isModel[0] = true;
   3.788 +                models.put(e, m.className());
   3.789 +            } else {
   3.790 +                ret = tm.toString();
   3.791 +            }
   3.792 +            TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
   3.793 +            enm = processingEnv.getTypeUtils().erasure(enm);
   3.794 +            isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
   3.795 +        }
   3.796 +        return ret;
   3.797 +    }
   3.798 +
   3.799 +    private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
   3.800 +        List<String> params = new ArrayList<>();
   3.801 +
   3.802 +        for (int pos = 0; ;) {
   3.803 +            int next = url.indexOf('{', pos);
   3.804 +            if (next == -1) {
   3.805 +                assembleURL.append('"')
   3.806 +                    .append(url.substring(pos))
   3.807 +                    .append('"');
   3.808 +                return params;
   3.809 +            }
   3.810 +            int close = url.indexOf('}', next);
   3.811 +            if (close == -1) {
   3.812 +                err().printMessage(Diagnostic.Kind.ERROR, "Unbalanced '{' and '}' in " + url, e);
   3.813 +                return params;
   3.814 +            }
   3.815 +            final String paramName = url.substring(next + 1, close);
   3.816 +            params.add(paramName);
   3.817 +            assembleURL.append('"')
   3.818 +                .append(url.substring(pos, next))
   3.819 +                .append("\" + ").append(paramName).append(" + ");
   3.820 +            pos = close + 1;
   3.821 +        }
   3.822 +    }
   3.823 +
   3.824 +    private static Property findProperty(Property[] properties, String propName) {
   3.825 +        for (Property p : properties) {
   3.826 +            if (propName.equals(p.name())) {
   3.827 +                return p;
   3.828 +            }
   3.829 +        }
   3.830 +        return null;
   3.831 +    }
   3.832 +
   3.833 +    private boolean isPrimitive(String type) {
   3.834 +        return 
   3.835 +            "int".equals(type) ||
   3.836 +            "double".equals(type) ||
   3.837 +            "long".equals(type) ||
   3.838 +            "short".equals(type) ||
   3.839 +            "byte".equals(type) ||
   3.840 +            "float".equals(type);
   3.841 +    }
   3.842 +
   3.843 +    private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
   3.844 +        Set<String> names = new HashSet<>();
   3.845 +        for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
   3.846 +            if (e.getValue().contains(derivedProp)) {
   3.847 +                names.add(e.getKey());
   3.848 +            }
   3.849 +        }
   3.850 +        return names;
   3.851 +    }
   3.852  }
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnPropertyChange.java	Mon Apr 08 19:33:08 2013 +0200
     4.3 @@ -0,0 +1,38 @@
     4.4 +/**
     4.5 + * Back 2 Browser Bytecode Translator
     4.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4.7 + *
     4.8 + * This program is free software: you can redistribute it and/or modify
     4.9 + * it under the terms of the GNU General Public License as published by
    4.10 + * the Free Software Foundation, version 2 of the License.
    4.11 + *
    4.12 + * This program is distributed in the hope that it will be useful,
    4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.15 + * GNU General Public License for more details.
    4.16 + *
    4.17 + * You should have received a copy of the GNU General Public License
    4.18 + * along with this program. Look for COPYING file in the top folder.
    4.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    4.20 + */
    4.21 +package org.apidesign.bck2brwsr.htmlpage.api;
    4.22 +
    4.23 +import java.lang.annotation.ElementType;
    4.24 +import java.lang.annotation.Retention;
    4.25 +import java.lang.annotation.RetentionPolicy;
    4.26 +import java.lang.annotation.Target;
    4.27 +
    4.28 +/** Represents a property. Either in a generated model of an HTML
    4.29 + * {@link Page} or in a class defined by {@link Model}.
    4.30 + *
    4.31 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    4.32 + */
    4.33 +@Retention(RetentionPolicy.SOURCE)
    4.34 +@Target(ElementType.METHOD)
    4.35 +public @interface OnPropertyChange {
    4.36 +    /** Name(s) of the properties. One wishes to observe.
    4.37 +     * 
    4.38 +     * @return valid java identifier
    4.39 +     */
    4.40 +    String[] value();
    4.41 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnReceive.java	Mon Apr 08 19:33:08 2013 +0200
     5.3 @@ -0,0 +1,54 @@
     5.4 +/**
     5.5 + * Back 2 Browser Bytecode Translator
     5.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     5.7 + *
     5.8 + * This program is free software: you can redistribute it and/or modify
     5.9 + * it under the terms of the GNU General Public License as published by
    5.10 + * the Free Software Foundation, version 2 of the License.
    5.11 + *
    5.12 + * This program is distributed in the hope that it will be useful,
    5.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    5.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    5.15 + * GNU General Public License for more details.
    5.16 + *
    5.17 + * You should have received a copy of the GNU General Public License
    5.18 + * along with this program. Look for COPYING file in the top folder.
    5.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    5.20 + */
    5.21 +package org.apidesign.bck2brwsr.htmlpage.api;
    5.22 +
    5.23 +import java.lang.annotation.ElementType;
    5.24 +import java.lang.annotation.Retention;
    5.25 +import java.lang.annotation.RetentionPolicy;
    5.26 +import java.lang.annotation.Target;
    5.27 +
    5.28 +/** Static methods in classes annotated by {@link Model} or {@link Page}
    5.29 + * can be marked by this annotation establish a JSON communication point.
    5.30 + * The associated model page then gets new method to invoke a network
    5.31 + * connection 
    5.32 + * 
    5.33 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    5.34 + */
    5.35 +@Retention(RetentionPolicy.SOURCE)
    5.36 +@Target(ElementType.METHOD)
    5.37 +public @interface OnReceive {
    5.38 +    /** The URL to connect to. Can contain variable names surrounded by '{' and '}'.
    5.39 +     * Those parameters will then become variables of the associated method.
    5.40 +     * 
    5.41 +     * @return the (possibly parametrized) url to connect to
    5.42 +     */
    5.43 +    String url();
    5.44 +    
    5.45 +    /** Support for <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a> requires
    5.46 +     * a callback from the server generated page to a function defined in the
    5.47 +     * system. The name of such function is usually specified as a property
    5.48 +     * (of possibly different names). By defining the <code>jsonp</code> attribute
    5.49 +     * one turns on the <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a> 
    5.50 +     * transmission and specifies the name of the property. The property should
    5.51 +     * also be used in the {@link #url()} attribute on appropriate place.
    5.52 +     * 
    5.53 +     * @return name of a property to carry the name of <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>
    5.54 +     *    callback function.
    5.55 +     */
    5.56 +    String jsonp() default "";
    5.57 +}
     6.1 --- a/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js	Wed Apr 03 13:43:22 2013 +0200
     6.2 +++ b/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js	Mon Apr 08 19:33:08 2013 +0200
     6.3 @@ -2193,7 +2193,14 @@
     6.4                      else
     6.5                          element[attrName] = attrValue;
     6.6                  } else if (!toRemove) {
     6.7 -                    element.setAttribute(attrName, attrValue.toString());
     6.8 +                    try {
     6.9 +                        element.setAttribute(attrName, attrValue.toString());
    6.10 +                    } catch (err) {
    6.11 +                        // ignore for now
    6.12 +                        if (console) {
    6.13 +                            console.log("Can't set attribute " + attrName + " to " + attrValue + " error: " + err);
    6.14 +                        }
    6.15 +                    }
    6.16                  }
    6.17  
    6.18                  // Treat "name" specially - although you can think of it as an attribute, it also needs
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypesTest.java	Mon Apr 08 19:33:08 2013 +0200
     7.3 @@ -0,0 +1,52 @@
     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;
    7.22 +
    7.23 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
    7.24 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
    7.25 +import org.apidesign.bck2brwsr.vmtest.VMTest;
    7.26 +import org.testng.annotations.Factory;
    7.27 +
    7.28 +/**
    7.29 + *
    7.30 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    7.31 + */
    7.32 +public class ConvertTypesTest {
    7.33 +    @JavaScriptBody(args = {  }, body = "var json = new Object();"
    7.34 +        + "json.firstName = 'son';\n"
    7.35 +        + "json.lastName = 'dj';\n"
    7.36 +        + "json.sex = 'MALE';\n"
    7.37 +        + "return json;"
    7.38 +    )
    7.39 +    private static native Object createJSON();
    7.40 +    
    7.41 +    @BrwsrTest
    7.42 +    public void testConvertToPeople() {
    7.43 +        final Object o = createJSON();
    7.44 +        
    7.45 +        Person p = new Person(o);
    7.46 +        
    7.47 +        assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
    7.48 +        assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName();
    7.49 +//        assert Sex.MALE.equals(p.getSex()) : "Sex: " + p.getSex();
    7.50 +    }
    7.51 +    
    7.52 +    @Factory public static Object[] create() {
    7.53 +        return VMTest.create(ConvertTypesTest.class);
    7.54 +    }
    7.55 +}
    7.56 \ No newline at end of file
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/JSONTest.java	Mon Apr 08 19:33:08 2013 +0200
     8.3 @@ -0,0 +1,325 @@
     8.4 +/**
     8.5 + * Back 2 Browser Bytecode Translator
     8.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     8.7 + *
     8.8 + * This program is free software: you can redistribute it and/or modify
     8.9 + * it under the terms of the GNU General Public License as published by
    8.10 + * the Free Software Foundation, version 2 of the License.
    8.11 + *
    8.12 + * This program is distributed in the hope that it will be useful,
    8.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    8.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    8.15 + * GNU General Public License for more details.
    8.16 + *
    8.17 + * You should have received a copy of the GNU General Public License
    8.18 + * along with this program. Look for COPYING file in the top folder.
    8.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    8.20 + */
    8.21 +package org.apidesign.bck2brwsr.htmlpage;
    8.22 +
    8.23 +import java.util.Arrays;
    8.24 +import java.util.Iterator;
    8.25 +import java.util.List;
    8.26 +import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
    8.27 +import org.apidesign.bck2brwsr.htmlpage.api.Page;
    8.28 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
    8.29 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
    8.30 +import org.apidesign.bck2brwsr.vmtest.Http;
    8.31 +import org.apidesign.bck2brwsr.vmtest.VMTest;
    8.32 +import org.json.JSONException;
    8.33 +import org.json.JSONObject;
    8.34 +import org.json.JSONTokener;
    8.35 +import org.testng.annotations.Test;
    8.36 +import static org.testng.Assert.*;
    8.37 +import org.testng.annotations.Factory;
    8.38 +
    8.39 +/** Need to verify that models produce reasonable JSON objects.
    8.40 + *
    8.41 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.42 + */
    8.43 +@Page(xhtml = "Empty.html", className = "JSONik", properties = {
    8.44 +    @Property(name = "fetched", type = PersonImpl.class),
    8.45 +    @Property(name = "fetchedCount", type = int.class)
    8.46 +})
    8.47 +public class JSONTest {
    8.48 +    private JSONik js;
    8.49 +    
    8.50 +    @Test public void personToString() throws JSONException {
    8.51 +        Person p = new Person();
    8.52 +        p.setSex(Sex.MALE);
    8.53 +        p.setFirstName("Jarda");
    8.54 +        p.setLastName("Tulach");
    8.55 +        
    8.56 +        JSONTokener t = new JSONTokener(p.toString());
    8.57 +        JSONObject o;
    8.58 +        try {
    8.59 +            o = new JSONObject(t);
    8.60 +        } catch (JSONException ex) {
    8.61 +            throw new AssertionError("Can't parse " + p.toString(), ex);
    8.62 +        }
    8.63 +        
    8.64 +        Iterator it = o.sortedKeys();
    8.65 +        assertEquals(it.next(), "firstName");
    8.66 +        assertEquals(it.next(), "lastName");
    8.67 +        assertEquals(it.next(), "sex");
    8.68 +        
    8.69 +        assertEquals(o.getString("firstName"), "Jarda");
    8.70 +        assertEquals(o.getString("lastName"), "Tulach");
    8.71 +        assertEquals(o.getString("sex"), "MALE");
    8.72 +    }
    8.73 +    
    8.74 +    @Test public void personWithWildCharactersAndNulls() throws JSONException {
    8.75 +        Person p = new Person();
    8.76 +        p.setFirstName("'\"\n");
    8.77 +        p.setLastName("\t\r\u0002");
    8.78 +        
    8.79 +        JSONTokener t = new JSONTokener(p.toString());
    8.80 +        JSONObject o;
    8.81 +        try {
    8.82 +            o = new JSONObject(t);
    8.83 +        } catch (JSONException ex) {
    8.84 +            throw new AssertionError("Can't parse " + p.toString(), ex);
    8.85 +        }
    8.86 +        
    8.87 +        Iterator it = o.sortedKeys();
    8.88 +        assertEquals(it.next(), "firstName");
    8.89 +        assertEquals(it.next(), "lastName");
    8.90 +        assertEquals(it.next(), "sex");
    8.91 +        
    8.92 +        assertEquals(o.getString("firstName"), p.getFirstName());
    8.93 +        assertEquals(o.getString("lastName"), p.getLastName());
    8.94 +        assertEquals(o.get("sex"), JSONObject.NULL);
    8.95 +    }
    8.96 +    
    8.97 +    @Test public void personsInArray() throws JSONException {
    8.98 +        Person p1 = new Person();
    8.99 +        p1.setFirstName("One");
   8.100 +
   8.101 +        Person p2 = new Person();
   8.102 +        p2.setFirstName("Two");
   8.103 +        
   8.104 +        People arr = new People();
   8.105 +        arr.getInfo().add(p1);
   8.106 +        arr.getInfo().add(p2);
   8.107 +        arr.getNicknames().add("Prvn\u00ed k\u016f\u0148");
   8.108 +        final String n2 = "Druh\u00fd hlem\u00fd\u017e\u010f, star\u0161\u00ed";
   8.109 +        arr.getNicknames().add(n2);
   8.110 +        arr.getAge().add(33);
   8.111 +        arr.getAge().add(73);
   8.112 +        
   8.113 +        
   8.114 +        final String json = arr.toString();
   8.115 +        
   8.116 +        JSONTokener t = new JSONTokener(json);
   8.117 +        JSONObject o;
   8.118 +        try {
   8.119 +            o = new JSONObject(t);
   8.120 +        } catch (JSONException ex) {
   8.121 +            throw new AssertionError("Can't parse " + json, ex);
   8.122 +        }
   8.123 +
   8.124 +        assertEquals(o.getJSONArray("info").getJSONObject(0).getString("firstName"), "One");
   8.125 +        assertEquals(o.getJSONArray("nicknames").getString(1), n2);
   8.126 +        assertEquals(o.getJSONArray("age").getInt(1), 73);
   8.127 +    }
   8.128 +    
   8.129 +    
   8.130 +    @OnReceive(url="/{url}")
   8.131 +    static void fetch(Person p, JSONik model) {
   8.132 +        model.setFetched(p);
   8.133 +    }
   8.134 +
   8.135 +    @OnReceive(url="/{url}")
   8.136 +    static void fetchArray(Person[] p, JSONik model) {
   8.137 +        model.setFetchedCount(p.length);
   8.138 +        model.setFetched(p[0]);
   8.139 +    }
   8.140 +    
   8.141 +    @OnReceive(url="/{url}")
   8.142 +    static void fetchPeople(People p, JSONik model) {
   8.143 +        model.setFetchedCount(p.getInfo().size());
   8.144 +        model.setFetched(p.getInfo().get(0));
   8.145 +    }
   8.146 +
   8.147 +    @OnReceive(url="/{url}")
   8.148 +    static void fetchPeopleAge(People p, JSONik model) {
   8.149 +        int sum = 0;
   8.150 +        for (int a : p.getAge()) {
   8.151 +            sum += a;
   8.152 +        }
   8.153 +        model.setFetchedCount(sum);
   8.154 +    }
   8.155 +    
   8.156 +    @Http(@Http.Resource(
   8.157 +        content = "{'firstName': 'Sitar', 'sex': 'MALE'}", 
   8.158 +        path="/person.json", 
   8.159 +        mimeType = "application/json"
   8.160 +    ))
   8.161 +    @BrwsrTest public void loadAndParseJSON() throws InterruptedException {
   8.162 +        if (js == null) {
   8.163 +            js = new JSONik();
   8.164 +            js.applyBindings();
   8.165 +
   8.166 +            js.fetch("person.json");
   8.167 +        }
   8.168 +    
   8.169 +        Person p = js.getFetched();
   8.170 +        if (p == null) {
   8.171 +            throw new InterruptedException();
   8.172 +        }
   8.173 +        
   8.174 +        assert "Sitar".equals(p.getFirstName()) : "Expecting Sitar: " + p.getFirstName();
   8.175 +      //  assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
   8.176 +    }
   8.177 +    
   8.178 +    @OnReceive(url="/{url}?callme={me}", jsonp = "me")
   8.179 +    static void fetchViaJSONP(Person p, JSONik model) {
   8.180 +        model.setFetched(p);
   8.181 +    }
   8.182 +    
   8.183 +    @Http(@Http.Resource(
   8.184 +        content = "$0({'firstName': 'Mitar', 'sex': 'MALE'})", 
   8.185 +        path="/person.json", 
   8.186 +        mimeType = "application/javascript",
   8.187 +        parameters = { "callme" }
   8.188 +    ))
   8.189 +    @BrwsrTest public void loadAndParseJSONP() throws InterruptedException {
   8.190 +        if (js == null) {
   8.191 +            js = new JSONik();
   8.192 +            js.applyBindings();
   8.193 +
   8.194 +            js.fetchViaJSONP("person.json");
   8.195 +        }
   8.196 +    
   8.197 +        Person p = js.getFetched();
   8.198 +        if (p == null) {
   8.199 +            throw new InterruptedException();
   8.200 +        }
   8.201 +        
   8.202 +        assert "Mitar".equals(p.getFirstName()) : "Unexpected: " + p.getFirstName();
   8.203 +      //  assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
   8.204 +    }
   8.205 +
   8.206 +    @Http(@Http.Resource(
   8.207 +        content = "{'firstName': 'Sitar', 'sex': 'MALE'}", 
   8.208 +        path="/person.json", 
   8.209 +        mimeType = "application/json"
   8.210 +    ))
   8.211 +    @BrwsrTest public void loadAndParseJSONSentToArray() throws InterruptedException {
   8.212 +        if (js == null) {
   8.213 +            js = new JSONik();
   8.214 +            js.applyBindings();
   8.215 +
   8.216 +            js.fetchArray("person.json");
   8.217 +        }
   8.218 +        
   8.219 +        Person p = js.getFetched();
   8.220 +        if (p == null) {
   8.221 +            throw new InterruptedException();
   8.222 +        }
   8.223 +        
   8.224 +        assert p != null : "We should get our person back: " + p;
   8.225 +        assert "Sitar".equals(p.getFirstName()) : "Expecting Sitar: " + p.getFirstName();
   8.226 +//        assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
   8.227 +    }
   8.228 +    
   8.229 +    @Http(@Http.Resource(
   8.230 +        content = "[{'firstName': 'Gitar', 'sex': 'FEMALE'}]", 
   8.231 +        path="/person.json", 
   8.232 +        mimeType = "application/json"
   8.233 +    ))
   8.234 +    @BrwsrTest public void loadAndParseJSONArraySingle() throws InterruptedException {
   8.235 +        if (js == null) {
   8.236 +            js = new JSONik();
   8.237 +            js.applyBindings();
   8.238 +        
   8.239 +            js.fetch("person.json");
   8.240 +        }
   8.241 +        
   8.242 +        Person p = js.getFetched();
   8.243 +        if (p == null) {
   8.244 +            throw new InterruptedException();
   8.245 +        }
   8.246 +        
   8.247 +        assert p != null : "We should get our person back: " + p;
   8.248 +        assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName();
   8.249 +//        assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
   8.250 +    }
   8.251 +    
   8.252 +    @Http(@Http.Resource(
   8.253 +        content = "{'info':[{'firstName': 'Gitar', 'sex': 'FEMALE'}]}", 
   8.254 +        path="/people.json", 
   8.255 +        mimeType = "application/json"
   8.256 +    ))
   8.257 +    @BrwsrTest public void loadAndParseArrayInPeople() throws InterruptedException {
   8.258 +        if (js == null) {
   8.259 +            js = new JSONik();
   8.260 +            js.applyBindings();
   8.261 +        
   8.262 +            js.fetchPeople("people.json");
   8.263 +        }
   8.264 +        
   8.265 +        if (0 == js.getFetchedCount()) {
   8.266 +            throw new InterruptedException();
   8.267 +        }
   8.268 +
   8.269 +        assert js.getFetchedCount() == 1 : "One person loaded: " + js.getFetchedCount();
   8.270 +        
   8.271 +        Person p = js.getFetched();
   8.272 +        
   8.273 +        assert p != null : "We should get our person back: " + p;
   8.274 +        assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName();
   8.275 +//        assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
   8.276 +    }
   8.277 +    
   8.278 +    @Http(@Http.Resource(
   8.279 +        content = "{'age':[1, 2, 3]}", 
   8.280 +        path="/people.json", 
   8.281 +        mimeType = "application/json"
   8.282 +    ))
   8.283 +    @BrwsrTest public void loadAndParseArrayOfIntegers() throws InterruptedException {
   8.284 +        if (js == null) {
   8.285 +            js = new JSONik();
   8.286 +            js.applyBindings();
   8.287 +        
   8.288 +            js.fetchPeopleAge("people.json");
   8.289 +        }
   8.290 +        
   8.291 +        if (0 == js.getFetchedCount()) {
   8.292 +            throw new InterruptedException();
   8.293 +        }
   8.294 +
   8.295 +        assert js.getFetchedCount() == 6 : "1 + 2 + 3 is " + js.getFetchedCount();
   8.296 +    }
   8.297 +    
   8.298 +    @Http(@Http.Resource(
   8.299 +        content = "[{'firstName': 'Gitar', 'sex': 'FEMALE'},"
   8.300 +        + "{'firstName': 'Peter', 'sex': 'MALE'}"
   8.301 +        + "]", 
   8.302 +        path="/person.json", 
   8.303 +        mimeType = "application/json"
   8.304 +    ))
   8.305 +    @BrwsrTest public void loadAndParseJSONArray() throws InterruptedException {
   8.306 +        if (js == null) {
   8.307 +            js = new JSONik();
   8.308 +            js.applyBindings();
   8.309 +            js.fetchArray("person.json");
   8.310 +        }
   8.311 +        
   8.312 +        
   8.313 +        Person p = js.getFetched();
   8.314 +        if (p == null) {
   8.315 +            throw new InterruptedException();
   8.316 +        }
   8.317 +        
   8.318 +        assert js.getFetchedCount() == 2 : "We got two values: " + js.getFetchedCount();
   8.319 +        assert p != null : "We should get our person back: " + p;
   8.320 +        assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName();
   8.321 +//        assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex();
   8.322 +    }
   8.323 +
   8.324 +    @Factory public static Object[] create() {
   8.325 +        return VMTest.create(JSONTest.class);
   8.326 +    }
   8.327 +    
   8.328 +}
     9.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java	Wed Apr 03 13:43:22 2013 +0200
     9.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java	Mon Apr 08 19:33:08 2013 +0200
     9.3 @@ -27,7 +27,9 @@
     9.4  import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
     9.5  import org.apidesign.bck2brwsr.vmtest.HtmlFragment;
     9.6  import org.apidesign.bck2brwsr.vmtest.VMTest;
     9.7 +import static org.testng.Assert.assertEquals;
     9.8  import org.testng.annotations.Factory;
     9.9 +import org.testng.annotations.Test;
    9.10  
    9.11  /**
    9.12   *
    9.13 @@ -139,30 +141,70 @@
    9.14          assert "changed".equals(txt) : "Expecting 'changed': " + txt;
    9.15      }
    9.16      
    9.17 +    @ComputedProperty
    9.18 +    static Person firstPerson(List<Person> people) {
    9.19 +        return people.isEmpty() ? null : people.get(0);
    9.20 +    }
    9.21 +    
    9.22 +    @HtmlFragment(
    9.23 +        "<p id='ul' data-bind='with: firstPerson'>\n"
    9.24 +        + "  <span data-bind='text: firstName, click: changeSex'></span>\n"
    9.25 +        + "</p>\n"
    9.26 +    )
    9.27 +    @BrwsrTest public void accessFirstPersonWithOnFunction() {
    9.28 +        trasfertToFemale();
    9.29 +    }
    9.30 +    
    9.31      @HtmlFragment(
    9.32          "<ul id='ul' data-bind='foreach: people'>\n"
    9.33          + "  <li data-bind='text: $data.firstName, click: changeSex'></li>\n"
    9.34          + "</ul>\n"
    9.35      )
    9.36      @BrwsrTest public void onPersonFunction() {
    9.37 +        trasfertToFemale();
    9.38 +    }
    9.39 +    
    9.40 +    private void trasfertToFemale() {
    9.41          KnockoutModel m = new KnockoutModel();
    9.42 -        
    9.43 +
    9.44          final Person first = new Person();
    9.45          first.setFirstName("first");
    9.46          first.setSex(Sex.MALE);
    9.47          m.getPeople().add(first);
    9.48 -        
    9.49 -        
    9.50 +
    9.51 +
    9.52          m.applyBindings();
    9.53 -        
    9.54 +
    9.55          int cnt = countChildren("ul");
    9.56          assert cnt == 1 : "One child, but was " + cnt;
    9.57 -        
    9.58 -        
    9.59 +
    9.60 +
    9.61          triggerChildClick("ul", 0);
    9.62 -        
    9.63 +
    9.64          assert first.getSex() == Sex.FEMALE : "Transverted to female: " + first.getSex();
    9.65      }
    9.66 +    
    9.67 +    @Test public void cloneModel() {
    9.68 +        Person model = new Person();
    9.69 +        
    9.70 +        model.setFirstName("first");
    9.71 +        Person snd = model.clone();
    9.72 +        snd.setFirstName("clone");
    9.73 +        assertEquals("first", model.getFirstName(), "Value has not changed");
    9.74 +        assertEquals("clone", snd.getFirstName(), "Value has changed in clone");
    9.75 +    }
    9.76 +   
    9.77 +    
    9.78 +    @Test public void deepCopyOnClone() {
    9.79 +        People model = new People();
    9.80 +        model.getNicknames().add("Jarda");
    9.81 +        assertEquals(model.getNicknames().size(), 1, "One element");
    9.82 +        People snd = model.clone();
    9.83 +        snd.getNicknames().clear();
    9.84 +        assertEquals(snd.getNicknames().size(), 0, "Clone is empty");
    9.85 +        assertEquals(model.getNicknames().size(), 1, "Still one element");
    9.86 +    }
    9.87 +    
    9.88       
    9.89      @OnFunction
    9.90      static void call(KnockoutModel m, String data) {
    10.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Wed Apr 03 13:43:22 2013 +0200
    10.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Mon Apr 08 19:33:08 2013 +0200
    10.3 @@ -24,6 +24,7 @@
    10.4  import java.util.ListIterator;
    10.5  import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
    10.6  import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
    10.7 +import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange;
    10.8  import org.apidesign.bck2brwsr.htmlpage.api.Page;
    10.9  import org.apidesign.bck2brwsr.htmlpage.api.Property;
   10.10  import static org.testng.Assert.*;
   10.11 @@ -40,7 +41,8 @@
   10.12      @Property(name = "unrelated", type = long.class),
   10.13      @Property(name = "names", type = String.class, array = true),
   10.14      @Property(name = "values", type = int.class, array = true),
   10.15 -    @Property(name = "people", type = PersonImpl.class, array = true)
   10.16 +    @Property(name = "people", type = PersonImpl.class, array = true),
   10.17 +    @Property(name = "changedProperty", type=String.class)
   10.18  })
   10.19  public class ModelTest {
   10.20      private Modelik model;
   10.21 @@ -138,6 +140,9 @@
   10.22          
   10.23          model.setValue(33);
   10.24          
   10.25 +        // not interested in change of this property
   10.26 +        my.mutated.remove("changedProperty");
   10.27 +        
   10.28          assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated);
   10.29          assertTrue(my.mutated.contains("powerValue"), "Power value is in there: " + my.mutated);
   10.30          assertTrue(my.mutated.contains("value"), "Simple value is in there: " + my.mutated);
   10.31 @@ -145,7 +150,11 @@
   10.32          my.mutated.clear();
   10.33          
   10.34          model.setUnrelated(44);
   10.35 -        assertEquals(my.mutated.size(), 1, "One property changed");
   10.36 +        
   10.37 +        
   10.38 +        // not interested in change of this property
   10.39 +        my.mutated.remove("changedProperty");
   10.40 +        assertEquals(my.mutated.size(), 1, "One property changed: " + my.mutated);
   10.41          assertTrue(my.mutated.contains("unrelated"), "Its name is unrelated");
   10.42      }
   10.43      
   10.44 @@ -178,6 +187,34 @@
   10.45          return value * value;
   10.46      }
   10.47      
   10.48 +    @OnPropertyChange({ "powerValue", "unrelated" })
   10.49 +    static void aPropertyChanged(Modelik m, String name) {
   10.50 +        m.setChangedProperty(name);
   10.51 +    }
   10.52 +
   10.53 +    @OnPropertyChange({ "values" })
   10.54 +    static void anArrayPropertyChanged(String name, Modelik m) {
   10.55 +        m.setChangedProperty(name);
   10.56 +    }
   10.57 +    
   10.58 +    @Test public void changeAnything() {
   10.59 +        model.setCount(44);
   10.60 +        assertNull(model.getChangedProperty(), "No observed value change");
   10.61 +    }
   10.62 +    @Test public void changeValue() {
   10.63 +        model.setValue(33);
   10.64 +        assertEquals(model.getChangedProperty(), "powerValue", "power property changed");
   10.65 +    }
   10.66 +    @Test public void changeUnrelated() {
   10.67 +        model.setUnrelated(333);
   10.68 +        assertEquals(model.getChangedProperty(), "unrelated", "unrelated changed");
   10.69 +    }
   10.70 +
   10.71 +    @Test public void changeInArray() {
   10.72 +        model.getValues().add(10);
   10.73 +        assertEquals(model.getChangedProperty(), "values", "Something added into the array");
   10.74 +    }
   10.75 +    
   10.76      @ComputedProperty
   10.77      static String notAllowedRead() {
   10.78          return "Not allowed callback: " + leakedModel.getUnrelated();
    11.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java	Wed Apr 03 13:43:22 2013 +0200
    11.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java	Mon Apr 08 19:33:08 2013 +0200
    11.3 @@ -50,4 +50,11 @@
    11.4              p.setSex(Sex.MALE);
    11.5          }
    11.6      }
    11.7 +    
    11.8 +    @Model(className = "People", properties = {
    11.9 +        @Property(array = true, name = "info", type = PersonImpl.class),
   11.10 +        @Property(array = true, name = "nicknames", type = String.class),
   11.11 +        @Property(array = true, name = "age", type = int.class),})
   11.12 +    public class PeopleImpl {
   11.13 +    }
   11.14  }
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/javaquery/demo-twitter/bck2brwsr-assembly.xml	Mon Apr 08 19:33:08 2013 +0200
    12.3 @@ -0,0 +1,62 @@
    12.4 +<?xml version="1.0"?>
    12.5 +<!--
    12.6 +
    12.7 +    Back 2 Browser Bytecode Translator
    12.8 +    Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    12.9 +
   12.10 +    This program is free software: you can redistribute it and/or modify
   12.11 +    it under the terms of the GNU General Public License as published by
   12.12 +    the Free Software Foundation, version 2 of the License.
   12.13 +
   12.14 +    This program is distributed in the hope that it will be useful,
   12.15 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
   12.16 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12.17 +    GNU General Public License for more details.
   12.18 +
   12.19 +    You should have received a copy of the GNU General Public License
   12.20 +    along with this program. Look for COPYING file in the top folder.
   12.21 +    If not, see http://opensource.org/licenses/GPL-2.0.
   12.22 +
   12.23 +-->
   12.24 +<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   12.25 +  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
   12.26 +  
   12.27 +  <id>bck2brwsr</id>
   12.28 +  <formats>
   12.29 +      <format>zip</format>
   12.30 +  </formats>
   12.31 +  <baseDirectory>public_html</baseDirectory>
   12.32 +  <dependencySets>
   12.33 +    <dependencySet>
   12.34 +        <useProjectArtifact>false</useProjectArtifact>
   12.35 +        <scope>runtime</scope>
   12.36 +        <outputDirectory>lib</outputDirectory>
   12.37 +        <includes>
   12.38 +            <include>*:jar</include>
   12.39 +            <include>*:rt</include>
   12.40 +        </includes>
   12.41 +    </dependencySet>
   12.42 +  </dependencySets> 
   12.43 +  <fileSets>
   12.44 +      <fileSet>
   12.45 +          <directory>${project.build.directory}/classes/org/apidesign/bck2brwsr/demo/twitter/</directory>
   12.46 +          <includes>
   12.47 +              <include>**/*</include>
   12.48 +          </includes>
   12.49 +          <excludes>
   12.50 +              <exclude>**/*.class</exclude>
   12.51 +          </excludes>
   12.52 +          <outputDirectory>/</outputDirectory>
   12.53 +      </fileSet>
   12.54 +  </fileSets>
   12.55 +  <files>
   12.56 +    <file>
   12.57 +      <source>${project.build.directory}/${project.build.finalName}.jar</source>
   12.58 +      <outputDirectory>/</outputDirectory>
   12.59 +    </file>
   12.60 +    <file>
   12.61 +      <source>${project.build.directory}/bck2brwsr.js</source>
   12.62 +      <outputDirectory>/</outputDirectory>
   12.63 +    </file>
   12.64 +  </files>
   12.65 +</assembly>
   12.66 \ No newline at end of file
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/javaquery/demo-twitter/nb-configuration.xml	Mon Apr 08 19:33:08 2013 +0200
    13.3 @@ -0,0 +1,37 @@
    13.4 +<?xml version="1.0" encoding="UTF-8"?>
    13.5 +<!--
    13.6 +
    13.7 +    Back 2 Browser Bytecode Translator
    13.8 +    Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    13.9 +
   13.10 +    This program is free software: you can redistribute it and/or modify
   13.11 +    it under the terms of the GNU General Public License as published by
   13.12 +    the Free Software Foundation, version 2 of the License.
   13.13 +
   13.14 +    This program is distributed in the hope that it will be useful,
   13.15 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
   13.16 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13.17 +    GNU General Public License for more details.
   13.18 +
   13.19 +    You should have received a copy of the GNU General Public License
   13.20 +    along with this program. Look for COPYING file in the top folder.
   13.21 +    If not, see http://opensource.org/licenses/GPL-2.0.
   13.22 +
   13.23 +-->
   13.24 +<project-shared-configuration>
   13.25 +    <!--
   13.26 +This file contains additional configuration written by modules in the NetBeans IDE.
   13.27 +The configuration is intended to be shared among all the users of project and
   13.28 +therefore it is assumed to be part of version control checkout.
   13.29 +Without this configuration present, some functionality in the IDE may be limited or fail altogether.
   13.30 +-->
   13.31 +    <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
   13.32 +        <!--
   13.33 +Properties that influence various parts of the IDE, especially code formatting and the like. 
   13.34 +You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
   13.35 +That way multiple projects can share the same settings (useful for formatting rules for example).
   13.36 +Any value defined here will override the pom.xml file value but is only applicable to the current project.
   13.37 +-->
   13.38 +        <netbeans.compile.on.save>none</netbeans.compile.on.save>
   13.39 +    </properties>
   13.40 +</project-shared-configuration>
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/javaquery/demo-twitter/nbactions.xml	Mon Apr 08 19:33:08 2013 +0200
    14.3 @@ -0,0 +1,29 @@
    14.4 +<?xml version="1.0" encoding="UTF-8"?>
    14.5 +<!--
    14.6 +
    14.7 +    Back 2 Browser Bytecode Translator
    14.8 +    Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    14.9 +
   14.10 +    This program is free software: you can redistribute it and/or modify
   14.11 +    it under the terms of the GNU General Public License as published by
   14.12 +    the Free Software Foundation, version 2 of the License.
   14.13 +
   14.14 +    This program is distributed in the hope that it will be useful,
   14.15 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
   14.16 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14.17 +    GNU General Public License for more details.
   14.18 +
   14.19 +    You should have received a copy of the GNU General Public License
   14.20 +    along with this program. Look for COPYING file in the top folder.
   14.21 +    If not, see http://opensource.org/licenses/GPL-2.0.
   14.22 +
   14.23 +-->
   14.24 +<actions>
   14.25 +    <action>
   14.26 +        <actionName>run</actionName>
   14.27 +        <goals>
   14.28 +            <goal>process-classes</goal>
   14.29 +            <goal>org.apidesign.bck2brwsr:mojo:0.6-SNAPSHOT:brwsr</goal>
   14.30 +        </goals>
   14.31 +    </action>
   14.32 +</actions>
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/javaquery/demo-twitter/pom.xml	Mon Apr 08 19:33:08 2013 +0200
    15.3 @@ -0,0 +1,137 @@
    15.4 +<?xml version="1.0" encoding="UTF-8"?>
    15.5 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    15.6 +  <modelVersion>4.0.0</modelVersion>
    15.7 +  <parent>
    15.8 +    <artifactId>javaquery</artifactId>
    15.9 +    <groupId>org.apidesign.bck2brwsr</groupId>
   15.10 +    <version>0.6-SNAPSHOT</version>
   15.11 +  </parent>
   15.12 +
   15.13 +  <groupId>org.apidesign.bck2brwsr</groupId>
   15.14 +  <artifactId>demo-twitter</artifactId>
   15.15 +  <version>0.6-SNAPSHOT</version>
   15.16 +  <packaging>jar</packaging>
   15.17 +
   15.18 +  <name>Bck2Brwsr's Twttr</name>
   15.19 +  <description>
   15.20 +      Rewrite of knockoutjs example to use model written in Java and
   15.21 +      execute using Bck2Brwsr virtual machine.
   15.22 +  </description>
   15.23 +
   15.24 +  <repositories>
   15.25 +      <repository>
   15.26 +          <id>java.net</id>
   15.27 +          <name>Java.net</name>
   15.28 +          <url>https://maven.java.net/content/repositories/releases/</url>
   15.29 +          <snapshots>
   15.30 +          </snapshots>
   15.31 +      </repository>
   15.32 +      <repository>
   15.33 +          <id>netbeans</id>
   15.34 +          <name>NetBeans</name>
   15.35 +          <url>http://bits.netbeans.org/maven2/</url>
   15.36 +      </repository>
   15.37 +  </repositories>
   15.38 +  <pluginRepositories>
   15.39 +      <pluginRepository>
   15.40 +          <id>java.net</id>
   15.41 +          <name>Java.net</name>
   15.42 +          <url>https://maven.java.net/content/repositories/releases/</url>
   15.43 +          <snapshots>
   15.44 +          </snapshots>
   15.45 +      </pluginRepository>
   15.46 +  </pluginRepositories>
   15.47 +
   15.48 +  <properties>
   15.49 +    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   15.50 +    <bck2brwsr.obfuscationlevel>FULL</bck2brwsr.obfuscationlevel>
   15.51 +  </properties>
   15.52 +  <build>
   15.53 +      <plugins>
   15.54 +            <plugin>
   15.55 +                <groupId>org.apidesign.bck2brwsr</groupId>
   15.56 +                <artifactId>mojo</artifactId>
   15.57 +                <version>0.6-SNAPSHOT</version>
   15.58 +                <executions>
   15.59 +                    <execution>
   15.60 +                        <goals>
   15.61 +                            <goal>brwsr</goal>
   15.62 +                            <goal>j2js</goal>
   15.63 +                        </goals>
   15.64 +                    </execution>
   15.65 +                </executions>
   15.66 +                <configuration>
   15.67 +                    <startpage>org/apidesign/bck2brwsr/demo/twitter/index.html</startpage>
   15.68 +                    <javascript>${project.build.directory}/bck2brwsr.js</javascript>
   15.69 +                    <obfuscation>${bck2brwsr.obfuscationlevel}</obfuscation>
   15.70 +                </configuration>
   15.71 +            </plugin>
   15.72 +         <plugin>
   15.73 +            <groupId>org.apache.maven.plugins</groupId>
   15.74 +            <artifactId>maven-compiler-plugin</artifactId>
   15.75 +            <version>2.3.2</version>
   15.76 +            <configuration>
   15.77 +               <source>1.7</source>
   15.78 +               <target>1.7</target>
   15.79 +            </configuration>
   15.80 +         </plugin>
   15.81 +         <plugin>
   15.82 +             <groupId>org.apache.maven.plugins</groupId>
   15.83 +             <artifactId>maven-jar-plugin</artifactId>
   15.84 +             <version>2.4</version>
   15.85 +             <configuration>
   15.86 +                 <archive>
   15.87 +                     <manifest>
   15.88 +                         <addClasspath>true</addClasspath>
   15.89 +                         <classpathPrefix>lib/</classpathPrefix>
   15.90 +                     </manifest>
   15.91 +                 </archive>
   15.92 +             </configuration>
   15.93 +         </plugin>
   15.94 +         <plugin>
   15.95 +             <artifactId>maven-assembly-plugin</artifactId>
   15.96 +             <version>2.4</version>
   15.97 +             <executions>
   15.98 +                 <execution>
   15.99 +                     <id>distro-assembly</id>
  15.100 +                     <phase>package</phase>
  15.101 +                     <goals>
  15.102 +                         <goal>single</goal>
  15.103 +                     </goals>
  15.104 +                     <configuration>
  15.105 +                         <descriptors>
  15.106 +                             <descriptor>bck2brwsr-assembly.xml</descriptor>
  15.107 +                         </descriptors>
  15.108 +                     </configuration>
  15.109 +                 </execution>
  15.110 +             </executions>                
  15.111 +         </plugin>      
  15.112 +      </plugins>
  15.113 +  </build>
  15.114 +
  15.115 +  <dependencies>
  15.116 +    <dependency>
  15.117 +      <groupId>org.apidesign.bck2brwsr</groupId>
  15.118 +      <artifactId>emul</artifactId>
  15.119 +      <version>0.6-SNAPSHOT</version>
  15.120 +      <classifier>rt</classifier>
  15.121 +    </dependency>
  15.122 +    <dependency>
  15.123 +      <groupId>org.apidesign.bck2brwsr</groupId>
  15.124 +      <artifactId>javaquery.api</artifactId>
  15.125 +      <version>0.6-SNAPSHOT</version>
  15.126 +    </dependency>
  15.127 +    <dependency>
  15.128 +      <groupId>org.testng</groupId>
  15.129 +      <artifactId>testng</artifactId>
  15.130 +      <version>6.5.2</version>
  15.131 +      <scope>test</scope>
  15.132 +    </dependency>
  15.133 +    <dependency>
  15.134 +      <groupId>org.apidesign.bck2brwsr</groupId>
  15.135 +      <artifactId>vmtest</artifactId>
  15.136 +      <version>0.6-SNAPSHOT</version>
  15.137 +      <scope>test</scope>
  15.138 +    </dependency>
  15.139 +  </dependencies>
  15.140 +</project>
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java	Mon Apr 08 19:33:08 2013 +0200
    16.3 @@ -0,0 +1,194 @@
    16.4 +/**
    16.5 + * Back 2 Browser Bytecode Translator
    16.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    16.7 + *
    16.8 + * This program is free software: you can redistribute it and/or modify
    16.9 + * it under the terms of the GNU General Public License as published by
   16.10 + * the Free Software Foundation, version 2 of the License.
   16.11 + *
   16.12 + * This program is distributed in the hope that it will be useful,
   16.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   16.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16.15 + * GNU General Public License for more details.
   16.16 + *
   16.17 + * You should have received a copy of the GNU General Public License
   16.18 + * along with this program. Look for COPYING file in the top folder.
   16.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
   16.20 + */
   16.21 +package org.apidesign.bck2brwsr.demo.twitter;
   16.22 +
   16.23 +import java.util.Arrays;
   16.24 +import java.util.List;
   16.25 +import org.apidesign.bck2brwsr.htmlpage.api.*;
   16.26 +import org.apidesign.bck2brwsr.htmlpage.api.Page;
   16.27 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
   16.28 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
   16.29 +
   16.30 +/** Controller class for access to Twitter.
   16.31 + * 
   16.32 + * @author Jaroslav Tulach
   16.33 + */
   16.34 +@Page(xhtml="index.html", className="TwitterModel", properties={
   16.35 +    @Property(name="savedLists", type=TwitterClient.Twttrs.class, array = true),
   16.36 +    @Property(name="activeTweetersName", type=String.class),
   16.37 +    @Property(name="activeTweeters", type=String.class, array = true),
   16.38 +    @Property(name="userNameToAdd", type=String.class),
   16.39 +    @Property(name="currentTweets", type=TwitterClient.Twt.class, array = true)
   16.40 +})
   16.41 +public class TwitterClient {
   16.42 +    @Model(className = "Tweeters", properties = {
   16.43 +        @Property(name="name", type = String.class),
   16.44 +        @Property(name="userNames", type = String.class, array = true)
   16.45 +    })
   16.46 +    static class Twttrs {
   16.47 +    }
   16.48 +    @Model(className = "Tweet", properties = {
   16.49 +        @Property(name = "from_user", type = String.class),
   16.50 +        @Property(name = "from_user_id", type = int.class),
   16.51 +        @Property(name = "profile_image_url", type = String.class),
   16.52 +        @Property(name = "text", type = String.class),
   16.53 +        @Property(name = "created_at", type = String.class),
   16.54 +    })
   16.55 +    static final class Twt {
   16.56 +        @ComputedProperty static String html(String text) {
   16.57 +            StringBuilder sb = new StringBuilder(320);
   16.58 +            for (int pos = 0;;) {
   16.59 +                int http = text.indexOf("http", pos);
   16.60 +                if (http == -1) {
   16.61 +                    sb.append(text.substring(pos));
   16.62 +                    return sb.toString();
   16.63 +                }
   16.64 +                int spc = text.indexOf(' ', http);
   16.65 +                if (spc == -1) {
   16.66 +                    spc = text.length();
   16.67 +                }
   16.68 +                sb.append(text.substring(pos, http));
   16.69 +                String url = text.substring(http, spc);
   16.70 +                sb.append("<a href='").append(url).append("'>").append(url).append("</a>");
   16.71 +                pos = spc;
   16.72 +            }
   16.73 +        }
   16.74 +        
   16.75 +        @ComputedProperty static String userUrl(String from_user) {
   16.76 +            return "http://twitter.com/" + from_user;
   16.77 +        }
   16.78 +    }
   16.79 +    @Model(className = "TwitterQuery", properties = {
   16.80 +        @Property(array = true, name = "results", type = Twt.class)
   16.81 +    })
   16.82 +    public static final class TwttrQr {
   16.83 +    }
   16.84 +    
   16.85 +    @OnReceive(url="{root}/search.json?{query}&callback={me}", jsonp="me")
   16.86 +    static void queryTweets(TwitterModel page, TwitterQuery q) {
   16.87 +        page.getCurrentTweets().clear();
   16.88 +        page.getCurrentTweets().addAll(q.getResults());
   16.89 +    }
   16.90 +    
   16.91 +    @OnPropertyChange("activeTweetersName")
   16.92 +    static void changeTweetersList(TwitterModel model) {
   16.93 +        Tweeters people = findByName(model.getSavedLists(), model.getActiveTweetersName());        
   16.94 +        model.getActiveTweeters().clear();
   16.95 +        model.getActiveTweeters().addAll(people.getUserNames());
   16.96 +    }
   16.97 +    
   16.98 +    @OnPropertyChange({ "activeTweeters", "activeTweetersCount" })
   16.99 +    static void refreshTweets(TwitterModel model) {
  16.100 +        StringBuilder sb = new StringBuilder();
  16.101 +        sb.append("rpp=25&q=");
  16.102 +        String sep = "";
  16.103 +        for (String p : model.getActiveTweeters()) {
  16.104 +            sb.append(sep);
  16.105 +            sb.append("from:");
  16.106 +            sb.append(p);
  16.107 +            sep = " OR ";
  16.108 +        }
  16.109 +        model.queryTweets("http://search.twitter.com", sb.toString());
  16.110 +    }
  16.111 +    
  16.112 +    static {
  16.113 +        final TwitterModel model = new TwitterModel();
  16.114 +        final List<Tweeters> svdLst = model.getSavedLists();
  16.115 +        svdLst.add(newTweeters("API Design", "JaroslavTulach"));
  16.116 +        svdLst.add(newTweeters("Celebrities", "JohnCleese", "MCHammer", "StephenFry", "algore", "StevenSanderson"));
  16.117 +        svdLst.add(newTweeters("Microsoft people", "BillGates", "shanselman", "ScottGu"));
  16.118 +        svdLst.add(newTweeters("NetBeans", "GeertjanW","monacotoni", "NetBeans", "petrjiricka"));
  16.119 +        svdLst.add(newTweeters("Tech pundits", "Scobleizer", "LeoLaporte", "techcrunch", "BoingBoing", "timoreilly", "codinghorror"));
  16.120 +
  16.121 +        model.setActiveTweetersName("NetBeans");
  16.122 +
  16.123 +        model.applyBindings();
  16.124 +    }
  16.125 +    
  16.126 +    @ComputedProperty
  16.127 +    static boolean hasUnsavedChanges(List<String> activeTweeters, List<Tweeters> savedLists, String activeTweetersName) {
  16.128 +        Tweeters tw = findByName(savedLists, activeTweetersName);
  16.129 +        if (activeTweeters == null) {
  16.130 +            return false;
  16.131 +        }
  16.132 +        return !tw.getUserNames().equals(activeTweeters);
  16.133 +    }
  16.134 +    
  16.135 +    @ComputedProperty
  16.136 +    static int activeTweetersCount(List<String> activeTweeters) {
  16.137 +        return activeTweeters.size();
  16.138 +    }
  16.139 +    
  16.140 +    @ComputedProperty
  16.141 +    static boolean userNameToAddIsValid(
  16.142 +        String userNameToAdd, String activeTweetersName, List<Tweeters> savedLists, List<String> activeTweeters
  16.143 +    ) {
  16.144 +        return userNameToAdd != null && 
  16.145 +            userNameToAdd.matches("[a-zA-Z0-9_]{1,15}") &&
  16.146 +            !activeTweeters.contains(userNameToAdd);
  16.147 +    }
  16.148 +    
  16.149 +    @OnFunction
  16.150 +    static void deleteList(TwitterModel model) {
  16.151 +        final List<Tweeters> sl = model.getSavedLists();
  16.152 +        sl.remove(findByName(sl, model.getActiveTweetersName()));
  16.153 +        if (sl.isEmpty()) {
  16.154 +            final Tweeters t = new Tweeters();
  16.155 +            t.setName("New");
  16.156 +            sl.add(t);
  16.157 +        }
  16.158 +        model.setActiveTweetersName(sl.get(0).getName());
  16.159 +    }
  16.160 +    
  16.161 +    @OnFunction
  16.162 +    static void saveChanges(TwitterModel model) {
  16.163 +        Tweeters t = findByName(model.getSavedLists(), model.getActiveTweetersName());
  16.164 +        int indx = model.getSavedLists().indexOf(t);
  16.165 +        if (indx != -1) {
  16.166 +            t.setName(model.getActiveTweetersName());
  16.167 +            t.getUserNames().clear();
  16.168 +            t.getUserNames().addAll(model.getActiveTweeters());
  16.169 +        }
  16.170 +    }
  16.171 +    
  16.172 +    @OnFunction
  16.173 +    static void addUser(TwitterModel model) {
  16.174 +        String n = model.getUserNameToAdd();
  16.175 +        model.getActiveTweeters().add(n);
  16.176 +    }
  16.177 +    @OnFunction
  16.178 +    static void removeUser(String data, TwitterModel model) {
  16.179 +        model.getActiveTweeters().remove(data);
  16.180 +    }
  16.181 +    
  16.182 +    private static Tweeters findByName(List<Tweeters> list, String name) {
  16.183 +        for (Tweeters l : list) {
  16.184 +            if (l.getName() != null && l.getName().equals(name)) {
  16.185 +                return l;
  16.186 +            }
  16.187 +        }
  16.188 +        return list.isEmpty() ? new Tweeters() : list.get(0);
  16.189 +    }
  16.190 +    
  16.191 +    private static Tweeters newTweeters(String listName, String... userNames) {
  16.192 +        Tweeters t = new Tweeters();
  16.193 +        t.setName(listName);
  16.194 +        t.getUserNames().addAll(Arrays.asList(userNames));
  16.195 +        return t;
  16.196 +    }
  16.197 +}
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/index.html	Mon Apr 08 19:33:08 2013 +0200
    17.3 @@ -0,0 +1,99 @@
    17.4 +<?xml version="1.0" encoding="UTF-8"?>
    17.5 +<!--
    17.6 +
    17.7 +    Back 2 Browser Bytecode Translator
    17.8 +    Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    17.9 +
   17.10 +    This program is free software: you can redistribute it and/or modify
   17.11 +    it under the terms of the GNU General Public License as published by
   17.12 +    the Free Software Foundation, version 2 of the License.
   17.13 +
   17.14 +    This program is distributed in the hope that it will be useful,
   17.15 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
   17.16 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17.17 +    GNU General Public License for more details.
   17.18 +
   17.19 +    You should have received a copy of the GNU General Public License
   17.20 +    along with this program. Look for COPYING file in the top folder.
   17.21 +    If not, see http://opensource.org/licenses/GPL-2.0.
   17.22 +
   17.23 +-->
   17.24 +
   17.25 +<!--
   17.26 +    Copied from knockout.js Twitter example:
   17.27 +    http://knockoutjs.com/examples/twitter.html
   17.28 +-->
   17.29 +
   17.30 +<!DOCTYPE html>
   17.31 +<html xmlns="http://www.w3.org/1999/xhtml">
   17.32 +    <head>
   17.33 +        <title>Bck2Brwsr's Twitter</title>
   17.34 +    </head>
   17.35 +    <body>
   17.36 +        <link href='twitterExample.css' rel='Stylesheet' ></link>
   17.37 +        
   17.38 +        <style type='text/css'>
   17.39 +           .liveExample select { height: 1.7em; }
   17.40 +           .liveExample button { height: 2em; }
   17.41 +        </style>
   17.42 +        
   17.43 +        
   17.44 +        <h2>Bck2Brwsr's Twitter</h2>
   17.45 +        
   17.46 +        <p>
   17.47 +        This code based on original <a href="http://knockoutjs.com/examples/twitter.html">knockout.js Twitter example</a> and
   17.48 +        uses almost unmodified HTML code. It just changes the model. It 
   17.49 +        is written in Java language and it is executed using <a href="http://bck2brwsr.apidesign.org">Bck2Brwsr</a>
   17.50 +        virtual machine. The Java source code has about 190 lines and is available 
   17.51 +        <a href="http://source.apidesign.org/hg/bck2brwsr/file/7fc6b7e9c982/javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java">here</a>
   17.52 +        - in fact it may even be more dense than the original JavaScript model.
   17.53 +        </p>
   17.54 +        
   17.55 +        <div class='liveExample'>
   17.56 +            <div class='configuration'>
   17.57 +                <div class='listChooser'>
   17.58 +                    <button data-bind='click: deleteList, enable: activeTweetersName'>Delete</button>
   17.59 +                    <button data-bind='click: saveChanges, enable: hasUnsavedChanges'>Save</button> 
   17.60 +                    <select data-bind='options: savedLists, optionsValue: "name", value: activeTweetersName'> </select>
   17.61 +                </div>
   17.62 +
   17.63 +                <p>Currently viewing <span data-bind='text: activeTweetersCount'> </span> user(s):</p>
   17.64 +                <div class='currentUsers' >
   17.65 +                    <ul data-bind='foreach: activeTweeters'>
   17.66 +                        <li>
   17.67 +                            <button data-bind='click: $root.removeUser'>Remove</button>
   17.68 +                            <div data-bind='text: $data'> </div>
   17.69 +                        </li>
   17.70 +                    </ul>
   17.71 +                </div>
   17.72 +
   17.73 +                <form data-bind='submit: addUser'>
   17.74 +                    <label>Add user:</label>
   17.75 +                    <input data-bind='value: userNameToAdd, valueUpdate: "keyup", css: { invalid: !userNameToAddIsValid() }' />
   17.76 +                    <button data-bind='enable: userNameToAddIsValid' type='submit'>Add</button>
   17.77 +                </form>
   17.78 +            </div>
   17.79 +            <div class='tweets'>
   17.80 +                <div class='loadingIndicator'>Loading...</div>
   17.81 +                <table data-bind='foreach: currentTweets' width='100%'>
   17.82 +                    <tr>
   17.83 +                        <td><img data-bind='attr: { src: profile_image_url }' /></td>
   17.84 +                        <td>
   17.85 +                            <a class='twitterUser' data-bind='attr: { href: userUrl }, text: from_user'> </a>
   17.86 +                            <span data-bind='html: html'> </span>
   17.87 +                            <div class='tweetInfo' data-bind='text: created_at'> </div>
   17.88 +                        </td>
   17.89 +                    </tr>
   17.90 +                </table>
   17.91 +            </div>
   17.92 +        </div>
   17.93 +        
   17.94 +        <script src="bck2brwsr.js"></script>
   17.95 +        <script type="text/javascript">
   17.96 +            var vm = bck2brwsr('demo-twitter-0.6-SNAPSHOT.jar');
   17.97 +            vm.loadClass('org.apidesign.bck2brwsr.demo.twitter.TwitterClient');
   17.98 +        </script>
   17.99 +
  17.100 +
  17.101 +    </body>
  17.102 +</html>
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/twitterExample.css	Mon Apr 08 19:33:08 2013 +0200
    18.3 @@ -0,0 +1,50 @@
    18.4 +/**
    18.5 + * Back 2 Browser Bytecode Translator
    18.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    18.7 + *
    18.8 + * This program is free software: you can redistribute it and/or modify
    18.9 + * it under the terms of the GNU General Public License as published by
   18.10 + * the Free Software Foundation, version 2 of the License.
   18.11 + *
   18.12 + * This program is distributed in the hope that it will be useful,
   18.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   18.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18.15 + * GNU General Public License for more details.
   18.16 + *
   18.17 + * You should have received a copy of the GNU General Public License
   18.18 + * along with this program. Look for COPYING file in the top folder.
   18.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
   18.20 + */
   18.21 +
   18.22 +/*
   18.23 +    Copied from knockout.js Twitter example:
   18.24 +    http://knockoutjs.com/examples/twitter.html
   18.25 +*/
   18.26 +
   18.27 +.configuration, .tweets, .tweets td { font-family: Verdana; font-size: 13px; }
   18.28 +.configuration { background-color: #DEDEDE; border: 2px solid gray; float:left; height: 40em; width: 40%; padding: 0.5em; border-right-width:0; }
   18.29 +.tweets { width: 55%; border: 2px solid gray; height: 40em; overflow: scroll; overflow-x: hidden; background-color: Black; color: White; padding: 0.5em; position: relative; }
   18.30 +.tweets table { border-width: 0;}
   18.31 +.tweets tr { vertical-align: top; }
   18.32 +.tweets td { padding: 0.4em 0.3em 1em 0.4em; border-width: 0; }
   18.33 +.tweets img { width: 4em; }
   18.34 +.tweetInfo { color: Gray; font-size: 0.9em; }
   18.35 +.twitterUser { color: #77AAFF; text-decoration: none; font-size: 1.1em; font-weight: bold; }
   18.36 +input.invalid { border: 1px solid red !important; background-color: #FFAAAA !important; }
   18.37 +
   18.38 +.listChooser select, .listChooser button { vertical-align:top; }
   18.39 +.listChooser select { width: 60%; font-size:1.2em; height:1.4em; }
   18.40 +.listChooser button { width: 19%; height:1.68em; float:right; }
   18.41 +
   18.42 +.currentUsers { height: 28em; overflow-y: auto; overflow-x: hidden; }
   18.43 +.currentUsers button { float: right; height: 2.5em; margin: 0.1em; padding-left: 1em; padding-right: 1em; }
   18.44 +.currentUsers ul, .configuration li { list-style: none; margin: 0; padding: 0 }
   18.45 +.currentUsers li { height: 2.4em; font-size: 1.2em; background-color: #A7D0E3; border: 1px solid gray; margin-bottom: 0.3em; -webkit-border-radius: 5px; -moz-border-radius: 5px; -webkit-box-shadow: 0 0.2em 0.5em gray; -moz-box-shadow: 0 0.2em 0.5em gray; }
   18.46 +.currentUsers li div { padding: 0.6em; }
   18.47 +.currentUsers li:hover { background-color: #EEC; }
   18.48 +
   18.49 +.configuration form label { width: 25%; display: inline-block; text-align:right; overflow: hidden; }
   18.50 +.configuration form input { width:40%; font-size: 1.3em; border:1px solid silver; background-color: White; padding: 0.1em; }
   18.51 +.configuration form button { width: 20%; margin-left: 0.3em; height: 2em; }
   18.52 +
   18.53 +.loadingIndicator { position: absolute; top: 0.1em; left: 0.1em; font: 0.8em Arial; background-color: #229; color: White; padding: 0.2em 0.5em 0.2em 0.5em; display: none; }
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClientTest.java	Mon Apr 08 19:33:08 2013 +0200
    19.3 @@ -0,0 +1,67 @@
    19.4 +/**
    19.5 + * Back 2 Browser Bytecode Translator
    19.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    19.7 + *
    19.8 + * This program is free software: you can redistribute it and/or modify
    19.9 + * it under the terms of the GNU General Public License as published by
   19.10 + * the Free Software Foundation, version 2 of the License.
   19.11 + *
   19.12 + * This program is distributed in the hope that it will be useful,
   19.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   19.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19.15 + * GNU General Public License for more details.
   19.16 + *
   19.17 + * You should have received a copy of the GNU General Public License
   19.18 + * along with this program. Look for COPYING file in the top folder.
   19.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
   19.20 + */
   19.21 +package org.apidesign.bck2brwsr.demo.twitter;
   19.22 +
   19.23 +import java.util.List;
   19.24 +import static org.testng.Assert.*;
   19.25 +import org.testng.annotations.BeforeMethod;
   19.26 +import org.testng.annotations.Test;
   19.27 +
   19.28 +/** We can unit test the TwitterModel smoothly.
   19.29 + *
   19.30 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   19.31 + */
   19.32 +public class TwitterClientTest {
   19.33 +    private TwitterModel model;
   19.34 +    
   19.35 +
   19.36 +    @BeforeMethod
   19.37 +    public void initModel() {
   19.38 +        model = new TwitterModel().applyBindings();
   19.39 +    }
   19.40 +
   19.41 +    @Test public void testIsValidToAdd() {
   19.42 +        model.setUserNameToAdd("Joe");
   19.43 +        Tweeters t = new Tweeters();
   19.44 +        t.setName("test");
   19.45 +        model.getSavedLists().add(t);
   19.46 +        model.setActiveTweetersName("test");
   19.47 +        
   19.48 +        assertTrue(model.isUserNameToAddIsValid(), "Joe is OK");
   19.49 +        TwitterClient.addUser(model);
   19.50 +        assertFalse(model.isUserNameToAddIsValid(), "Can't add Joe for the 2nd time");
   19.51 +        assertEquals(t.getUserNames().size(), 0, "Original tweeters list remains empty");
   19.52 +        
   19.53 +        List<String> mod = model.getActiveTweeters();
   19.54 +        assertTrue(model.isHasUnsavedChanges(), "We have modifications");
   19.55 +        assertEquals(mod.size(), 1, "One element in the list");
   19.56 +        assertEquals(mod.get(0), "Joe", "Its name is Joe");
   19.57 +        
   19.58 +        assertSame(model.getActiveTweeters(), mod, "Editing list is the modified one");
   19.59 +        
   19.60 +        TwitterClient.saveChanges(model);
   19.61 +        assertFalse(model.isHasUnsavedChanges(), "Does not have anything to save");
   19.62 +        
   19.63 +        assertSame(model.getActiveTweeters(), mod, "Still editing the old modified one");
   19.64 +    }
   19.65 +    
   19.66 +    @Test public void httpAtTheEnd() {
   19.67 +        String res = TwitterClient.Twt.html("Ahoj http://kuk");
   19.68 +        assertEquals(res, "Ahoj <a href='http://kuk'>http://kuk</a>");
   19.69 +    }
   19.70 +}
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterProtocolTest.java	Mon Apr 08 19:33:08 2013 +0200
    20.3 @@ -0,0 +1,94 @@
    20.4 +/**
    20.5 + * Back 2 Browser Bytecode Translator
    20.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    20.7 + *
    20.8 + * This program is free software: you can redistribute it and/or modify
    20.9 + * it under the terms of the GNU General Public License as published by
   20.10 + * the Free Software Foundation, version 2 of the License.
   20.11 + *
   20.12 + * This program is distributed in the hope that it will be useful,
   20.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   20.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20.15 + * GNU General Public License for more details.
   20.16 + *
   20.17 + * You should have received a copy of the GNU General Public License
   20.18 + * along with this program. Look for COPYING file in the top folder.
   20.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
   20.20 + */
   20.21 +package org.apidesign.bck2brwsr.demo.twitter;
   20.22 +
   20.23 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
   20.24 +import org.apidesign.bck2brwsr.vmtest.Http;
   20.25 +import org.apidesign.bck2brwsr.vmtest.VMTest;
   20.26 +import org.testng.annotations.Factory;
   20.27 +
   20.28 +/**
   20.29 + *
   20.30 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   20.31 + */
   20.32 +public class TwitterProtocolTest {
   20.33 +    private TwitterModel page;
   20.34 +    @Http(@Http.Resource(
   20.35 +        path = "/search.json",
   20.36 +        mimeType = "application/json",
   20.37 +        parameters = {"callback"},
   20.38 +        content = "$0({\"completed_in\":0.04,\"max_id\":320055706885689344,\"max_id_str\""
   20.39 +        + ":\"320055706885689344\",\"page\":1,\"query\":\"from%3AJaroslavTulach\",\"refresh_url\":"
   20.40 +        + "\"?since_id=320055706885689344&q=from%3AJaroslavTulach\","
   20.41 +        + "\"results\":[{\"created_at\":\"Fri, 05 Apr 2013 06:10:01 +0000\","
   20.42 +        + "\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,\"from_user_id_str\":"
   20.43 +        + "\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,\"id\":320055706885689344,"
   20.44 +        + "\"id_str\":\"320055706885689344\",\"iso_language_code\":\"en\",\"metadata\":{\"result_type\":"
   20.45 +        + "\"recent\"},\"profile_image_url\":\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
   20.46 +        + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
   20.47 +        + "\"source\":\"&lt;a href=&quot;http:\\/\\/twitter.com\\/&quot;&gt;web&lt;\\/a&gt;\",\"text\":"
   20.48 +        + "\"@tom_enebo Amzng! Not that I would like #ruby, but I am really glad you guys stabilized the plugin + "
   20.49 +        + "made it work in #netbeans 7.3! Gd wrk.\",\"to_user\":\"tom_enebo\",\"to_user_id\":14498747,"
   20.50 +        + "\"to_user_id_str\":\"14498747\",\"to_user_name\":\"tom_enebo\",\"in_reply_to_status_id\":319832359509839872,"
   20.51 +        + "\"in_reply_to_status_id_str\":\"319832359509839872\"},{\"created_at\":\"Thu, 04 Apr 2013 07:33:06 +0000\","
   20.52 +        + "\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,\"from_user_id_str\":"
   20.53 +        + "\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,\"id\":319714227088678913,"
   20.54 +        + "\"id_str\":\"319714227088678913\",\"iso_language_code\":\"en\",\"metadata\":{\"result_type\":"
   20.55 +        + "\"recent\"},\"profile_image_url\":\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
   20.56 +        + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
   20.57 +        + "\"source\":\"&lt;a href=&quot;http:\\/\\/twitter.com\\/&quot;&gt;web&lt;\\/a&gt;\",\"text\":"
   20.58 +        + "\"RT @drkrab: At #erlangfactory @joerl: Frameworks grow in complexity until nobody can use them.\"},"
   20.59 +        + "{\"created_at\":\"Tue, 02 Apr 2013 07:44:34 +0000\",\"from_user\":\"JaroslavTulach\","
   20.60 +        + "\"from_user_id\":420944648,\"from_user_id_str\":\"420944648\",\"from_user_name\":\"Jaroslav Tulach\","
   20.61 +        + "\"geo\":null,\"id\":318992336145248256,\"id_str\":\"318992336145248256\",\"iso_language_code\":\"en\","
   20.62 +        + "\"metadata\":{\"result_type\":\"recent\"},\"profile_image_url\":"
   20.63 +        + "\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
   20.64 +        + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
   20.65 +        + "\"source\":\"&lt;a href=&quot;http:\\/\\/twitter.com\\/&quot;&gt;web&lt;\\/a&gt;\",\"text\":"
   20.66 +        + "\"Twitter renamed to twttr http:\\/\\/t.co\\/tqaN4T1xlZ - good, I don't have to rename #bck2brwsr!\"},"
   20.67 +        + "{\"created_at\":\"Sun, 31 Mar 2013 03:52:04 +0000\",\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,"
   20.68 +        + "\"from_user_id_str\":\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,"
   20.69 +        + "\"id\":318209051223789568,\"id_str\":\"318209051223789568\",\"iso_language_code\":\"en\",\"metadata\":"
   20.70 +        + "{\"result_type\":\"recent\"},\"profile_image_url\":"
   20.71 +        + "\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
   20.72 +        + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
   20.73 +        + "\"source\":\"&lt;a href=&quot;http:\\/\\/twitter.com\\/&quot;&gt;web&lt;\\/a&gt;\",\"text\":"
   20.74 +        + "\"Math proofs without words. Ingenious: http:\\/\\/t.co\\/sz7yVbfpGw\"}],\"results_per_page\":100,"
   20.75 +        + "\"since_id\":0,\"since_id_str\":\"0\"})"
   20.76 +    ))
   20.77 +    @BrwsrTest public void readFromTwttr() throws InterruptedException {
   20.78 +        if (page == null) {
   20.79 +            page = new TwitterModel();
   20.80 +            page.applyBindings();
   20.81 +            page.queryTweets("", "q=xyz");
   20.82 +        }
   20.83 +
   20.84 +        if (page.getCurrentTweets().isEmpty()) {
   20.85 +            throw new InterruptedException();
   20.86 +        }
   20.87 +
   20.88 +        assert 4 == page.getCurrentTweets().size() : "Four tweets: " + page.getCurrentTweets();
   20.89 +        
   20.90 +        String firstDate = page.getCurrentTweets().get(0).getCreated_at();
   20.91 +        assert "Fri, 05 Apr 2013 06:10:01 +0000".equals(firstDate) : "Date is OK: " + firstDate;
   20.92 +    }
   20.93 +    
   20.94 +    @Factory public static Object[] create() {
   20.95 +        return VMTest.create(TwitterProtocolTest.class);
   20.96 +    }
   20.97 +}
    21.1 --- a/javaquery/pom.xml	Wed Apr 03 13:43:22 2013 +0200
    21.2 +++ b/javaquery/pom.xml	Mon Apr 08 19:33:08 2013 +0200
    21.3 @@ -15,5 +15,6 @@
    21.4          <module>api</module>
    21.5          <module>demo-calculator</module>
    21.6          <module>demo-calculator-dynamic</module>
    21.7 -    </modules>
    21.8 -</project>
    21.9 +    <module>demo-twitter</module>
   21.10 +  </modules>
   21.11 +</project>
   21.12 \ No newline at end of file
    22.1 --- a/rt/emul/mini/src/main/java/java/lang/Class.java	Wed Apr 03 13:43:22 2013 +0200
    22.2 +++ b/rt/emul/mini/src/main/java/java/lang/Class.java	Mon Apr 08 19:33:08 2013 +0200
    22.3 @@ -1252,6 +1252,7 @@
    22.4      }
    22.5      
    22.6      @JavaScriptBody(args = { "sig" }, body = 
    22.7 +        "if (!sig) sig = '[Ljava/lang/Object;';\n" +
    22.8          "var c = Array[sig];\n" +
    22.9          "if (c) return c;\n" +
   22.10          "c = vm.java_lang_Class(true);\n" +
    23.1 --- a/rt/emul/mini/src/main/java/java/lang/String.java	Wed Apr 03 13:43:22 2013 +0200
    23.2 +++ b/rt/emul/mini/src/main/java/java/lang/String.java	Mon Apr 08 19:33:08 2013 +0200
    23.3 @@ -2220,9 +2220,19 @@
    23.4       *         <code>replacement</code> is <code>null</code>.
    23.5       * @since 1.5
    23.6       */
    23.7 -    public String replace(CharSequence target, CharSequence replacement) {
    23.8 -        throw new UnsupportedOperationException("This one should be supported, but without dep on rest of regexp");
    23.9 -    }
   23.10 +    @JavaScriptBody(args = { "target", "replacement" }, body = 
   23.11 +          "var s = this.toString();\n"
   23.12 +        + "target = target.toString();\n"
   23.13 +        + "replacement = replacement.toString();\n"
   23.14 +        + "for (;;) {\n"
   23.15 +        + "  var ret = s.replace(target, replacement);\n"
   23.16 +        + "  if (ret === s) {\n"
   23.17 +        + "    return ret;\n"
   23.18 +        + "  }\n"
   23.19 +        + "  s = ret;\n"
   23.20 +        + "}"
   23.21 +    )
   23.22 +    public native String replace(CharSequence target, CharSequence replacement);
   23.23  
   23.24      /**
   23.25       * Splits this string around matches of the given
    24.1 --- a/rt/emul/mini/src/main/java/java/lang/reflect/Method.java	Wed Apr 03 13:43:22 2013 +0200
    24.2 +++ b/rt/emul/mini/src/main/java/java/lang/reflect/Method.java	Mon Apr 08 19:33:08 2013 +0200
    24.3 @@ -501,8 +501,8 @@
    24.4          throws IllegalAccessException, IllegalArgumentException,
    24.5             InvocationTargetException
    24.6      {
    24.7 -        final boolean isStatic = (getModifiers() & Modifier.STATIC) == 0;
    24.8 -        if (isStatic && obj == null) {
    24.9 +        final boolean nonStatic = (getModifiers() & Modifier.STATIC) == 0;
   24.10 +        if (nonStatic && obj == null) {
   24.11              throw new NullPointerException();
   24.12          }
   24.13          Class[] types = getParameterTypes();
   24.14 @@ -517,7 +517,7 @@
   24.15                  }
   24.16              }
   24.17          }
   24.18 -        Object res = invoke0(isStatic, this, obj, args);
   24.19 +        Object res = invokeTry(nonStatic, this, obj, args);
   24.20          if (getReturnType().isPrimitive()) {
   24.21              res = fromPrimitive(getReturnType(), res);
   24.22          }
   24.23 @@ -536,6 +536,15 @@
   24.24          + "return method._data().apply(self, p);\n"
   24.25      )
   24.26      private static native Object invoke0(boolean isStatic, Method m, Object self, Object[] args);
   24.27 +    
   24.28 +    private static Object invokeTry(boolean isStatic, Method m, Object self, Object[] args)
   24.29 +    throws InvocationTargetException {
   24.30 +        try {
   24.31 +            return invoke0(isStatic, m, self, args);
   24.32 +        } catch (Throwable ex) {
   24.33 +            throw new InvocationTargetException(ex, ex.getMessage());
   24.34 +        }
   24.35 +    }
   24.36  
   24.37      static Object fromPrimitive(Class<?> type, Object o) {
   24.38          if (type == Integer.TYPE) {
    25.1 --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java	Wed Apr 03 13:43:22 2013 +0200
    25.2 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java	Mon Apr 08 19:33:08 2013 +0200
    25.3 @@ -98,9 +98,9 @@
    25.4          }
    25.5          HttpServer s = initServer(".", true);
    25.6          int last = startpage.lastIndexOf('/');
    25.7 +        String prefix = startpage.substring(0, last);
    25.8          String simpleName = startpage.substring(last);
    25.9 -        s.getServerConfiguration().addHttpHandler(new Page(resources, startpage), simpleName);
   25.10 -        s.getServerConfiguration().addHttpHandler(new Page(resources, null), "/");
   25.11 +        s.getServerConfiguration().addHttpHandler(new SubTree(resources, prefix), "/");
   25.12          try {
   25.13              launchServerAndBrwsr(s, simpleName);
   25.14          } catch (URISyntaxException | InterruptedException ex) {
   25.15 @@ -177,7 +177,16 @@
   25.16                      if (r.httpPath.equals(request.getRequestURI())) {
   25.17                          LOG.log(Level.INFO, "Serving HttpResource for {0}", request.getRequestURI());
   25.18                          response.setContentType(r.httpType);
   25.19 -                        copyStream(r.httpContent, response.getOutputStream(), null);
   25.20 +                        r.httpContent.reset();
   25.21 +                        String[] params = null;
   25.22 +                        if (r.parameters.length != 0) {
   25.23 +                            params = new String[r.parameters.length];
   25.24 +                            for (int i = 0; i < r.parameters.length; i++) {
   25.25 +                                params[i] = request.getParameter(r.parameters[i]);
   25.26 +                            }
   25.27 +                        }
   25.28 +                        
   25.29 +                        copyStream(r.httpContent, response.getOutputStream(), null, params);
   25.30                      }
   25.31                  }
   25.32              }
   25.33 @@ -315,7 +324,7 @@
   25.34              }
   25.35              if (ch == '$' && params.length > 0) {
   25.36                  int cnt = is.read() - '0';
   25.37 -                if (cnt == 'U' - '0') {
   25.38 +                if (baseURL != null && cnt == 'U' - '0') {
   25.39                      os.write(baseURL.getBytes("UTF-8"));
   25.40                  } else {
   25.41                      if (cnt >= 0 && cnt < params.length) {
   25.42 @@ -454,7 +463,7 @@
   25.43      }
   25.44  
   25.45      private static class Page extends HttpHandler {
   25.46 -        private final String resource;
   25.47 +        final String resource;
   25.48          private final String[] args;
   25.49          private final Res res;
   25.50          
   25.51 @@ -466,10 +475,7 @@
   25.52  
   25.53          @Override
   25.54          public void service(Request request, Response response) throws Exception {
   25.55 -            String r = resource;
   25.56 -            if (r == null) {
   25.57 -                r = request.getHttpHandlerPath();
   25.58 -            }
   25.59 +            String r = computePage(request);
   25.60              if (r.startsWith("/")) {
   25.61                  r = r.substring(1);
   25.62              }
   25.63 @@ -493,6 +499,28 @@
   25.64                  response.setStatus(404);
   25.65              }
   25.66          }
   25.67 +
   25.68 +        protected String computePage(Request request) {
   25.69 +            String r = resource;
   25.70 +            if (r == null) {
   25.71 +                r = request.getHttpHandlerPath();
   25.72 +            }
   25.73 +            return r;
   25.74 +        }
   25.75 +    }
   25.76 +    
   25.77 +    private static class SubTree extends Page {
   25.78 +
   25.79 +        public SubTree(Res res, String resource, String... args) {
   25.80 +            super(res, resource, args);
   25.81 +        }
   25.82 +
   25.83 +        @Override
   25.84 +        protected String computePage(Request request) {
   25.85 +            return resource + request.getHttpHandlerPath();
   25.86 +        }
   25.87 +        
   25.88 +        
   25.89      }
   25.90  
   25.91      private static class VM extends HttpHandler {
    26.1 --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java	Wed Apr 03 13:43:22 2013 +0200
    26.2 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java	Mon Apr 08 19:33:08 2013 +0200
    26.3 @@ -55,11 +55,11 @@
    26.4      /** HTTP resource to be available during execution. An invocation may
    26.5       * perform an HTTP query and obtain a resource relative to the page.
    26.6       */
    26.7 -    public void addHttpResource(String relativePath, String mimeType, InputStream content) {
    26.8 -        if (relativePath == null || mimeType == null || content == null) {
    26.9 +    public void addHttpResource(String relativePath, String mimeType, String[] parameters, InputStream content) {
   26.10 +        if (relativePath == null || mimeType == null || content == null || parameters == null) {
   26.11              throw new NullPointerException();
   26.12          }
   26.13 -        resources.add(new Resource(content, mimeType, relativePath));
   26.14 +        resources.add(new Resource(content, mimeType, relativePath, parameters));
   26.15      }
   26.16      
   26.17      /** Invokes the associated method. 
   26.18 @@ -100,11 +100,16 @@
   26.19          final InputStream httpContent;
   26.20          final String httpType;
   26.21          final String httpPath;
   26.22 +        final String[] parameters;
   26.23  
   26.24 -        Resource(InputStream httpContent, String httpType, String httpPath) {
   26.25 +        Resource(InputStream httpContent, String httpType, String httpPath,
   26.26 +            String[] parameters
   26.27 +        ) {
   26.28 +            httpContent.mark(Integer.MAX_VALUE);
   26.29              this.httpContent = httpContent;
   26.30              this.httpType = httpType;
   26.31              this.httpPath = httpPath;
   26.32 +            this.parameters = parameters;
   26.33          }
   26.34      }
   26.35  }
    27.1 --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java	Wed Apr 03 13:43:22 2013 +0200
    27.2 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java	Mon Apr 08 19:33:08 2013 +0200
    27.3 @@ -113,13 +113,6 @@
    27.4      )
    27.5      private static native void beginTest(String test, Case c, Object[] arr);
    27.6      
    27.7 -    public static void execute() throws Exception {
    27.8 -        String clazz = (String) getAttr("clazz", "value");
    27.9 -        String method = (String) getAttr("method", "value");
   27.10 -        Object res = invokeMethod(clazz, method);
   27.11 -        setAttr("bck2brwsr.result", "value", res);
   27.12 -    }
   27.13 -
   27.14      @JavaScriptBody(args = { "url", "callback", "arr" }, body = ""
   27.15          + "var request = new XMLHttpRequest();\n"
   27.16          + "request.open('GET', url, true);\n"
   27.17 @@ -141,39 +134,53 @@
   27.18      private static class Request implements Runnable {
   27.19          private final String[] arr = { null };
   27.20          private final String url;
   27.21 +        private Case c;
   27.22 +        private int retries;
   27.23  
   27.24          private Request(String url) throws IOException {
   27.25              this.url = url;
   27.26              loadText(url, this, arr);
   27.27          }
   27.28 +        private Request(String url, String u) throws IOException {
   27.29 +            this.url = url;
   27.30 +            loadText(u, this, arr);
   27.31 +        }
   27.32          
   27.33          @Override
   27.34          public void run() {
   27.35              try {
   27.36 -                String data = arr[0];
   27.37 -                log("\nGot \"" + data + "\"");
   27.38 +                if (c == null) {
   27.39 +                    String data = arr[0];
   27.40 +
   27.41 +                    if (data == null) {
   27.42 +                        log("Some error exiting");
   27.43 +                        closeWindow();
   27.44 +                        return;
   27.45 +                    }
   27.46 +
   27.47 +                    if (data.isEmpty()) {
   27.48 +                        log("No data, exiting");
   27.49 +                        closeWindow();
   27.50 +                        return;
   27.51 +                    }
   27.52 +
   27.53 +                    c = Case.parseData(data);
   27.54 +                    beginTest(c);
   27.55 +                    log("Got \"" + data + "\"");
   27.56 +                } else {
   27.57 +                    log("Processing \"" + arr[0] + "\" for " + retries + " time");
   27.58 +                }
   27.59 +                Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest();
   27.60 +                finishTest(c, result);
   27.61                  
   27.62 -                if (data == null) {
   27.63 -                    log("Some error exiting");
   27.64 -                    closeWindow();
   27.65 +                String u = url + "?request=" + c.getRequestId() + "&result=" + result;
   27.66 +                new Request(url, u);
   27.67 +            } catch (Exception ex) {
   27.68 +                if (ex instanceof InterruptedException) {
   27.69 +                    log("Re-scheduling in 100ms");
   27.70 +                    schedule(this, 100);
   27.71                      return;
   27.72                  }
   27.73 -                
   27.74 -                if (data.isEmpty()) {
   27.75 -                    log("No data, exiting");
   27.76 -                    closeWindow();
   27.77 -                    return;
   27.78 -                }
   27.79 -                
   27.80 -                Case c = Case.parseData(data);
   27.81 -                beginTest(c);
   27.82 -                Object result = c.runTest();
   27.83 -                finishTest(c, result);
   27.84 -                String u = url + "?request=" + c.getRequestId() + "&result=" + result;
   27.85 -                
   27.86 -                loadText(u, this, arr);
   27.87 -                
   27.88 -            } catch (Exception ex) {
   27.89                  log(ex.getClass().getName() + ":" + ex.getMessage());
   27.90              }
   27.91          }
   27.92 @@ -199,8 +206,10 @@
   27.93          return sb.toString();
   27.94      }
   27.95      
   27.96 -    static String invoke(String clazz, String method) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
   27.97 -        final Object r = invokeMethod(clazz, method);
   27.98 +    static String invoke(String clazz, String method) throws 
   27.99 +    ClassNotFoundException, InvocationTargetException, IllegalAccessException, 
  27.100 +    InstantiationException, InterruptedException {
  27.101 +        final Object r = new Case(null).invokeMethod(clazz, method);
  27.102          return r == null ? "null" : r.toString().toString();
  27.103      }
  27.104  
  27.105 @@ -235,40 +244,17 @@
  27.106          }
  27.107      }
  27.108     
  27.109 -    private static Object invokeMethod(String clazz, String method) 
  27.110 -    throws ClassNotFoundException, InvocationTargetException, 
  27.111 -    SecurityException, IllegalAccessException, IllegalArgumentException,
  27.112 -    InstantiationException {
  27.113 -        Method found = null;
  27.114 -        Class<?> c = Class.forName(clazz);
  27.115 -        for (Method m : c.getMethods()) {
  27.116 -            if (m.getName().equals(method)) {
  27.117 -                found = m;
  27.118 -            }
  27.119 -        }
  27.120 -        Object res;
  27.121 -        if (found != null) {
  27.122 -            try {
  27.123 -                if ((found.getModifiers() & Modifier.STATIC) != 0) {
  27.124 -                    res = found.invoke(null);
  27.125 -                } else {
  27.126 -                    res = found.invoke(c.newInstance());
  27.127 -                }
  27.128 -            } catch (Throwable ex) {
  27.129 -                res = ex.getClass().getName() + ":" + ex.getMessage();
  27.130 -            }
  27.131 -        } else {
  27.132 -            res = "Can't find method " + method + " in " + clazz;
  27.133 -        }
  27.134 -        return res;
  27.135 -    }
  27.136 -
  27.137      @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;")
  27.138      private static void turnAssetionStatusOn() {
  27.139      }
  27.140 +
  27.141 +    @JavaScriptBody(args = {"r", "time"}, body =
  27.142 +        "return window.setTimeout(function() { r.run__V(); }, time);")
  27.143 +    private static native Object schedule(Runnable r, int time);
  27.144      
  27.145      private static final class Case {
  27.146          private final Object data;
  27.147 +        private Object inst;
  27.148  
  27.149          private Case(Object data) {
  27.150              this.data = data;
  27.151 @@ -305,7 +291,9 @@
  27.152              }
  27.153          }
  27.154  
  27.155 -        private Object runTest() throws IllegalAccessException, IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, InvocationTargetException, InstantiationException, SecurityException {
  27.156 +        private Object runTest() throws IllegalAccessException, 
  27.157 +        IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, 
  27.158 +        InvocationTargetException, InstantiationException, InterruptedException {
  27.159              if (this.getHtmlFragment() != null) {
  27.160                  setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment());
  27.161              }
  27.162 @@ -317,6 +305,43 @@
  27.163              log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result);
  27.164              return result;
  27.165          }
  27.166 +
  27.167 +        private Object invokeMethod(String clazz, String method)
  27.168 +        throws ClassNotFoundException, InvocationTargetException,
  27.169 +        InterruptedException, IllegalAccessException, IllegalArgumentException,
  27.170 +        InstantiationException {
  27.171 +            Method found = null;
  27.172 +            Class<?> c = Class.forName(clazz);
  27.173 +            for (Method m : c.getMethods()) {
  27.174 +                if (m.getName().equals(method)) {
  27.175 +                    found = m;
  27.176 +                }
  27.177 +            }
  27.178 +            Object res;
  27.179 +            if (found != null) {
  27.180 +                try {
  27.181 +                    if ((found.getModifiers() & Modifier.STATIC) != 0) {
  27.182 +                        res = found.invoke(null);
  27.183 +                    } else {
  27.184 +                        if (inst == null) {
  27.185 +                            inst = c.newInstance();
  27.186 +                        }
  27.187 +                        res = found.invoke(inst);
  27.188 +                    }
  27.189 +                } catch (Throwable ex) {
  27.190 +                    if (ex instanceof InvocationTargetException) {
  27.191 +                        ex = ((InvocationTargetException) ex).getTargetException();
  27.192 +                    }
  27.193 +                    if (ex instanceof InterruptedException) {
  27.194 +                        throw (InterruptedException)ex;
  27.195 +                    }
  27.196 +                    res = ex.getClass().getName() + ":" + ex.getMessage();
  27.197 +                }
  27.198 +            } else {
  27.199 +                res = "Can't find method " + method + " in " + clazz;
  27.200 +            }
  27.201 +            return res;
  27.202 +        }
  27.203          
  27.204          @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');")
  27.205          private static native Object toJSON(String s);
    28.1 --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java	Wed Apr 03 13:43:22 2013 +0200
    28.2 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java	Mon Apr 08 19:33:08 2013 +0200
    28.3 @@ -18,6 +18,7 @@
    28.4  package org.apidesign.vm4brwsr;
    28.5  
    28.6  import java.io.UnsupportedEncodingException;
    28.7 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
    28.8  
    28.9  /**
   28.10   *
   28.11 @@ -129,4 +130,20 @@
   28.12      public String toString() {
   28.13          return HELLO + cnt;
   28.14      }
   28.15 +    
   28.16 +    @JavaScriptBody(args = {}, body = "return [1, 2];")
   28.17 +    private static native Object crtarr();
   28.18 +    @JavaScriptBody(args = { "o" }, body = "return o.toString();")
   28.19 +    private static native String toStrng(Object o);
   28.20 +    
   28.21 +    public static String toStringArray(boolean fakeArr, boolean toString) {
   28.22 +        final Object arr = fakeArr ? crtarr() : new Object[2];
   28.23 +        final String whole = toString ? arr.toString() : toStrng(arr);
   28.24 +        int zav = whole.indexOf('@');
   28.25 +        if (zav <= 0) {
   28.26 +            zav = whole.length();
   28.27 +        }
   28.28 +        return whole.substring(0, zav).toString().toString();
   28.29 +    }
   28.30 +    
   28.31  }
    29.1 --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java	Wed Apr 03 13:43:22 2013 +0200
    29.2 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java	Mon Apr 08 19:33:08 2013 +0200
    29.3 @@ -194,6 +194,34 @@
    29.4          
    29.5      }
    29.6      
    29.7 +    @Test public void toStringOnJSArray() throws Exception {
    29.8 +        String exp = StringSample.toStringArray(false, true);
    29.9 +        
   29.10 +        assertExec(
   29.11 +            "Treated as Java Object array",
   29.12 +            StringSample.class, "toStringArray__Ljava_lang_String_2ZZ",
   29.13 +            exp, true, true
   29.14 +        );
   29.15 +    }
   29.16 +
   29.17 +    @Test public void toStringOnRealArray() throws Exception {
   29.18 +        String exp = StringSample.toStringArray(false, true);
   29.19 +        
   29.20 +        assertExec(
   29.21 +            "Is Java Object array",
   29.22 +            StringSample.class, "toStringArray__Ljava_lang_String_2ZZ",
   29.23 +            exp, false, true
   29.24 +        );
   29.25 +    }
   29.26 +
   29.27 +    @Test public void valueOfOnJSArray() throws Exception {
   29.28 +        assertExec(
   29.29 +            "Treated as classical JavaScript array",
   29.30 +            StringSample.class, "toStringArray__Ljava_lang_String_2ZZ",
   29.31 +            "1,2", true, false
   29.32 +        );
   29.33 +    }
   29.34 +    
   29.35      private static TestVM code;
   29.36      
   29.37      @BeforeClass 
    30.1 --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/BrwsrTest.java	Wed Apr 03 13:43:22 2013 +0200
    30.2 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/BrwsrTest.java	Mon Apr 08 19:33:08 2013 +0200
    30.3 @@ -29,6 +29,10 @@
    30.4   * The browser to is by default executed via {@link java.awt.Desktop#browse(java.net.URI)},
    30.5   * but one can change that by specifying <code>-Dvmtest.brwsrs=firefox,google-chrome</code>
    30.6   * property.
    30.7 + * <p>
    30.8 + * If the annotated method throws {@link InterruptedException}, it will return
    30.9 + * the processing to the browser and after 100ms, called again. This is useful
   30.10 + * for testing asynchronous communication, etc.
   30.11   *
   30.12   * @author Jaroslav Tulach <jtulach@netbeans.org>
   30.13   */
    31.1 --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java	Wed Apr 03 13:43:22 2013 +0200
    31.2 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java	Mon Apr 08 19:33:08 2013 +0200
    31.3 @@ -53,5 +53,10 @@
    31.4          String resource() default "";
    31.5          /** mime type of the resource */
    31.6          String mimeType();
    31.7 +        /** query parameters. Can be referenced from the {@link #content} as
    31.8 +         * <code>$0</code>, <code>$1</code>, etc. The values will be extracted
    31.9 +         * from URL parameters of the request.
   31.10 +         */
   31.11 +        String[] parameters() default {};
   31.12      }
   31.13  }
    32.1 --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java	Wed Apr 03 13:43:22 2013 +0200
    32.2 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java	Mon Apr 08 19:33:08 2013 +0200
    32.3 @@ -65,17 +65,17 @@
    32.4                  for (Http.Resource r : http) {
    32.5                      if (!r.content().isEmpty()) {
    32.6                          InputStream is = new ByteArrayInputStream(r.content().getBytes("UTF-8"));
    32.7 -                        c.addHttpResource(r.path(), r.mimeType(), is);
    32.8 +                        c.addHttpResource(r.path(), r.mimeType(), r.parameters(), is);
    32.9                      } else {
   32.10                          InputStream is = m.getDeclaringClass().getResourceAsStream(r.resource());
   32.11 -                        c.addHttpResource(r.path(), r.mimeType(), is);
   32.12 +                        c.addHttpResource(r.path(), r.mimeType(), r.parameters(), is);
   32.13                      }
   32.14                  }
   32.15              }
   32.16              String res = c.invoke();
   32.17              value = res;
   32.18              if (fail) {
   32.19 -                int idx = res.indexOf(':');
   32.20 +                int idx = res == null ? -1 : res.indexOf(':');
   32.21                  if (idx >= 0) {
   32.22                      Class<? extends Throwable> thrwbl = null;
   32.23                      try {
    33.1 --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java	Wed Apr 03 13:43:22 2013 +0200
    33.2 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java	Mon Apr 08 19:33:08 2013 +0200
    33.3 @@ -149,6 +149,18 @@
    33.4      public int stringToBytesLenght() throws UnsupportedEncodingException {
    33.5          return "\u017dlu\u0165ou\u010dk\u00fd k\u016f\u0148".getBytes("utf8").length;
    33.6      }
    33.7 +    
    33.8 +    @Compare public String replaceSeq() {
    33.9 +        return "Hello World.".replace(".", "!");
   33.10 +    }
   33.11 +    @Compare public String replaceSeqAll() {
   33.12 +        return "Hello World! Hello World.".replace("World", "Jarda");
   33.13 +    }
   33.14 +    @Compare public String replaceSeqAA() {
   33.15 +        String res = "aaa".replace("aa", "b");
   33.16 +        assert res.equals("ba") : "Expecting ba: " + res;
   33.17 +        return res;
   33.18 +    }
   33.19  
   33.20      @Factory
   33.21      public static Object[] create() {
    34.1 --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java	Wed Apr 03 13:43:22 2013 +0200
    34.2 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java	Mon Apr 08 19:33:08 2013 +0200
    34.3 @@ -18,6 +18,8 @@
    34.4  package org.apidesign.bck2brwsr.tck;
    34.5  
    34.6  import java.lang.reflect.Array;
    34.7 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
    34.8 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
    34.9  import org.apidesign.bck2brwsr.vmtest.Compare;
   34.10  import org.apidesign.bck2brwsr.vmtest.VMTest;
   34.11  import org.testng.annotations.Factory;
   34.12 @@ -127,6 +129,30 @@
   34.13          return Array.newInstance(int.class, 3, 3, 3).getClass().getName();
   34.14      }
   34.15      
   34.16 +    @JavaScriptBody(args = {}, body = "return [1, 2];")
   34.17 +    private static native Object crtarr();
   34.18 +
   34.19 +    @JavaScriptBody(args = {}, body = "return new Object();")
   34.20 +    private static native Object newobj();
   34.21 +
   34.22 +    @BrwsrTest
   34.23 +    public static void toStringArray() {
   34.24 +        final Object arr = crtarr();
   34.25 +        final Object real = new Object[2];
   34.26 +        assert arr instanceof Object[] : "Any array is Java array: " + arr;
   34.27 +        assert arr.getClass() == real.getClass() : "Same classes " + arr + " and " + real.getClass();
   34.28 +        final String str = arr.toString();
   34.29 +        assert str != null;
   34.30 +        assert str.startsWith("[Ljava.lang.Object;@") : str;
   34.31 +    }
   34.32 +    
   34.33 +    @BrwsrTest
   34.34 +    public static void objectToString() {
   34.35 +        String s = newobj().toString();
   34.36 +        assert s != null : "Some string computed";
   34.37 +        assert s.startsWith("java.lang.Object@") : "Regular object toString(): " + s;
   34.38 +    }
   34.39 +
   34.40      
   34.41      @Factory
   34.42      public static Object[] create() {
    35.1 --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java	Wed Apr 03 13:43:22 2013 +0200
    35.2 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java	Mon Apr 08 19:33:08 2013 +0200
    35.3 @@ -104,6 +104,11 @@
    35.4          return "should not happen";
    35.5      }
    35.6  
    35.7 +    @Compare public String methodThatThrowsException() throws Exception {
    35.8 +        StaticUse.class.getMethod("instanceMethod").invoke(new StaticUse());
    35.9 +        return "should not happen";
   35.10 +    }
   35.11 +
   35.12      @Compare public Object voidReturnType() throws Exception {
   35.13          return StaticUse.class.getMethod("instanceMethod").getReturnType();
   35.14      }
    36.1 --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java	Wed Apr 03 13:43:22 2013 +0200
    36.2 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java	Mon Apr 08 19:33:08 2013 +0200
    36.3 @@ -30,6 +30,7 @@
    36.4      }
    36.5      
    36.6      public void instanceMethod() {
    36.7 +        throw new IllegalStateException();
    36.8      }
    36.9  
   36.10      public static int plus(int a, int b) {
    37.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    37.2 +++ b/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CallMeTwiceTest.java	Mon Apr 08 19:33:08 2013 +0200
    37.3 @@ -0,0 +1,43 @@
    37.4 +/**
    37.5 + * Back 2 Browser Bytecode Translator
    37.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    37.7 + *
    37.8 + * This program is free software: you can redistribute it and/or modify
    37.9 + * it under the terms of the GNU General Public License as published by
   37.10 + * the Free Software Foundation, version 2 of the License.
   37.11 + *
   37.12 + * This program is distributed in the hope that it will be useful,
   37.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   37.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   37.15 + * GNU General Public License for more details.
   37.16 + *
   37.17 + * You should have received a copy of the GNU General Public License
   37.18 + * along with this program. Look for COPYING file in the top folder.
   37.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
   37.20 + */
   37.21 +package org.apidesign.bck2brwsr.vmtest.impl;
   37.22 +
   37.23 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
   37.24 +import org.apidesign.bck2brwsr.vmtest.VMTest;
   37.25 +import org.testng.annotations.Factory;
   37.26 +
   37.27 +/**
   37.28 + *
   37.29 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   37.30 + */
   37.31 +public class CallMeTwiceTest {
   37.32 +    int cnt;
   37.33 +    
   37.34 +    @BrwsrTest public void callMeTwice() throws InterruptedException {
   37.35 +        if (cnt++ == 0) {
   37.36 +            throw new InterruptedException();
   37.37 +        }
   37.38 +        int prevCnt = cnt;
   37.39 +        cnt = 0;
   37.40 +        assert prevCnt == 2 : "We need to receive two calls " + prevCnt;
   37.41 +    }
   37.42 +    
   37.43 +    @Factory public static Object[] create() {
   37.44 +        return VMTest.create(CallMeTwiceTest.class);
   37.45 +    }
   37.46 +}