You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jt...@apache.org on 2017/10/26 08:23:30 UTC

[incubator-netbeans-html4j] branch master updated (35d4422 -> 246d073)

This is an automated email from the ASF dual-hosted git repository.

jtulach pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-netbeans-html4j.git.


    from 35d4422  NETBEANS-89 & co. Using Apache licensed org.json API.
     new ea070de  Ignoring local NetBeans configuration files
     new 246d073  NETBEANS-99: Let @Model classes point directly to Knockout structures and only indirectly to JavaScript representation of ko objects. Stress test by FXGCPresenter

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |  2 +
 .../java/net/java/html/boot/fx/FXBrowsers.java     | 71 ++---------------
 .../netbeans/html/boot/fx/AbstractFXPresenter.java | 34 +++++---
 .../java/org/netbeans/html/boot/fx/FXBrwsr.java    |  4 +-
 .../fx/{FXPresenter.java => FXGCPresenter.java}    | 57 +++++++++++---
 .../org/netbeans/html/boot/fx/FXPresenter.java     |  6 +-
 .../netbeans/html/boot/fx/InitializeWebView.java   | 90 ++++++++++++++++++++++
 .../java/org/netbeans/html/json/impl/Bindings.java |  6 +-
 .../java/org/netbeans/html/json/impl/JSON.java     |  2 +-
 .../org/netbeans/html/json/spi/Technology.java     | 36 +++++++--
 .../netbeans/html/wstyrus/TyrusKnockoutTest.java   | 10 +--
 .../main/java/org/netbeans/html/ko4j/KOTech.java   | 41 ++++++----
 .../main/java/org/netbeans/html/ko4j/Knockout.java |  4 +
 .../org/netbeans/html/ko4j/KnockoutFXTest.java     |  3 +-
 pom.xml                                            |  1 +
 src/main/javadoc/overview.html                     |  3 +
 .../org/netbeans/html/xhr4j/JsonKnockoutTest.java  |  3 +-
 17 files changed, 250 insertions(+), 123 deletions(-)
 copy boot-fx/src/main/java/org/netbeans/html/boot/fx/{FXPresenter.java => FXGCPresenter.java} (53%)
 create mode 100644 boot-fx/src/main/java/org/netbeans/html/boot/fx/InitializeWebView.java

-- 
To stop receiving notification emails like this one, please contact
['"commits@netbeans.apache.org" <co...@netbeans.apache.org>'].

[incubator-netbeans-html4j] 01/02: Ignoring local NetBeans configuration files

Posted by jt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jtulach pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-netbeans-html4j.git

commit ea070de1c001314fca79e379511802bc08eff13d
Author: Jaroslav Tulach <ja...@oracle.com>
AuthorDate: Wed Oct 25 09:11:20 2017 +0200

    Ignoring local NetBeans configuration files
---
 .gitignore | 2 ++
 pom.xml    | 1 +
 2 files changed, 3 insertions(+)

diff --git a/.gitignore b/.gitignore
index cdac775..22ed2af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 */target/
 /target/
 *.orig
+*/nb-configuration.xml
+
diff --git a/pom.xml b/pom.xml
index 17ed49a..11a5054 100644
--- a/pom.xml
+++ b/pom.xml
@@ -192,6 +192,7 @@ org.netbeans.html.boot.impl:org.netbeans.html.boot.fx:org.netbeans.html.context.
                         <exclude>**/.repository/**</exclude>
                         <exclude>**/.maven/**</exclude>
                         <exclude>**/*.sigtest</exclude>
+                        <exclude>**/nb-configuration.xml</exclude>
                         <exclude>README.md</exclude>
                         <exclude>DEPENDENCIES</exclude>
                     </excludes>

-- 
To stop receiving notification emails like this one, please contact
"commits@netbeans.apache.org" <co...@netbeans.apache.org>.

[incubator-netbeans-html4j] 02/02: NETBEANS-99: Let @Model classes point directly to Knockout structures and only indirectly to JavaScript representation of ko objects. Stress test by FXGCPresenter

Posted by jt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jtulach pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-netbeans-html4j.git

commit 246d0733a636def7bd4c951895e3b5d7cd73ce65
Author: Jaroslav Tulach <ja...@oracle.com>
AuthorDate: Thu Oct 26 10:24:07 2017 +0200

    NETBEANS-99: Let @Model classes point directly to Knockout structures and only indirectly to JavaScript representation of ko objects. Stress test by FXGCPresenter
---
 .../java/net/java/html/boot/fx/FXBrowsers.java     | 71 ++---------------
 .../netbeans/html/boot/fx/AbstractFXPresenter.java | 34 +++++---
 .../java/org/netbeans/html/boot/fx/FXBrwsr.java    |  4 +-
 .../fx/{FXPresenter.java => FXGCPresenter.java}    | 57 +++++++++++---
 .../org/netbeans/html/boot/fx/FXPresenter.java     |  6 +-
 .../netbeans/html/boot/fx/InitializeWebView.java   | 90 ++++++++++++++++++++++
 .../java/org/netbeans/html/json/impl/Bindings.java |  6 +-
 .../java/org/netbeans/html/json/impl/JSON.java     |  2 +-
 .../org/netbeans/html/json/spi/Technology.java     | 36 +++++++--
 .../netbeans/html/wstyrus/TyrusKnockoutTest.java   | 10 +--
 .../main/java/org/netbeans/html/ko4j/KOTech.java   | 41 ++++++----
 .../main/java/org/netbeans/html/ko4j/Knockout.java |  4 +
 .../org/netbeans/html/ko4j/KnockoutFXTest.java     |  3 +-
 src/main/javadoc/overview.html                     |  3 +
 .../org/netbeans/html/xhr4j/JsonKnockoutTest.java  |  3 +-
 15 files changed, 247 insertions(+), 123 deletions(-)

diff --git a/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java b/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java
index 8ceae82..4ae8909 100644
--- a/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java
+++ b/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java
@@ -18,16 +18,13 @@
  */
 package net.java.html.boot.fx;
 
+import org.netbeans.html.boot.fx.InitializeWebView;
 import java.net.URL;
 import javafx.application.Platform;
-import javafx.beans.value.ChangeListener;
-import javafx.beans.value.ObservableValue;
-import javafx.concurrent.Worker;
 import javafx.scene.web.WebView;
 import net.java.html.BrwsrCtx;
 import net.java.html.boot.BrowserBuilder;
 import net.java.html.js.JavaScriptBody;
-import org.netbeans.html.boot.fx.AbstractFXPresenter;
 import org.netbeans.html.context.spi.Contexts;
 import org.netbeans.html.context.spi.Contexts.Id;
 
@@ -89,7 +86,7 @@ public final class FXBrowsers {
     ) {
         Object[] context = new Object[args.length + 1];
         System.arraycopy(args, 0, context, 1, args.length);
-        final Load load = new Load(webView, null);
+        final InitializeWebView load = new InitializeWebView(webView, null);
         context[0] = load;
         BrowserBuilder.newBrowser(context).
             loadPage(url.toExternalForm()).
@@ -172,7 +169,7 @@ public final class FXBrowsers {
     ) {
         Object[] newCtx = new Object[context.length + 1];
         System.arraycopy(context, 0, newCtx, 1, context.length);
-        final Load load = new Load(webView, onPageLoad);
+        final InitializeWebView load = new InitializeWebView(webView, onPageLoad);
         newCtx[0] = load;
         BrowserBuilder.newBrowser(newCtx).
                 loadPage(url.toExternalForm()).
@@ -203,67 +200,9 @@ public final class FXBrowsers {
      */
     public static void runInBrowser(WebView webView, Runnable code) {
         Object ud = webView.getUserData();
-        if (!(ud instanceof Load)) {
+        if (!(ud instanceof InitializeWebView)) {
             throw new IllegalArgumentException();
         }
-        ((Load)ud).ctx.execute(code);
+        ((InitializeWebView)ud).runInContext(code);
     }
-    
-    private static class Load extends AbstractFXPresenter implements Runnable {
-        private final WebView webView;
-        private final Runnable myLoad;
-        private BrwsrCtx ctx;
-
-        public Load(WebView webView, Runnable onLoad) {
-            this.webView = webView;
-            this.myLoad = onLoad;
-            webView.setUserData(this);
-        }
-
-        public void run() {
-            ctx = BrwsrCtx.findDefault(Load.class);
-            if (myLoad != null) {
-                myLoad.run();
-            }
-        }
-        
-        @Override
-        protected void waitFinished() {
-            // don't wait
-        }
-
-        @Override
-        protected WebView findView(final URL resource) {
-            final Worker<Void> w = webView.getEngine().getLoadWorker();
-            w.stateProperty().addListener(new ChangeListener<Worker.State>() {
-                private String previous;
-
-                @Override
-                public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State newState) {
-                    if (newState.equals(Worker.State.SUCCEEDED)) {
-                        if (checkValid()) {
-                            onPageLoad();
-                        }
-                    }
-                    if (newState.equals(Worker.State.FAILED)) {
-                        checkValid();
-                        throw new IllegalStateException("Failed to load " + resource);
-                    }
-                }
-
-                private boolean checkValid() {
-                    final String crnt = webView.getEngine().getLocation();
-                    if (previous != null && !previous.equals(crnt)) {
-                        w.stateProperty().removeListener(this);
-                        return false;
-                    }
-                    previous = crnt;
-                    return true;
-                }
-            });
-
-            return webView;
-        }
-    }
-    
 }
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
index 08590c1..53dce08 100644
--- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
@@ -22,6 +22,7 @@ import java.io.BufferedReader;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.Reader;
+import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Array;
 import java.net.URL;
@@ -182,9 +183,9 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable {
         waitFinished();
     }
 
-    protected abstract void waitFinished();
+    abstract void waitFinished();
 
-    protected abstract WebView findView(final URL resource);
+    abstract WebView findView(final URL resource);
 
     final JSObject convertArrays(Object[] arr) {
         for (int i = 0; i < arr.length; i++) {
@@ -569,8 +570,9 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable {
             Object java = obj.getMember("fxBrwsrId");
             if (java instanceof JSObject) {
                 for (;;) {
-                    int resultHash;
-                    int resultId;
+                    final int resultHash;
+                    final int resultId;
+                    final NavigableSet<Ref> refs;
                     synchronized (this) {
                         this.hash = -1;
                         this.id = -1;
@@ -579,28 +581,38 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable {
                         assert this.id != -1;
                         resultHash = this.hash;
                         resultId = this.id;
+                        refs = values.get(resultHash);
+                        if (refs == null) {
+                            return null;
+                        }
                     }
 
-                    final NavigableSet<Ref> refs = values.get(resultHash);
                     Iterator<Ref> it = refs.iterator();
                     while (it.hasNext()) {
                         Ref next = it.next();
-                        Object pojo = next.value();
-                        if (next.id() == resultId) {
-                            return pojo;
-                        }
-                        if (pojo == null) {
+                        Object[] pojo = { next.value() };
+                        if (pojo[0] == null) {
                             it.remove();
+                            continue;
+                        }
+                        if (next.id() == resultId) {
+                            return emitJavaObject(pojo, resultHash, resultId);
                         }
                     }
                     if (refs.isEmpty()) {
-                        values.remove(resultHash);
+                        synchronized (this) {
+                            values.remove(resultHash);
+                        }
                     }
                 }
             }
             return obj;
         }
+
     }
 
+    Object emitJavaObject(Object[] pojo, int hash, int id) {
+        return pojo[0];
+    }
 
 }
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java
index 5fd6039..a6f96f7 100644
--- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java
@@ -65,7 +65,7 @@ public class FXBrwsr extends Application {
     private static final CountDownLatch FINISHED = new CountDownLatch(1);
     private BorderPane root;
 
-    public static synchronized WebView findWebView(final URL url, final FXPresenter onLoad) {
+    public static synchronized WebView findWebView(final URL url, final AbstractFXPresenter onLoad) {
         if (INSTANCE == null) {
             final String callee = findCalleeClassName();
             Executors.newFixedThreadPool(1).submit(new Runnable() {
@@ -211,7 +211,7 @@ public class FXBrwsr extends Application {
         return arr;
     }
 
-    private WebView newView(final URL url, final FXPresenter onLoad) {
+    private WebView newView(final URL url, final AbstractFXPresenter onLoad) {
         final WebView view = new WebView();
         view.setContextMenuEnabled(false);
         Stage newStage;
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXGCPresenter.java
similarity index 53%
copy from boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
copy to boot-fx/src/main/java/org/netbeans/html/boot/fx/FXGCPresenter.java
index 3be269e..330b13a 100644
--- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXGCPresenter.java
@@ -19,22 +19,22 @@
 package org.netbeans.html.boot.fx;
 
 import java.io.File;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.logging.Level;
 import javafx.scene.web.WebView;
-import net.java.html.boot.BrowserBuilder;
-import org.netbeans.html.boot.spi.Fn;
-import org.openide.util.lookup.ServiceProvider;
 
-/** This is an implementation class, use {@link BrowserBuilder} API. Just
- * include this JAR on classpath and the {@link BrowserBuilder} API will find
- * this implementation automatically.
+/** Presenter for stress testing. It tries to force few GC cycles
+ * before returning a Java object from {@link WebView} to simulate the
+ * fact that in JDK8 newer than build 112 the Java objects exposed to
+ * {@link WebView} are held by weak references.
  *
  * @author Jaroslav Tulach
  */
-@ServiceProvider(service = Fn.Presenter.class)
-public final class FXPresenter extends AbstractFXPresenter {
+public final class FXGCPresenter extends AbstractFXPresenter {
     static {
         try {
             try {
@@ -54,11 +54,48 @@ public final class FXPresenter extends AbstractFXPresenter {
         }
     }
 
-    protected void waitFinished() {
+    @Override
+    void waitFinished() {
         FXBrwsr.waitFinished();
     }
 
-    protected WebView findView(final URL resource) {
+    @Override
+    WebView findView(final URL resource) {
         return FXBrwsr.findWebView(resource, this);
     }
+
+    @Override
+    Object emitJavaObject(Object[] pojo, int hash, int id) {
+        Reference<Object> ref = new WeakReference<Object>(pojo[0]);
+        boolean nonNull = ref.get() != null;
+        assertGC(ref);
+        Object r;
+        if ((r = ref.get()) == null && nonNull) {
+            throw new NullPointerException("Value has been GCed to null for " + hash + " and " + id);
+        }
+        return r;
+    }
+
+    private static boolean isGone(Reference<?> ref) {
+        return ref.get() == null;
+    }
+
+    private static void assertGC(Reference<Object> ref) {
+        long l = System.currentTimeMillis();
+        for (int i = 0; i < 3; i++) {
+            if (isGone(ref)) {
+                return;
+            }
+
+            try {
+                System.gc();
+                System.runFinalization();
+            } catch (Error err) {
+                LOG.log(Level.INFO, "Problems during GCing attempt of " + ref.get(), err);
+            }
+        }
+        final long took = System.currentTimeMillis() - l;
+        LOG.log(Level.FINE, "Good: No GC of {1} for {0} ms.", new Object[]{took, ref.get()});
+    }
+
 }
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
index 3be269e..1554113 100644
--- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
@@ -54,11 +54,13 @@ public final class FXPresenter extends AbstractFXPresenter {
         }
     }
 
-    protected void waitFinished() {
+    @Override
+    void waitFinished() {
         FXBrwsr.waitFinished();
     }
 
-    protected WebView findView(final URL resource) {
+    @Override
+    WebView findView(final URL resource) {
         return FXBrwsr.findWebView(resource, this);
     }
 }
diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/InitializeWebView.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/InitializeWebView.java
new file mode 100644
index 0000000..b093565
--- /dev/null
+++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/InitializeWebView.java
@@ -0,0 +1,90 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.html.boot.fx;
+
+import java.net.URL;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.concurrent.Worker;
+import javafx.scene.web.WebView;
+import net.java.html.BrwsrCtx;
+import org.netbeans.html.boot.fx.AbstractFXPresenter;
+
+public final class InitializeWebView extends AbstractFXPresenter implements Runnable {
+
+    private final WebView webView;
+    private final Runnable myLoad;
+    BrwsrCtx ctx;
+
+    public InitializeWebView(WebView webView, Runnable onLoad) {
+        this.webView = webView;
+        this.myLoad = onLoad;
+        webView.setUserData(this);
+    }
+
+    @Override
+    public void run() {
+        ctx = BrwsrCtx.findDefault(InitializeWebView.class);
+        if (myLoad != null) {
+            myLoad.run();
+        }
+    }
+
+    @Override
+    void waitFinished() {
+        // don't wait
+    }
+
+    @Override
+    WebView findView(final URL resource) {
+        final Worker<Void> w = webView.getEngine().getLoadWorker();
+        w.stateProperty().addListener(new ChangeListener<Worker.State>() {
+            private String previous;
+
+            @Override
+            public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State newState) {
+                if (newState.equals(Worker.State.SUCCEEDED)) {
+                    if (checkValid()) {
+                        onPageLoad();
+                    }
+                }
+                if (newState.equals(Worker.State.FAILED)) {
+                    checkValid();
+                    throw new IllegalStateException("Failed to load " + resource);
+                }
+            }
+
+            private boolean checkValid() {
+                final String crnt = webView.getEngine().getLocation();
+                if (previous != null && !previous.equals(crnt)) {
+                    w.stateProperty().removeListener(this);
+                    return false;
+                }
+                previous = crnt;
+                return true;
+            }
+        });
+        return webView;
+    }
+
+    public final void runInContext(Runnable r) {
+        ctx.execute(r);
+    }
+
+}
diff --git a/json/src/main/java/org/netbeans/html/json/impl/Bindings.java b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java
index fdd1b57..3b0e062 100644
--- a/json/src/main/java/org/netbeans/html/json/impl/Bindings.java
+++ b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java
@@ -69,7 +69,11 @@ public final class Bindings<Data> {
     }
     
     
-    public Data koData() {
+    final Object jsObj() {
+        if (bp instanceof Technology.ToJavaScript) {
+            Technology.ToJavaScript<Data> toJS = (Technology.ToJavaScript<Data>) bp;
+            return toJS.toJavaScript(data);
+        }
         return data;
     }
 
diff --git a/json/src/main/java/org/netbeans/html/json/impl/JSON.java b/json/src/main/java/org/netbeans/html/json/impl/JSON.java
index 1745c0c..09c691f 100644
--- a/json/src/main/java/org/netbeans/html/json/impl/JSON.java
+++ b/json/src/main/java/org/netbeans/html/json/impl/JSON.java
@@ -267,7 +267,7 @@ public final class JSON {
             return null;
         }
         final Bindings b = PropertyBindingAccessor.getBindings(proto, true, null);
-        return b == null ? null : b.koData();
+        return b == null ? null : b.jsObj();
     }
 
     private static Proto findProto(Object object) {
diff --git a/json/src/main/java/org/netbeans/html/json/spi/Technology.java b/json/src/main/java/org/netbeans/html/json/spi/Technology.java
index 2e0a206..b138dbe 100644
--- a/json/src/main/java/org/netbeans/html/json/spi/Technology.java
+++ b/json/src/main/java/org/netbeans/html/json/spi/Technology.java
@@ -23,16 +23,19 @@ import net.java.html.json.Model;
 import net.java.html.json.Models;
 import org.netbeans.html.context.spi.Contexts.Id;
 
-/** An implementation of a binding between model classes (see {@link Model})
+/** *  An implementation of a binding between model classes (see {@link Model})
  * and particular technology like <a href="http://knockoutjs.com">knockout.js</a>
- * in a browser window, etc.
- * Since introduction of {@link Id technology identifiers} one can choose between
- * different background implementations to handle the conversion and
- * communication requests. The currently known provider is
- * <code>org.netbeans.html:ko4j</code> module which registers 
+ * in a browser window, etc.Since introduction of {@link Id technology identifiers} one can choose between
+ different background implementations to handle the conversion and
+ communication requests.
+ * The currently known provider is
+ <code>org.netbeans.html:ko4j</code> module which registers 
  * a <a href="http://knockoutjs.com" target="_blank">knockout.js</a>
  * implementation called <b>ko4j</b>.
  *
+ * @param <Data> technology internal type that keeps internal data for each
+ *    instance of {@linkplains Model model class}.
+ *
  * @author Jaroslav Tulach
  */
 public interface Technology<Data> {
@@ -189,4 +192,25 @@ public interface Technology<Data> {
          */
         public D wrapModel(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr);
     }
+
+    /** Convertor of the internal data type to object suitable as a JavaScript
+     * representation. Certain technologies need to keep some data in Java
+     * and only part of them in JavaScript-ready object. With the help of
+     * {@code ToJavaScript} interface, they can parametrize their
+     * {@link Technology} with the Java type and implement
+     * {@link #toJavaScript(java.lang.Object)}
+     * method to extract the proper JavaScript part from that object.
+     *
+     * @param <D> the internal data type
+     * @since 1.5.1
+     */
+    public static interface ToJavaScript<D> extends Technology<D> {
+        /** Extracts JavaScript ready representation.
+         *
+         * @param data technology's internal data structure
+         * @return object ready to represent the data in JavaScript
+         * @since 1.5.1
+         */
+        public Object toJavaScript(D data);
+    }
 }
diff --git a/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java b/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java
index 0a17692..c651346 100644
--- a/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java
+++ b/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java
@@ -35,6 +35,9 @@ import java.util.concurrent.Executors;
 import net.java.html.BrwsrCtx;
 import net.java.html.boot.BrowserBuilder;
 import net.java.html.js.JavaScriptBody;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.netbeans.html.boot.fx.FXGCPresenter;
 import org.netbeans.html.boot.spi.Fn;
 import org.netbeans.html.context.spi.Contexts;
 import org.netbeans.html.json.spi.Technology;
@@ -42,13 +45,10 @@ import org.netbeans.html.json.spi.Transfer;
 import org.netbeans.html.json.spi.WSTransfer;
 import org.netbeans.html.json.tck.KOTest;
 import org.netbeans.html.json.tck.KnockoutTCK;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.netbeans.html.boot.impl.FnContext;
 import org.netbeans.html.ko4j.KO4J;
 import org.openide.util.lookup.ServiceProvider;
 import org.testng.Assert;
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
 import org.testng.annotations.Factory;
 
 /**
@@ -75,7 +75,7 @@ public final class TyrusKnockoutTest extends KnockoutTCK {
         
         URI uri = TyrusDynamicHTTP.initServer();
     
-        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(TyrusKnockoutTest.class).
+        final BrowserBuilder bb = BrowserBuilder.newBrowser(new FXGCPresenter()).loadClass(TyrusKnockoutTest.class).
             loadPage(uri.toString()).
             invoke("initialized");
         
diff --git a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java
index c94e5ae..32f0ea9 100644
--- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java
+++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java
@@ -34,7 +34,8 @@ import org.netbeans.html.json.spi.Technology;
  */
 @Contexts.Id("ko4j")
 final class KOTech
-implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Technology.ApplyId<Object> {
+implements Technology.BatchCopy<Knockout>, Technology.ValueMutated<Knockout>,
+Technology.ApplyId<Knockout>, Technology.ToJavaScript<Knockout> {
     private Object[] jsObjects;
     private int jsIndex;
 
@@ -42,11 +43,11 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno
     }
     
     @Override
-    public Object wrapModel(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr) {
+    public Knockout wrapModel(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr) {
         return createKO(model, copyFrom, propArr, funcArr, null);
     }
 
-    final Object createKO(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr, Knockout[] ko) {
+    final Knockout createKO(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr, Knockout[] ko) {
         String[] propNames = new String[propArr.length];
         Number[] propInfo = new Number[propArr.length];
         Object[] propValues = new Object[propArr.length];
@@ -76,7 +77,7 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno
             ret, copyFrom,
             propNames, propInfo, propValues, funcNames
         );
-        return ret;
+        return newKO;
     }
     
     private Object getJSObject() {
@@ -95,42 +96,43 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno
     }
     
     @Override
-    public Object wrapModel(Object model) {
+    public Knockout wrapModel(Object model) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void bind(PropertyBinding b, Object model, Object data) {
+    public void bind(PropertyBinding b, Object model, Knockout data) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void valueHasMutated(Object data, String propertyName) {
-        Knockout.cleanUp();
-        Knockout.valueHasMutated(data, propertyName, null, null);
+    public void valueHasMutated(Knockout data, String propertyName) {
+        valueHasMutated(data, propertyName, null, null);
     }
     
     @Override
-    public void valueHasMutated(Object data, String propertyName, Object oldValue, Object newValue) {
+    public void valueHasMutated(Knockout data, String propertyName, Object oldValue, Object newValue) {
         Knockout.cleanUp();
-        if (newValue instanceof Enum) {
-            newValue = newValue.toString();
+        if (data != null) {
+            if (newValue instanceof Enum) {
+                newValue = newValue.toString();
+            }
+            Knockout.valueHasMutated(data.js(), propertyName, oldValue, newValue);
         }
-        Knockout.valueHasMutated(data, propertyName, oldValue, newValue);
     }
 
     @Override
-    public void expose(FunctionBinding fb, Object model, Object d) {
+    public void expose(FunctionBinding fb, Object model, Knockout data) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void applyBindings(Object data) {
+    public void applyBindings(Knockout data) {
         applyBindings(null, data);
     }
     @Override
-    public void applyBindings(String id, Object data) {
-        Object ko = Knockout.applyBindings(id, data);
+    public void applyBindings(String id, Knockout data) {
+        Object ko = Knockout.applyBindings(id, data.js());
         if (ko instanceof Knockout) {
             ((Knockout)ko).hold();
             applied.add((Knockout) ko);
@@ -153,4 +155,9 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno
     public <M> M toModel(Class<M> modelClass, Object data) {
         return modelClass.cast(Knockout.toModel(data));
     }
+
+    @Override
+    public Object toJavaScript(Knockout data) {
+        return data.js();
+    }
 }
diff --git a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java
index 7500715..ae52c39 100644
--- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java
+++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java
@@ -71,6 +71,10 @@ final class Knockout  {
         }
     }
 
+    final Object js() {
+        return js;
+    }
+
     static void cleanUp() {
         for (;;) {
             Knockout ko = null;
diff --git a/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java b/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java
index 4306983..8bde691 100644
--- a/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java
+++ b/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java
@@ -34,6 +34,7 @@ import java.util.concurrent.Executors;
 import net.java.html.BrwsrCtx;
 import net.java.html.boot.BrowserBuilder;
 import net.java.html.js.JavaScriptBody;
+import org.netbeans.html.boot.fx.FXGCPresenter;
 import org.netbeans.html.boot.spi.Fn;
 import org.netbeans.html.context.spi.Contexts;
 import org.netbeans.html.json.spi.Technology;
@@ -70,7 +71,7 @@ public final class KnockoutFXTest extends KnockoutTCK {
         
         URI uri = DynamicHTTP.initServer();
     
-        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(KnockoutFXTest.class).
+        final BrowserBuilder bb = BrowserBuilder.newBrowser(new FXGCPresenter()).loadClass(KnockoutFXTest.class).
             loadPage(uri.toString()).
             invoke("initialized");
         
diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html
index 9bdbc8b..b9f066a 100644
--- a/src/main/javadoc/overview.html
+++ b/src/main/javadoc/overview.html
@@ -60,6 +60,9 @@
         {@link net.java.html.json.ComputedProperty computing a property}.
         Regular subclassing of {@link org.netbeans.html.json.spi.Proto.Type} is
         possible.
+        Bugfix <a target="_blank" href="https://issues.apache.org/jira/browse/NETBEANS-99">#99</a>
+        - better garbage collector related behavior of <b>ko4j</b> instances thanks
+        to introduction of {@link org.netbeans.html.json.spi.Technology.ToJavaScript}.
         
         <h3>New in version 1.5</h3>
 
diff --git a/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java b/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java
index b148218..168de1d 100644
--- a/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java
+++ b/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java
@@ -35,6 +35,7 @@ import java.util.concurrent.Executors;
 import net.java.html.BrwsrCtx;
 import net.java.html.boot.BrowserBuilder;
 import net.java.html.js.JavaScriptBody;
+import org.netbeans.html.boot.fx.FXGCPresenter;
 import org.netbeans.html.boot.spi.Fn;
 import org.netbeans.html.context.spi.Contexts;
 import org.netbeans.html.json.spi.Technology;
@@ -69,7 +70,7 @@ public final class JsonKnockoutTest extends KnockoutTCK {
         
         URI uri = JsonDynamicHTTP.initServer();
     
-        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(JsonKnockoutTest.class).
+        final BrowserBuilder bb = BrowserBuilder.newBrowser(new FXGCPresenter()).loadClass(JsonKnockoutTest.class).
             loadPage(uri.toString()).
             invoke("initialized");
         

-- 
To stop receiving notification emails like this one, please contact
"commits@netbeans.apache.org" <co...@netbeans.apache.org>.