# HG changeset patch # User Jaroslav Tulach # Date 1418110402 -3600 # Node ID de02b7c13379813f40086f59d668dbb68ef5f9b0 # Parent 5bea583947da2c8a15bece25566a7bb0181e1450# Parent 548982033730df74377024bb50232ab030e73a91 Bringing the most recent changes in default branch to gc diff -r 5bea583947da -r de02b7c13379 boot-agent-test/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/boot-agent-test/pom.xml Tue Dec 09 08:33:22 2014 +0100 @@ -0,0 +1,61 @@ + + + 4.0.0 + + org.netbeans.html + pom + 2.0-SNAPSHOT + + boot-agent-test + jar + Dynamic Boot Test + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + org.testng + testng + test + + + org.netbeans.html + net.java.html + ${project.version} + test + jar + + + org.netbeans.html + net.java.html.boot + ${project.version} + test + jar + + + org.netbeans.api + org-openide-util-lookup + test + jar + + + ${project.groupId} + net.java.html.boot.fx + ${project.version} + test + + + org.ow2.asm + asm + test + + + \ No newline at end of file diff -r 5bea583947da -r de02b7c13379 boot-agent-test/src/test/java/org/netbeans/html/bootagent/DynamicClassLoaderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/DynamicClassLoaderTest.java Tue Dec 09 08:33:22 2014 +0100 @@ -0,0 +1,126 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.bootagent; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import net.java.html.BrwsrCtx; +import net.java.html.boot.BrowserBuilder; +import org.netbeans.html.boot.spi.Fn; +import org.testng.Assert; +import static org.testng.Assert.assertNotSame; +import static org.testng.Assert.assertSame; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class DynamicClassLoaderTest { + private static Class browserClass; + private static Fn.Presenter browserPresenter; + + public DynamicClassLoaderTest() { + } + + @Factory public static Object[] compatibilityTests() throws Exception { + final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(DynamicClassLoaderTest.class). + loadPage("empty.html"). + invoke("initialized"); + + Executors.newSingleThreadExecutor().submit(new Runnable() { + @Override + public void run() { + bb.showAndWait(); + } + }); + + List res = new ArrayList(); + + Class[] arr = new Class[] { loadClass() }; + for (Class c : arr) { + for (Method m : c.getDeclaredMethods()) { + if ((m.getModifiers() & Modifier.PUBLIC) != 0) { + res.add(new KOFx(browserPresenter, m)); + } + } + } + return res.toArray(); + } + + static synchronized Class loadClass() throws InterruptedException { + while (browserClass == null) { + DynamicClassLoaderTest.class.wait(); + } + return browserClass; + } + + public static void ready(Class browserCls) throws Exception { + Class origClazz = ClassLoader.getSystemClassLoader().loadClass(DynamicClassLoaderTest.class.getName()); + final Field f1 = origClazz.getDeclaredField("browserClass"); + f1.setAccessible(true); + f1.set(null, browserCls); + final Field f2 = origClazz.getDeclaredField("browserPresenter"); + f2.setAccessible(true); + f2.set(null, Fn.activePresenter()); + synchronized (origClazz) { + origClazz.notifyAll(); + } + } + + public static void initialized() throws Exception { + BrwsrCtx b1 = BrwsrCtx.findDefault(DynamicClassLoaderTest.class); + assertNotSame(b1, BrwsrCtx.EMPTY, "Browser context is not empty"); + BrwsrCtx b2 = BrwsrCtx.findDefault(DynamicClassLoaderTest.class); + assertSame(b1, b2, "Browser context remains stable"); + Assert.assertNotSame(DynamicClassLoaderTest.class.getClassLoader(), + ClassLoader.getSystemClassLoader(), + "Should use special classloader, not system one" + ); + DynamicClassLoaderTest.ready(JavaScriptBodyTst.class); + } +} diff -r 5bea583947da -r de02b7c13379 boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java Tue Dec 09 08:33:22 2014 +0100 @@ -0,0 +1,63 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.bootagent; + +import net.java.html.js.JavaScriptBody; + +/** + * + * @author Jaroslav Tulach + */ +public class JavaScriptBodyTst { + + public JavaScriptBodyTst() { + } + + public void assert42() { + int v = mul(7, 6); + assert v == 42 : "Really 42: " + v; + } + + @JavaScriptBody(args = { "x", "y" }, body = "return x * y;") + private static native int mul(int x, int y); +} diff -r 5bea583947da -r de02b7c13379 boot-agent-test/src/test/java/org/netbeans/html/bootagent/KOFx.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/KOFx.java Tue Dec 09 08:33:22 2014 +0100 @@ -0,0 +1,125 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.bootagent; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javafx.application.Platform; +import org.netbeans.html.boot.impl.FnContext; +import org.netbeans.html.boot.spi.Fn; +import org.testng.IHookCallBack; +import org.testng.IHookable; +import org.testng.ITest; +import org.testng.ITestResult; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public final class KOFx implements ITest, IHookable, Runnable { + private final Fn.Presenter p; + private final Method m; + private Object result; + private Object inst; + + KOFx(Fn.Presenter p, Method m) { + this.p = p; + this.m = m; + } + + @Override + public String getTestName() { + return m.getName(); + } + + @Test + public synchronized void executeTest() throws Exception { + if (result == null) { + Platform.runLater(this); + wait(); + } + if (result instanceof Exception) { + throw (Exception)result; + } + if (result instanceof Error) { + throw (Error)result; + } + } + + @Override + public synchronized void run() { + boolean notify = true; + try { + FnContext.currentPresenter(p); + if (inst == null) { + inst = m.getDeclaringClass().newInstance(); + } + result = m.invoke(inst); + if (result == null) { + result = this; + } + } catch (InvocationTargetException ex) { + Throwable r = ex.getTargetException(); + if (r instanceof InterruptedException) { + notify = false; + Platform.runLater(this); + return; + } + result = r; + } catch (Exception ex) { + result = ex; + } finally { + if (notify) { + notifyAll(); + } + FnContext.currentPresenter(null); + } + } + + @Override + public void run(IHookCallBack ihcb, ITestResult itr) { + ihcb.runTestMethod(itr); + } + +} diff -r 5bea583947da -r de02b7c13379 boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty.html Tue Dec 09 08:33:22 2014 +0100 @@ -0,0 +1,55 @@ + + + + + Bootstrap Dynamically + + + + +
Bootstrap Dynamically
+ + diff -r 5bea583947da -r de02b7c13379 boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java --- a/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java Tue Dec 09 07:23:22 2014 +0100 +++ b/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java Tue Dec 09 08:33:22 2014 +0100 @@ -52,6 +52,8 @@ 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; /** Utility methods to use {@link WebView} and {@link JavaScriptBody} code * in existing JavaFX applications. @@ -82,6 +84,16 @@ *

* This method sets {@link WebView#getUserData()} and {@link #runInBrowser(javafx.scene.web.WebView, java.lang.Runnable)} * relies on the value. Please don't alter it. + *

+ * Since introduction of {@link Id technology identifiers} the + * provided args strings are also passed to the + * {@link BrwsrCtx context} when it is being + * {@link Contexts#newBuilder(java.lang.Object...) created} + * and can influence the selection + * of available technologies + * (like {@link org.netbeans.html.json.spi.Technology}, + * {@link org.netbeans.html.json.spi.Transfer} or + * {@link org.netbeans.html.json.spi.WSTransfer}). * * @param webView the instance of Web View to tweak * @param url the URL of the HTML page to load into the view @@ -94,7 +106,10 @@ Class onPageLoad, String methodName, String... args ) { - BrowserBuilder.newBrowser(new Load(webView)). + Object[] context = new Object[args.length + 1]; + System.arraycopy(args, 0, context, 1, args.length); + context[0] = new Load(webView); + BrowserBuilder.newBrowser(context). loadPage(url.toExternalForm()). loadClass(onPageLoad). invoke(methodName, args). @@ -126,8 +141,8 @@ /** Enables the Java/JavaScript bridge (that supports {@link JavaScriptBody} and co.) * in the provided webView. This method returns - * immediately. Once the support is active, it calls back specified - * method in onPageLoad's run method. + * immediately. Once the support is active, it calls back {@link Runnable#run() run} + * method in onPageLoad. * This is more convenient way to initialize the webview, * but it requires one to make sure * all {@link JavaScriptBody} methods has been post-processed during @@ -145,7 +160,37 @@ public static void load( WebView webView, final URL url, Runnable onPageLoad, ClassLoader loader ) { - BrowserBuilder.newBrowser(new Load(webView)). + load(webView, url, onPageLoad, loader, new Object[0]); + } + + /** Enables the Java/JavaScript bridge (that supports {@link JavaScriptBody} and co.) + * in the provided webView. This method returns + * immediately. Once the support is active, it calls back {@link Runnable#run() run} + * method in onPageLoad. + * This is more convenient way to initialize the webview, + * but it requires one to make sure + * all {@link JavaScriptBody} methods has been post-processed during + * compilation and there will be no need to instantiate new classloader. + *

+ * This method sets {@link WebView#getUserData()} and {@link #runInBrowser(javafx.scene.web.WebView, java.lang.Runnable)} + * relies on the value. Please don't alter it. + * + * @param webView the instance of Web View to tweak + * @param url the URL of the HTML page to load into the view + * @param onPageLoad callback to call when the page is ready + * @param loader the loader to use when constructing initial {@link BrwsrCtx} or null + * @param context additonal configuration to pass to {@link BrowserBuilder#newBrowser(java.lang.Object...)} + * and {@link Contexts#newBuilder(java.lang.Object...)} factory methods + * @since 1.1 + */ + public static void load( + WebView webView, final URL url, Runnable onPageLoad, ClassLoader loader, + Object... context + ) { + Object[] newCtx = new Object[context.length + 1]; + System.arraycopy(context, 0, newCtx, 1, context.length); + newCtx[0] = new Load(webView); + BrowserBuilder.newBrowser(newCtx). loadPage(url.toExternalForm()). loadFinished(onPageLoad). classloader(loader). diff -r 5bea583947da -r de02b7c13379 boot-fx/src/main/java/org/netbeans/html/boot/fx/FXToolbar.java --- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXToolbar.java Tue Dec 09 07:23:22 2014 +0100 +++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXToolbar.java Tue Dec 09 08:33:22 2014 +0100 @@ -42,8 +42,6 @@ */ package org.netbeans.html.boot.fx; -import java.io.IOException; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -55,10 +53,11 @@ import javafx.collections.FXCollections; import javafx.event.ActionEvent; import javafx.event.EventHandler; +import javafx.scene.Group; +import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; -import javafx.scene.control.ScrollPane; import javafx.scene.control.Separator; import javafx.scene.control.Toggle; import javafx.scene.control.ToggleButton; @@ -69,6 +68,9 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.web.WebView; +import javafx.stage.Screen; +import javafx.stage.Stage; +import javafx.stage.Window; final class FXToolbar extends ToolBar { private final ArrayList resizeButtons; @@ -185,21 +187,25 @@ } - private void _resize( final double width, final double height ) { - ScrollPane scroll; - if (container.getCenter() == webView) { - scroll = new ScrollPane(); - scroll.setContent(webView); - container.setCenter(scroll); - } else { - scroll = (ScrollPane) container.getCenter(); - } - scroll.setPrefViewportWidth( width ); - scroll.setPrefViewportHeight(height ); - webView.setMaxWidth( width ); - webView.setMaxHeight( height ); - webView.setMinWidth( width ); - webView.setMinHeight( height ); + private void _resize(final double width, final double height) { + Window window = container.getScene().getWindow(); + // size difference between root node and window depends on OS and Decorations + double diffY = window.getHeight() - container.getHeight(); + double diffX = window.getWidth() - container.getWidth(); + + webView.setMaxWidth(width); + webView.setMaxHeight(height); + webView.setMinWidth(width); + webView.setMinHeight(height); + javafx.geometry.Rectangle2D screenBounds = Screen.getPrimary().getBounds(); + double scaleX = screenBounds.getWidth() / ( width + diffX ); + double scaleY = screenBounds.getHeight() / ( height + diffY ); + // calculate scale factor if too big for device, the .1 adds some padding + double scale = Math.min(Math.min(scaleX, scaleY), 1.1) - .1; + webView.setScaleX(scale); + webView.setScaleY(scale); + container.getScene().setRoot(new Group()); + ((Stage)window).setScene(new Scene(container, width * scale, height * scale)); } private void _autofit() { diff -r 5bea583947da -r de02b7c13379 boot/pom.xml --- a/boot/pom.xml Tue Dec 09 07:23:22 2014 +0100 +++ b/boot/pom.xml Tue Dec 09 08:33:22 2014 +0100 @@ -36,6 +36,8 @@ org.ow2.asm asm jar + provided + true org.testng diff -r 5bea583947da -r de02b7c13379 boot/src/main/java/net/java/html/boot/BrowserBuilder.java --- a/boot/src/main/java/net/java/html/boot/BrowserBuilder.java Tue Dec 09 07:23:22 2014 +0100 +++ b/boot/src/main/java/net/java/html/boot/BrowserBuilder.java Tue Dec 09 08:33:22 2014 +0100 @@ -63,7 +63,9 @@ import net.java.html.BrwsrCtx; import net.java.html.js.JavaScriptBody; import org.netbeans.html.boot.spi.Fn; +import org.netbeans.html.boot.spi.Fn.Presenter; import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.context.spi.Contexts.Id; import org.netbeans.html.boot.impl.FindResources; import org.netbeans.html.boot.impl.FnContext; import org.netbeans.html.boot.impl.FnUtils; @@ -120,6 +122,15 @@ /** Entry method to obtain a new browser builder. Follow by calling * its instance methods like {@link #loadClass(java.lang.Class)} and * {@link #loadPage(java.lang.String)}. + * Since introduction of {@link Id technology identifiers} the + * provided context objects are also passed to the + * {@link BrwsrCtx context} when it is being + * {@link Contexts#newBuilder(java.lang.Object...) created} + * and can influence the selection + * of available technologies + * (like {@link org.netbeans.html.json.spi.Technology}, + * {@link org.netbeans.html.json.spi.Transfer} or + * {@link org.netbeans.html.json.spi.WSTransfer}) by name. * * @param context any instances that should be available to the builder - * implementation dependant @@ -210,7 +221,7 @@ } /** Loader to use when searching for classes to initialize. - * If specified, this loader is going to be used to load {@link Fn.Presenter} + * If specified, this loader is going to be used to load {@link Presenter} * and {@link Contexts#fillInByProviders(java.lang.Class, org.netbeans.html.context.spi.Contexts.Builder) fill} {@link BrwsrCtx} in. * Specifying special classloader may be useful in modular systems, * like OSGi, where one needs to load classes from many otherwise independent @@ -272,13 +283,16 @@ final ClassLoader activeLoader; if (loader != null) { - if (!FnUtils.isJavaScriptCapable(loader)) { + if (!FnContext.isJavaScriptCapable(loader)) { throw new IllegalStateException("Loader cannot resolve @JavaScriptBody: " + loader); } activeLoader = loader; - } else if (FnUtils.isJavaScriptCapable(myCls.getClassLoader())) { + } else if (FnContext.isJavaScriptCapable(myCls.getClassLoader())) { activeLoader = myCls.getClassLoader(); } else { + if (!FnContext.isAsmPresent()) { + throw new IllegalStateException("Cannot find asm-5.0.jar classes!"); + } FImpl impl = new FImpl(myCls.getClassLoader()); activeLoader = FnUtils.newLoader(impl, dfnr, myCls.getClassLoader().getParent()); } @@ -297,7 +311,7 @@ if (browserClass != null) { browserClass[0] = newClazz; } - Contexts.Builder cb = Contexts.newBuilder(); + Contexts.Builder cb = Contexts.newBuilder(context); if (!Contexts.fillInByProviders(newClazz, cb)) { LOG.log(Level.WARNING, "Using empty technology for {0}", newClazz); } diff -r 5bea583947da -r de02b7c13379 boot/src/main/java/net/java/html/js/package.html --- a/boot/src/main/java/net/java/html/js/package.html Tue Dec 09 07:23:22 2014 +0100 +++ b/boot/src/main/java/net/java/html/js/package.html Tue Dec 09 08:33:22 2014 +0100 @@ -197,7 +197,41 @@ passing of arrays works OK. It is however good to keep it in mind to avoid unwanted surprises. +

Instance Reference to JavaScript Object

+ + When writing wrappers around existing JavaScript libraries, it may be + useful to hold a reference to some JavaScript object from a Java + instance and use it later. +
+class WrapperAroundJsObj {
+  private final Object js;
+
+  WrapperAroundJsObj() {
+    js = initValue();
+  }
+
+  public void set(int v) {
+    setValue(js, v);
+  }
+
+  {@link net.java.html.js.JavaScriptBody @JavaScriptBody}(args = {}, body = "return { value : 0 };")
+  private static native Object initValue();
+
+  {@link net.java.html.js.JavaScriptBody @JavaScriptBody}(
+    args = { "js", "v" }, body = "js.value = v;", wait4js = false
+  )
+  private static native void setValue(Object js, int v);
+}            
+
+ The type of the Java reference is {@link java.lang.Object}. + From a Java perspective it has no additional methods or fields, however + its properties can be manipulated from JavaScript. Send the object back + to JavaScript by passing it as a parameter of some method + (like the setValue one) and perform necessary JavaScript + calls or changes on it. +

Post Process Classes

+ Classes with {@link net.java.html.js.JavaScriptBody} annotated methods need to be post processed before they can be used - e.g. their native @@ -257,7 +291,9 @@ will create a special classloader to that does the processing before loading the bytecode into the virtual machine.

- The options are rich, however to avoid any troubles, it is recommended + The options are rich, however to avoid any troubles (as the runtime + processing needs to also include asm-5.0.jar on application + classpath), it is recommended to perform the compile time processing.

Getting Started

diff -r 5bea583947da -r de02b7c13379 boot/src/main/java/org/netbeans/html/boot/impl/FnContext.java --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnContext.java Tue Dec 09 07:23:22 2014 +0100 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnContext.java Tue Dec 09 08:33:22 2014 +0100 @@ -45,8 +45,10 @@ import java.io.Closeable; import java.io.Flushable; import java.io.IOException; +import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.html.boot.spi.Fn; +import org.objectweb.asm.Opcodes; /** * @@ -60,6 +62,33 @@ DUMMY.prev = DUMMY; } + public static boolean isJavaScriptCapable(ClassLoader l) { + if (l instanceof JsClassLoader) { + return true; + } + if (l.getResource("META-INF/net.java.html.js.classes") != null) { + return false; + } + return true; + } + + public static boolean isAsmPresent() { + Throwable t; + try { + Class.forName(Opcodes.class.getName()); + return true; + } catch (LinkageError ex) { + t = ex; + } catch (ClassNotFoundException ex) { + t = ex; + } + LOG.log(Level.SEVERE, "When using @JavaScriptBody methods, one needs to either:"); + LOG.log(Level.SEVERE, " - include asm-5.0.jar on runtime classpath"); + LOG.log(Level.SEVERE, " - post process classes, see http://bits.netbeans.org/html+java/dev/net/java/html/js/package-summary.html#post-process"); + LOG.log(Level.SEVERE, "Cannot initialize asm-5.0.jar!", t); + return false; + } + private Object prev; private final Fn.Presenter current; private FnContext(Fn.Presenter prevP, Fn.Presenter newP) { diff -r 5bea583947da -r de02b7c13379 boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java Tue Dec 09 07:23:22 2014 +0100 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java Tue Dec 09 08:33:22 2014 +0100 @@ -108,16 +108,6 @@ return bytecode; } - public static boolean isJavaScriptCapable(ClassLoader l) { - if (l instanceof JsClassLoader) { - return true; - } - if (l.getResource("META-INF/net.java.html.js.classes") != null) { - return false; - } - return true; - } - public static boolean isValid(Fn fn) { return fn != null && fn.isValid(); } diff -r 5bea583947da -r de02b7c13379 context/src/main/java/net/java/html/BrwsrCtx.java --- a/context/src/main/java/net/java/html/BrwsrCtx.java Tue Dec 09 07:23:22 2014 +0100 +++ b/context/src/main/java/net/java/html/BrwsrCtx.java Tue Dec 09 08:33:22 2014 +0100 @@ -47,6 +47,7 @@ import org.netbeans.html.context.impl.CtxAccssr; import org.netbeans.html.context.impl.CtxImpl; import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.context.spi.Contexts.Id; /** Represents context where the net.java.html.json.Model * and other objects @@ -54,7 +55,12 @@ * The context is also associated with the actual HTML technology * in the HTML page - there is likely to be different context for * knockout.js and different one - * for angular. + * for angular. Since version 1.1 + * the content of contexts can be selected by registering + * implementations under specific + * {@link Id technology identifiers} and requesting them during + * {@link Contexts#newBuilder(java.lang.Object...) construction} of the + * context. * * @author Jaroslav Tulach */ diff -r 5bea583947da -r de02b7c13379 context/src/main/java/org/netbeans/html/context/impl/CtxImpl.java --- a/context/src/main/java/org/netbeans/html/context/impl/CtxImpl.java Tue Dec 09 07:23:22 2014 +0100 +++ b/context/src/main/java/org/netbeans/html/context/impl/CtxImpl.java Tue Dec 09 08:33:22 2014 +0100 @@ -44,8 +44,10 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import net.java.html.BrwsrCtx; +import org.netbeans.html.context.spi.Contexts; /** Implementation detail. Holds list of technologies for particular * {@link BrwsrCtx}. @@ -54,13 +56,15 @@ */ public final class CtxImpl { private final List> techs; + private final Object[] context; - public CtxImpl() { - techs = new ArrayList>(); + public CtxImpl(Object[] context) { + this(context, new ArrayList>()); } - private CtxImpl(List> techs) { + private CtxImpl(Object[] context, List> techs) { this.techs = techs; + this.context = context; } public static Tech find(BrwsrCtx context, Class technology) { @@ -74,9 +78,9 @@ } public BrwsrCtx build() { - Collections.sort(techs); + Collections.sort(techs, new BindCompare()); final List> arr = Collections.unmodifiableList(techs); - CtxImpl impl = new CtxImpl(arr); + CtxImpl impl = new CtxImpl(context, arr); BrwsrCtx ctx = CtxAccssr.getDefault().newContext(impl); return ctx; } @@ -85,7 +89,7 @@ techs.add(new Bind(type, impl, priority)); } - private static final class Bind implements Comparable> { + private static final class Bind { private final Class clazz; private final Tech impl; private final int priority; @@ -97,16 +101,39 @@ } @Override - public int compareTo(Bind o) { - if (priority != o.priority) { - return priority - o.priority; - } - return clazz.getName().compareTo(o.clazz.getName()); - } - - @Override public String toString() { return "Bind{" + "clazz=" + clazz + "@" + clazz.getClassLoader() + ", impl=" + impl + ", priority=" + priority + '}'; } } + + private final class BindCompare implements Comparator> { + boolean isPrefered(Bind b) { + final Class implClazz = b.impl.getClass(); + Contexts.Id id = implClazz.getAnnotation(Contexts.Id.class); + if (id == null) { + return false; + } + for (String v : id.value()) { + for (Object c : context) { + if (v.equals(c)) { + return true; + } + } + } + return false; + } + + @Override + public int compare(Bind o1, Bind o2) { + boolean p1 = isPrefered(o1); + boolean p2 = isPrefered(o2); + if (p1 != p2) { + return p1 ? -1 : 1; + } + if (o1.priority != o2.priority) { + return o1.priority - o2.priority; + } + return o1.clazz.getName().compareTo(o2.clazz.getName()); + } + } // end of BindCompare } diff -r 5bea583947da -r de02b7c13379 context/src/main/java/org/netbeans/html/context/spi/Contexts.java --- a/context/src/main/java/org/netbeans/html/context/spi/Contexts.java Tue Dec 09 07:23:22 2014 +0100 +++ b/context/src/main/java/org/netbeans/html/context/spi/Contexts.java Tue Dec 09 08:33:22 2014 +0100 @@ -42,6 +42,10 @@ */ package org.netbeans.html.context.spi; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ServiceLoader; import net.java.html.BrwsrCtx; import org.netbeans.html.context.impl.CtxImpl; @@ -59,11 +63,23 @@ /** Creates new, empty builder for creation of {@link BrwsrCtx}. At the * end call the {@link Builder#build()} method to generate the context. - * + * + * @param context instances of various classes or names of {@link Id technologies} + * to be preferred and used in the built {@link BrwsrCtx context}. + * @return new instance of the builder + * @since 1.1 + */ + public static Builder newBuilder(Object... context) { + return new Builder(context); + } + /** Creates new, empty builder for creation of {@link BrwsrCtx}. At the + * end call the {@link Builder#build()} method to generate the context. + * Simply calls {@link #newBuilder(java.lang.Object...) newBuilder(new Object[0])}. + * * @return new instance of the builder */ public static Builder newBuilder() { - return new Builder(); + return newBuilder(new Object[0]); } /** Seeks for the specified technology in the provided context. @@ -118,6 +134,23 @@ } return found; } + + /** Identifies the technologies passed to {@link Builder context builder} + * by a name. Each implementation of a technology + * {@link Builder#register(java.lang.Class, java.lang.Object, int) registered into a context} + * can be annotated with a name (or multiple names). Such implementation + * will later be + * {@link Contexts#fillInByProviders(java.lang.Class, org.netbeans.html.context.spi.Contexts.Builder) preferred during lookup} + * if their name(s) has been requested in when + * {@link Contexts#newBuilder(java.lang.Object...) creating a context}. + * @since 1.1 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Id { + /** Identifier(s) for the implementation. */ + public String[] value(); + } /** Implementors of various HTML technologies should * register their implementation via org.openide.util.lookup.ServiceProvider, so @@ -152,9 +185,10 @@ * @author Jaroslav Tulach */ public static final class Builder { - private final CtxImpl impl = new CtxImpl(); + private final CtxImpl impl; - Builder() { + public Builder(Object[] context) { + this.impl = new CtxImpl(context); } /** Registers new technology into the context. Each technology is diff -r 5bea583947da -r de02b7c13379 context/src/test/java/net/java/html/BrwsrCtxTest.java --- a/context/src/test/java/net/java/html/BrwsrCtxTest.java Tue Dec 09 07:23:22 2014 +0100 +++ b/context/src/test/java/net/java/html/BrwsrCtxTest.java Tue Dec 09 08:33:22 2014 +0100 @@ -44,6 +44,7 @@ import org.netbeans.html.context.spi.Contexts; import static org.testng.Assert.*; +import org.testng.annotations.Test; /** * @@ -72,4 +73,48 @@ assertTrue(arr[0], "Runnable was executed"); } + + @Test public void defaultOrderOfRegistrations() { + BrwsrCtx ctx = registerRs(Contexts.newBuilder()); + Class clazz = Contexts.find(ctx, Runnable.class).getClass(); + assertEquals(clazz, R1.class, "R1 is registered at value 10"); + } + + @Test public void preferOne() { + BrwsrCtx ctx = registerRs(Contexts.newBuilder("one")); + Class clazz = Contexts.find(ctx, Runnable.class).getClass(); + assertEquals(clazz, R1.class, "R1 is registered at value 10"); + } + + @Test public void preferTwo() { + BrwsrCtx ctx = registerRs(Contexts.newBuilder("two")); + Class clazz = Contexts.find(ctx, Runnable.class).getClass(); + assertEquals(clazz, R2.class, "R2 is preferred"); + } + + @Test public void preferBoth() { + BrwsrCtx ctx = registerRs(Contexts.newBuilder("one", "two")); + Class clazz = Contexts.find(ctx, Runnable.class).getClass(); + assertEquals(clazz, R1.class, "R1 is registered at value 10"); + } + + private static BrwsrCtx registerRs(Contexts.Builder b) { + b.register(Runnable.class, new R1(), 10); + b.register(Runnable.class, new R2(), 20); + return b.build(); + } + + @Contexts.Id("one") + static final class R1 implements Runnable { + @Override + public void run() { + } + } + @Contexts.Id("two") + static final class R2 implements Runnable { + @Override + public void run() { + } + } + } \ No newline at end of file diff -r 5bea583947da -r de02b7c13379 geo/src/main/java/org/netbeans/html/geo/spi/GLProvider.java --- a/geo/src/main/java/org/netbeans/html/geo/spi/GLProvider.java Tue Dec 09 07:23:22 2014 +0100 +++ b/geo/src/main/java/org/netbeans/html/geo/spi/GLProvider.java Tue Dec 09 08:33:22 2014 +0100 @@ -47,7 +47,7 @@ import net.java.html.geo.Position; import net.java.html.geo.Position.Handle; import net.java.html.geo.Position.Coordinates; -import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.context.spi.Contexts.Builder; import org.netbeans.html.geo.impl.Accessor; import org.openide.util.lookup.ServiceProvider; @@ -55,7 +55,7 @@ * Subclass this class, implement its method and register it into the system. * You can either use {@link ServiceProvider} to register globally, or * one can register into {@link BrwsrCtx} (via - * {@link Contexts.Builder#register(java.lang.Class, java.lang.Object, int) context builder}). + * {@link Builder#register(java.lang.Class, java.lang.Object, int) context builder}). *

* There is default system provider (used as a fallback) based on * @@ -93,11 +93,11 @@ * * * @author Jaroslav Tulach - * @param your choosen type to represent one query (one time) or watch (repeated) request - + * @param your chosen type to represent one query (one time) or watch (repeated) request - * this type is used in {@link #start(org.netbeans.html.geo.spi.GLProvider.Query) start} * and {@link #stop(java.lang.Object) stop} methods. * - * @param your choosen type to represent geolocation coordinates - + * @param your chosen type to represent geolocation coordinates - * use in many methods in this class like {@link #latitude(java.lang.Object)} and * {@link #longitude(java.lang.Object)}. * @@ -263,7 +263,7 @@ /** * Is this one time or repeated request? Mimics value provided in - * {@link Handle#Handle(boolean) constructor}. + * {@link Handle constructor}. * * @return true if this is one time request, false if the request is * permanent (up until {@link Handle#stop() } is called). diff -r 5bea583947da -r de02b7c13379 json-tck/src/main/java/org/netbeans/html/json/tck/KnockoutTCK.java --- a/json-tck/src/main/java/org/netbeans/html/json/tck/KnockoutTCK.java Tue Dec 09 07:23:22 2014 +0100 +++ b/json-tck/src/main/java/org/netbeans/html/json/tck/KnockoutTCK.java Tue Dec 09 08:33:22 2014 +0100 @@ -55,6 +55,7 @@ import net.java.html.json.tests.WebSocketTest; import org.netbeans.html.context.spi.Contexts.Builder; import org.openide.util.lookup.ServiceProvider; +import org.testng.annotations.Factory; /** Entry point for providers of different HTML binding technologies (like * Knockout.js in JavaFX's WebView). Sample usage: @@ -67,7 +68,7 @@ // use {@link Builder}.{@link Builder#build() build}(); } - {@code @}{@link org.testng.annotations.Factory} public static Object[] create() { + {@code @}{@link Factory} public static Object[] create() { return VMTest.newTests().withClasses({@link KnockoutTCK#testClasses}()).build(); } } diff -r 5bea583947da -r de02b7c13379 json/src/main/java/net/java/html/json/Model.java --- a/json/src/main/java/net/java/html/json/Model.java Tue Dec 09 07:23:22 2014 +0100 +++ b/json/src/main/java/net/java/html/json/Model.java Tue Dec 09 08:33:22 2014 +0100 @@ -50,19 +50,19 @@ import java.util.List; /** Defines a model class that represents a single - * JSON-like object + * JSON-like object * named {@link #className()}. The generated class contains * getters and setters for properties defined via {@link #properties()} and * getters for other, derived properties defined by annotating methods * of this class by {@link ComputedProperty}. Each property * can be of primitive type, an {@link Enum enum type} or (in order to create - * nested JSON structure) + * nested JSON structure) * of another {@link Model class generated by @Model} annotation. Each property * can either represent a single value or be an array of its values. *

* The {@link #className() generated class}'s toString method * converts the state of the object into - * JSON format. One can + * JSON format. One can * use {@link Models#parse(net.java.html.BrwsrCtx, java.lang.Class, java.io.InputStream)} * method to read the JSON text stored in a file or other stream back into the Java model. * One can also use {@link OnReceive @OnReceive} annotation to read the model @@ -109,7 +109,7 @@ * new Address("Markoušovice", "Úpice"), * new Address("V Parku", "Praha") * ); - * // p.toString() then returns equivalent of following JSON object + * // p.toString() then returns equivalent of following JSON object * { * "firstName" : "Jaroslav", * "lastName" : "Tulach", @@ -120,10 +120,10 @@ * } * *

- * In case you are using Knockout technology + * In case you are using Knockout technology * for Java then you can associate such model object with surrounding HTML page by * calling: p.applyBindings();. The page can then use regular - * Knockout bindings to reference your + * Knockout bindings to reference your * model and create dynamic connection between your model in Java and * live DOM structure in the browser: *

@@ -132,7 +132,58 @@
  *   Lives in <span data-bind="text: town"/>
  * </div>
  * 
- *

+ * + *

Access Raw Knockout Observables

+ * + * One can obtain raw JavaScript object representing the + * instance of {@link Model model class} (with appropriate + * Knockout observable properties) + * by calling {@link Models#toRaw(java.lang.Object) Models.toRaw(p)}. For + * example here is a way to obtain the value of fullName property + * (inefficient as it switches between Java and JavaScript back and forth, + * but functional and instructive) via a JavaScript call: + *
+ * {@link net.java.html.js.JavaScriptBody @JavaScriptBody}(args = "raw", javacall = true, body =
+ *   "return raw.fullName();" // yes, the Knockout property is a function
+ * )
+ * static native String jsFullName(Object raw);
+ * // and later
+ * Person p = ...;
+ * String fullName = jsFullName({@link Models#toRaw(java.lang.Object) Models.toRaw(p)});
+ * 
+ * The above shows how to read a value from Knockout + * observable. There is a way to change the value too: + * One can pass a parameter to the property-function and then + * it acts like a setter (of course not in the case of read only fullName property, + * but for firstName or lastName the setter is + * available). Everything mentioned in this paragraph applies only when + * Knockout technology is active + * other technologies may behave differently. + * + *

Copy to Plain JSON

+ * There is a way to pass a value of a Java {@link Model model class} instance + * by copy and convert + * the {@link Model the whole object} into plain + * JSON. Just + * print it as a string and parse it in JavaScript: + *
+ * {@link net.java.html.js.JavaScriptBody @JavaScriptBody}(args = { "txt" }, body =
+ *   "return JSON.parse(txt);"
+ * )
+ * private static native Object parseJSON(String txt);
+ * 
+ * public static Object toPlainJSON(Object model) {
+ *   return parseJSON(model.toString());
+ * }
+ * 
+ * The newly returned instance is a one time copy of the original model and is no longer + * connected to it. The copy based behavior is independent on any + * particular technology and should work + * in Knockout as well as other + * technology implementations. + * + *

References

+ * * Visit an on-line demo * to see a histogram driven by the {@link Model} annotation or try * a little math test. diff -r 5bea583947da -r de02b7c13379 json/src/main/java/org/netbeans/html/json/spi/Technology.java --- a/json/src/main/java/org/netbeans/html/json/spi/Technology.java Tue Dec 09 07:23:22 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/spi/Technology.java Tue Dec 09 08:33:22 2014 +0100 @@ -45,10 +45,17 @@ import net.java.html.BrwsrCtx; 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}) * and particular technology like knockout.js * 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 + * org.netbeans.html:ko4j module which registers + * a knockout.js + * implementation called ko4j. * * @author Jaroslav Tulach */ diff -r 5bea583947da -r de02b7c13379 json/src/main/java/org/netbeans/html/json/spi/Transfer.java --- a/json/src/main/java/org/netbeans/html/json/spi/Transfer.java Tue Dec 09 07:23:22 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/spi/Transfer.java Tue Dec 09 08:33:22 2014 +0100 @@ -45,9 +45,17 @@ import java.io.IOException; import java.io.InputStream; import org.netbeans.html.context.spi.Contexts.Builder; +import org.netbeans.html.context.spi.Contexts.Id; /** A {@link Builder service provider interface} responsible for * conversion of JSON objects to Java ones and vice-versa. + * Since introduction of {@link Id technology identifiers} one can choose between + * different background implementations to handle the conversion and + * communication requests. The known providers include + * org.netbeans.html:ko4j module which registers + * a native browser implementation called xhr, and a + * org.netbeans.html:ko-ws-tyrus module which registers + * Java based implementation named tyrus. * * @author Jaroslav Tulach */ diff -r 5bea583947da -r de02b7c13379 json/src/main/java/org/netbeans/html/json/spi/WSTransfer.java --- a/json/src/main/java/org/netbeans/html/json/spi/WSTransfer.java Tue Dec 09 07:23:22 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/spi/WSTransfer.java Tue Dec 09 08:33:22 2014 +0100 @@ -44,9 +44,17 @@ import net.java.html.BrwsrCtx; import org.netbeans.html.context.spi.Contexts.Provider; +import org.netbeans.html.context.spi.Contexts.Id; /** Interface for providers of WebSocket protocol. Register into a - * {@link BrwsrCtx context} in your own {@link Provider} + * {@link BrwsrCtx context} in your own {@link Provider}. + * Since introduction of {@link Id technology identifiers} one can choose between + * different background implementations to handle the conversion and + * communication requests. The known providers include + * org.netbeans.html:ko4j module which registers + * a native browser implementation called websocket, and a + * org.netbeans.html:ko-ws-tyrus module which registers + * Java based implementation named tyrus. * * @author Jaroslav Tulach * @param internal implementation type representing the socket diff -r 5bea583947da -r de02b7c13379 json/src/test/java/net/java/html/json/MapModelTest.java --- a/json/src/test/java/net/java/html/json/MapModelTest.java Tue Dec 09 07:23:22 2014 +0100 +++ b/json/src/test/java/net/java/html/json/MapModelTest.java Tue Dec 09 08:33:22 2014 +0100 @@ -47,6 +47,8 @@ import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; +import java.util.Iterator; +import java.util.ListIterator; import java.util.Map; import org.netbeans.html.context.spi.Contexts; import org.netbeans.html.json.spi.FunctionBinding; @@ -179,6 +181,62 @@ assertEquals(p.getSex(), Sex.FEMALE, "Changed"); } + + @Test public void removeViaIterator() { + People p = Models.bind(new People(), c); + p.getNicknames().add("One"); + p.getNicknames().add("Two"); + p.getNicknames().add("Three"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("nicknames"); + assertNotNull(o, "List registered in the model"); + assertEquals(o.getClass(), One.class); + One one = (One)o; + + + assertEquals(one.changes, 0, "No change"); + + Iterator it = p.getNicknames().iterator(); + assertEquals(it.next(), "One"); + assertEquals(it.next(), "Two"); + it.remove(); + assertEquals(it.next(), "Three"); + assertFalse(it.hasNext()); + + + assertEquals(one.changes, 1, "One change"); + } + + @Test public void removeViaListIterator() { + People p = Models.bind(new People(), c); + p.getNicknames().add("One"); + p.getNicknames().add("Two"); + p.getNicknames().add("Three"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("nicknames"); + assertNotNull(o, "List registered in the model"); + assertEquals(o.getClass(), One.class); + One one = (One)o; + + + assertEquals(one.changes, 0, "No change"); + + ListIterator it = p.getNicknames().listIterator(1); + assertEquals(it.next(), "Two"); + it.remove(); + assertEquals(it.next(), "Three"); + assertFalse(it.hasNext()); + + + assertEquals(one.changes, 1, "One change"); + + it.set("3"); + assertEquals(p.getNicknames().get(1), "3"); + + assertEquals(one.changes, 2, "Snd change"); + } static final class One { int changes; diff -r 5bea583947da -r de02b7c13379 ko-felix-test/src/main/java/org/netbeans/html/ko/felix/test/KnockoutFelixTCKImpl.java --- a/ko-felix-test/src/main/java/org/netbeans/html/ko/felix/test/KnockoutFelixTCKImpl.java Tue Dec 09 07:23:22 2014 +0100 +++ b/ko-felix-test/src/main/java/org/netbeans/html/ko/felix/test/KnockoutFelixTCKImpl.java Tue Dec 09 08:33:22 2014 +0100 @@ -46,6 +46,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; @@ -140,16 +141,9 @@ @Override public BrwsrCtx createContext() { try { - Class fxCls = loadOSGiClass( - "org.netbeans.html.ko4j.FXContext", - FrameworkUtil.getBundle(KnockoutFelixTCKImpl.class).getBundleContext() - ); - final Constructor cnstr = fxCls.getConstructor(Fn.Presenter.class); - cnstr.setAccessible(true); - Object fx = cnstr.newInstance(browserContext); Contexts.Builder cb = Contexts.newBuilder(). - register(Technology.class, (Technology)fx, 10). - register(Transfer.class, (Transfer)fx, 10). + register(Technology.class, (Technology)osgiInstance("KOTech"), 10). + register(Transfer.class, (Transfer)osgiInstance("KOTransfer"), 10). register(Executor.class, (Executor)browserContext, 10); // if (fx.areWebSocketsSupported()) { // cb.register(WSTransfer.class, fx, 10); @@ -160,6 +154,17 @@ } } + private Object osgiInstance(String simpleName) throws IllegalAccessException, SecurityException, IllegalArgumentException, Exception, NoSuchMethodException, InstantiationException, InvocationTargetException { + Class fxCls = loadOSGiClass( + "org.netbeans.html.ko4j." + simpleName, + FrameworkUtil.getBundle(KnockoutFelixTCKImpl.class).getBundleContext() + ); + final Constructor cnstr = fxCls.getDeclaredConstructor(); + cnstr.setAccessible(true); + Object fx = cnstr.newInstance(); + return fx; + } + @Override public Object createJSON(Map values) { JSONObject json = new JSONObject(); diff -r 5bea583947da -r de02b7c13379 ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java --- a/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java Tue Dec 09 07:23:22 2014 +0100 +++ b/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java Tue Dec 09 08:33:22 2014 +0100 @@ -46,6 +46,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; @@ -135,16 +136,9 @@ @Override public BrwsrCtx createContext() { try { - Class fxCls = loadOSGiClass( - "org.netbeans.html.ko4j.FXContext", - FrameworkUtil.getBundle(KnockoutEquinoxTCKImpl.class).getBundleContext() - ); - final Constructor cnstr = fxCls.getConstructor(Fn.Presenter.class); - cnstr.setAccessible(true); - Object fx = cnstr.newInstance(browserContext); Contexts.Builder cb = Contexts.newBuilder(). - register(Technology.class, (Technology)fx, 10). - register(Transfer.class, (Transfer)fx, 10). + register(Technology.class, (Technology)osgiInstance("KOTech"), 10). + register(Transfer.class, (Transfer)osgiInstance("KOTransfer"), 10). register(Executor.class, (Executor)browserContext, 10); // if (fx.areWebSocketsSupported()) { // cb.register(WSTransfer.class, fx, 10); @@ -154,6 +148,16 @@ throw new IllegalStateException(ex); } } + private Object osgiInstance(String simpleName) throws IllegalAccessException, SecurityException, IllegalArgumentException, Exception, NoSuchMethodException, InstantiationException, InvocationTargetException { + Class fxCls = loadOSGiClass( + "org.netbeans.html.ko4j." + simpleName, + FrameworkUtil.getBundle(KnockoutEquinoxTCKImpl.class).getBundleContext() + ); + final Constructor cnstr = fxCls.getDeclaredConstructor(); + cnstr.setAccessible(true); + Object fx = cnstr.newInstance(); + return fx; + } @Override public Object createJSON(Map values) { diff -r 5bea583947da -r de02b7c13379 ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java --- a/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java Tue Dec 09 07:23:22 2014 +0100 +++ b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java Tue Dec 09 08:33:22 2014 +0100 @@ -82,6 +82,7 @@ * * @author Jaroslav Tulach */ +@Contexts.Id("tyrus") @ServiceProvider(service = Contexts.Provider.class) public final class TyrusContext implements Contexts.Provider, WSTransfer, Transfer { diff -r 5bea583947da -r de02b7c13379 ko4j/pom.xml --- a/ko4j/pom.xml Tue Dec 09 07:23:22 2014 +0100 +++ b/ko4j/pom.xml Tue Dec 09 08:33:22 2014 +0100 @@ -15,6 +15,7 @@ UTF-8 org.netbeans.html.ko4j + none @@ -92,7 +93,6 @@ javax.servlet javax.servlet-api test - 3.1.0 Binds net.java.html.json APIs together with knockout.js diff -r 5bea583947da -r de02b7c13379 ko4j/src/main/java/org/netbeans/html/ko4j/FXContext.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/FXContext.java Tue Dec 09 07:23:22 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,243 +0,0 @@ -/** - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * Contributor(s): - * - * The Original Software is NetBeans. The Initial Developer of the Original - * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - */ -package org.netbeans.html.ko4j; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; -import java.util.logging.Logger; -import org.netbeans.html.boot.spi.Fn; -import org.netbeans.html.json.spi.FunctionBinding; -import org.netbeans.html.json.spi.JSONCall; -import org.netbeans.html.json.spi.PropertyBinding; -import org.netbeans.html.json.spi.Technology; -import org.netbeans.html.json.spi.Transfer; -import org.netbeans.html.json.spi.WSTransfer; - -/** This is an implementation package - just - * include its JAR on classpath and use official {@link Context} API - * to access the functionality. - *

- * - * @author Jaroslav Tulach - */ -final class FXContext -implements Technology.BatchInit, Technology.ValueMutated, -Transfer, WSTransfer { - static final Logger LOG = Logger.getLogger(FXContext.class.getName()); - private Object[] jsObjects; - private int jsIndex; - - public FXContext(Fn.Presenter browserContext) { - } - - @Override - public Object wrapModel(Object model, PropertyBinding[] propArr, FunctionBinding[] funcArr) { - String[] propNames = new String[propArr.length]; - boolean[] propReadOnly = new boolean[propArr.length]; - Object[] propValues = new Object[propArr.length]; - for (int i = 0; i < propNames.length; i++) { - propNames[i] = propArr[i].getPropertyName(); - propReadOnly[i] = propArr[i].isReadOnly(); - propValues[i] = propArr[i].getValue(); - } - String[] funcNames = new String[funcArr.length]; - for (int i = 0; i < funcNames.length; i++) { - funcNames[i] = funcArr[i].getFunctionName(); - } - Object ret = getJSObject(); - Knockout.wrapModel(new Knockout(model, ret, propArr, funcArr), - ret, - propNames, propReadOnly, propValues, - funcNames - ); - return ret; - } - - private Object getJSObject() { - int len = 64; - if (jsObjects != null && jsIndex < (len = jsObjects.length)) { - Object ret = jsObjects[jsIndex]; - jsObjects[jsIndex] = null; - jsIndex++; - return ret; - } - jsObjects = Knockout.allocJS(len * 2); - jsIndex = 1; - Object ret = jsObjects[0]; - jsObjects[0] = null; - return ret; - } - - @Override - public Object wrapModel(Object model) { - throw new UnsupportedOperationException(); - } - - @Override - public void bind(PropertyBinding b, Object model, Object data) { - throw new UnsupportedOperationException(); - } - - @Override - public void valueHasMutated(Object data, String propertyName) { - Knockout.cleanUp(); - Knockout.valueHasMutated(data, propertyName, null, null); - } - - @Override - public void valueHasMutated(Object data, String propertyName, Object oldValue, Object newValue) { - Knockout.cleanUp(); - Knockout.valueHasMutated(data, propertyName, oldValue, newValue); - } - - @Override - public void expose(FunctionBinding fb, Object model, Object d) { - throw new UnsupportedOperationException(); - } - - @Override - public void applyBindings(Object data) { - Object ko = Knockout.applyBindings(data); - if (ko instanceof Knockout) { - ((Knockout)ko).hold(); - } - } - - @Override - public Object wrapArray(Object[] arr) { - return arr; - } - - @Override - public void extract(Object obj, String[] props, Object[] values) { - LoadJSON.extractJSON(obj, props, values); - } - - @Override - public void loadJSON(final JSONCall call) { - if (call.isJSONP()) { - String me = LoadJSON.createJSONP(call); - LoadJSON.loadJSONP(call.composeURL(me), me); - } else { - String data = null; - if (call.isDoOutput()) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - call.writeData(bos); - data = new String(bos.toByteArray(), "UTF-8"); - } catch (IOException ex) { - call.notifyError(ex); - } - } - LoadJSON.loadJSON(call.composeURL(null), call, call.getMethod(), data); - } - } - - @Override - public M toModel(Class modelClass, Object data) { - return modelClass.cast(Knockout.toModel(data)); - } - - @Override - public Object toJSON(InputStream is) throws IOException { - StringBuilder sb = new StringBuilder(); - InputStreamReader r = new InputStreamReader(is); - for (;;) { - int ch = r.read(); - if (ch == -1) { - break; - } - sb.append((char)ch); - } - return LoadJSON.parse(sb.toString()); - } - - @Override - public void runSafe(final Runnable r) { - LOG.warning("Technology.runSafe has been deprecated. Use BrwsrCtx.execute!"); - r.run(); - } - - @Override - public LoadWS open(String url, JSONCall onReply) { - return new LoadWS(onReply, url); - } - - @Override - public void send(LoadWS socket, JSONCall data) { - socket.send(data); - } - - @Override - public void close(LoadWS socket) { - socket.close(); - } - - boolean areWebSocketsSupported() { - return Knockout.areWebSocketsSupported(); - } - - private static final class TrueFn extends Fn implements Fn.Presenter { - @Override - public Object invoke(Object thiz, Object... args) throws Exception { - return true; - } - - @Override - public Fn defineFn(String code, String... names) { - return this; - } - - @Override - public void displayPage(URL page, Runnable onPageLoad) { - } - - @Override - public void loadScript(Reader code) throws Exception { - } - } // end of TrueFn -} diff -r 5bea583947da -r de02b7c13379 ko4j/src/main/java/org/netbeans/html/ko4j/KO4J.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KO4J.java Tue Dec 09 07:23:22 2014 +0100 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KO4J.java Tue Dec 09 08:33:22 2014 +0100 @@ -42,7 +42,9 @@ */ package org.netbeans.html.ko4j; +import java.util.logging.Logger; import net.java.html.json.Model; +import net.java.html.json.OnReceive; import org.netbeans.html.boot.spi.Fn; import org.netbeans.html.context.spi.Contexts; import org.netbeans.html.context.spi.Contexts.Provider; @@ -54,35 +56,49 @@ /** Support for knockout.js * and its Java binding via {@link Model model classes}. * Registers {@link Provider}, so {@link java.util.ServiceLoader} can find it. + * The provider registers following technologies: + *
    + *
  • ko4j - bindings for knockout.js + * and the classes generated by the {@link Model} annotation. + *
  • + *
  • xhr - XMLHttpRequest + * based implementation for REST calls + * (GET, PUT, POST, DELETE methods) + * for {@link OnReceive} annotation. + *
  • + *
  • websocket - + * native browser websockets + * based implementation for {@link OnReceive} annotation and its WebSocket + * subprotocol. + *
  • + *
* * @author Jaroslav Tulach * @since 0.7 */ @ServiceProvider(service = Provider.class) public final class KO4J implements Provider { - private final Fn.Presenter presenter; - private FXContext c; + static final Logger LOG = Logger.getLogger(KOSockets.class.getName()); + private KOTech ko4j; + private KOTransfer trans; + private KOSockets socks; public KO4J() { this(null); } - + + @Deprecated public KO4J(Fn.Presenter presenter) { - this.presenter = presenter; - } - - private FXContext getKO() { - if (c == null) { - c = new FXContext(presenter == null ? Fn.activePresenter() : presenter); - } - return c; } /** Return instance of the knockout.js for Java technology. * @return non-null instance */ public Technology knockout() { - return getKO(); + if (ko4j == null) { + ko4j = new KOTech(); + } + return ko4j; } /** Browser based implementation of transfer interface. Uses @@ -91,7 +107,10 @@ * @return non-null instance */ public Transfer transfer() { - return getKO(); + if (trans == null) { + trans = new KOTransfer(); + } + return trans; } /** Returns browser based implementation of websocket transfer. @@ -102,7 +121,13 @@ * WebSocket object in the browser */ public WSTransfer websockets() { - return getKO().areWebSocketsSupported() ? getKO() : null; + if (!KOSockets.areWebSocketsSupported()) { + return null; + } + if (socks == null) { + socks = new KOSockets(); + } + return socks; } /** Registers technologies at position 100: diff -r 5bea583947da -r de02b7c13379 ko4j/src/main/java/org/netbeans/html/ko4j/KOSockets.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOSockets.java Tue Dec 09 08:33:22 2014 +0100 @@ -0,0 +1,81 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.ko4j; + +import net.java.html.js.JavaScriptBody; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.JSONCall; +import org.netbeans.html.json.spi.WSTransfer; + +/** This is an implementation package - just + * include its JAR on classpath and use official {@link Context} API + * to access the functionality. + * + * @author Jaroslav Tulach + */ +@Contexts.Id("websocket") +final class KOSockets +implements WSTransfer { + KOSockets() { + } + + @Override + public LoadWS open(String url, JSONCall onReply) { + return new LoadWS(onReply, url); + } + + @Override + public void send(LoadWS socket, JSONCall data) { + socket.send(data); + } + + @Override + public void close(LoadWS socket) { + socket.close(); + } + + @JavaScriptBody(args = {}, body = "if (window['WebSocket']) return true; else return false;") + static final boolean areWebSocketsSupported() { + return false; + } +} diff -r 5bea583947da -r de02b7c13379 ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java Tue Dec 09 08:33:22 2014 +0100 @@ -0,0 +1,155 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.ko4j; + +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.FunctionBinding; +import org.netbeans.html.json.spi.PropertyBinding; +import org.netbeans.html.json.spi.Technology; +import static org.netbeans.html.ko4j.KO4J.LOG; + +/** This is an implementation package - just + * include its JAR on classpath and use official {@link Context} API + * to access the functionality. + *

+ * + * @author Jaroslav Tulach + */ +@Contexts.Id("ko4j") +final class KOTech +implements Technology.BatchInit, Technology.ValueMutated { + private Object[] jsObjects; + private int jsIndex; + + public KOTech() { + } + + @Override + public Object wrapModel(Object model, PropertyBinding[] propArr, FunctionBinding[] funcArr) { + String[] propNames = new String[propArr.length]; + boolean[] propReadOnly = new boolean[propArr.length]; + Object[] propValues = new Object[propArr.length]; + for (int i = 0; i < propNames.length; i++) { + propNames[i] = propArr[i].getPropertyName(); + propReadOnly[i] = propArr[i].isReadOnly(); + propValues[i] = propArr[i].getValue(); + } + String[] funcNames = new String[funcArr.length]; + for (int i = 0; i < funcNames.length; i++) { + funcNames[i] = funcArr[i].getFunctionName(); + } + Object ret = getJSObject(); + Knockout.wrapModel(new Knockout(model, ret, propArr, funcArr), + ret, + propNames, propReadOnly, propValues, + funcNames + ); + return ret; + } + + private Object getJSObject() { + int len = 64; + if (jsObjects != null && jsIndex < (len = jsObjects.length)) { + Object ret = jsObjects[jsIndex]; + jsObjects[jsIndex] = null; + jsIndex++; + return ret; + } + jsObjects = Knockout.allocJS(len * 2); + jsIndex = 1; + Object ret = jsObjects[0]; + jsObjects[0] = null; + return ret; + } + + @Override + public Object wrapModel(Object model) { + throw new UnsupportedOperationException(); + } + + @Override + public void bind(PropertyBinding b, Object model, Object data) { + throw new UnsupportedOperationException(); + } + + @Override + public void valueHasMutated(Object data, String propertyName) { + Knockout.cleanUp(); + Knockout.valueHasMutated(data, propertyName, null, null); + } + + @Override + public void valueHasMutated(Object data, String propertyName, Object oldValue, Object newValue) { + Knockout.cleanUp(); + Knockout.valueHasMutated(data, propertyName, oldValue, newValue); + } + + @Override + public void expose(FunctionBinding fb, Object model, Object d) { + throw new UnsupportedOperationException(); + } + + @Override + public void applyBindings(Object data) { + Object ko = Knockout.applyBindings(data); + if (ko instanceof Knockout) { + ((Knockout)ko).hold(); + } + } + + @Override + public Object wrapArray(Object[] arr) { + return arr; + } + + @Override + public void runSafe(final Runnable r) { + LOG.warning("Technology.runSafe has been deprecated. Use BrwsrCtx.execute!"); + r.run(); + } + + @Override + public M toModel(Class modelClass, Object data) { + return modelClass.cast(Knockout.toModel(data)); + } +} diff -r 5bea583947da -r de02b7c13379 ko4j/src/main/java/org/netbeans/html/ko4j/KOTransfer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTransfer.java Tue Dec 09 08:33:22 2014 +0100 @@ -0,0 +1,104 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.ko4j; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.JSONCall; +import org.netbeans.html.json.spi.Transfer; + +/** This is an implementation package - just + * include its JAR on classpath and use official {@link Context} API + * to access the functionality. + *

+ * + * @author Jaroslav Tulach + */ +@Contexts.Id("xhr") +final class KOTransfer +implements Transfer { + KOTransfer() { + } + + @Override + public void extract(Object obj, String[] props, Object[] values) { + LoadJSON.extractJSON(obj, props, values); + } + + @Override + public void loadJSON(final JSONCall call) { + if (call.isJSONP()) { + String me = LoadJSON.createJSONP(call); + LoadJSON.loadJSONP(call.composeURL(me), me); + } else { + String data = null; + if (call.isDoOutput()) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + call.writeData(bos); + data = new String(bos.toByteArray(), "UTF-8"); + } catch (IOException ex) { + call.notifyError(ex); + } + } + LoadJSON.loadJSON(call.composeURL(null), call, call.getMethod(), data); + } + } + + @Override + public Object toJSON(InputStream is) throws IOException { + StringBuilder sb = new StringBuilder(); + InputStreamReader r = new InputStreamReader(is); + for (;;) { + int ch = r.read(); + if (ch == -1) { + break; + } + sb.append((char)ch); + } + return LoadJSON.parse(sb.toString()); + } +} diff -r 5bea583947da -r de02b7c13379 ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Tue Dec 09 07:23:22 2014 +0100 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Tue Dec 09 08:33:22 2014 +0100 @@ -229,9 +229,4 @@ return o; } } - - @JavaScriptBody(args = {}, body = "if (window['WebSocket']) return true; else return false;") - static final boolean areWebSocketsSupported() { - return false; - } } diff -r 5bea583947da -r de02b7c13379 ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java --- a/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java Tue Dec 09 07:23:22 2014 +0100 +++ b/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java Tue Dec 09 08:33:22 2014 +0100 @@ -153,12 +153,12 @@ @Override public BrwsrCtx createContext() { - FXContext fx = new FXContext(browserContext); + KO4J ko4j = new KO4J(); Contexts.Builder cb = Contexts.newBuilder(). - register(Technology.class, fx, 10). - register(Transfer.class, fx, 10); - if (fx.areWebSocketsSupported()) { - cb.register(WSTransfer.class, fx, 10); + register(Technology.class, ko4j.knockout(), 10). + register(Transfer.class, ko4j.transfer(), 10); + if (ko4j.websockets() != null) { + cb.register(WSTransfer.class, ko4j.websockets(), 10); } cb.register(Executor.class, (Executor)browserContext, 10); cb.register(Fn.Presenter.class, browserContext, 10); diff -r 5bea583947da -r de02b7c13379 pom.xml --- a/pom.xml Tue Dec 09 07:23:22 2014 +0100 +++ b/pom.xml Tue Dec 09 08:33:22 2014 +0100 @@ -35,6 +35,7 @@ ko-osgi-test equinox-agentclass-hook boot-script + boot-agent-test @@ -246,7 +247,7 @@ org.ow2.asm asm - 4.1 + 5.0 org.netbeans.api diff -r 5bea583947da -r de02b7c13379 src/main/javadoc/overview.html --- a/src/main/javadoc/overview.html Tue Dec 09 07:23:22 2014 +0100 +++ b/src/main/javadoc/overview.html Tue Dec 09 08:33:22 2014 +0100 @@ -78,6 +78,22 @@

What's New in Version 1.1?

+ The content of a {@link net.java.html.BrwsrCtx context} + can be selected by registering implementations under specific + {@link org.netbeans.html.context.spi.Contexts.Id technology identifiers} + and requesting them during + {@link org.netbeans.html.context.spi.Contexts#newBuilder(java.lang.Object...) construction} + of the context. org.netbeans.html:ko4j module's implementation + offers ko4j, xhr and websocket identifiers + for its registered services + (e.g. {@link org.netbeans.html.json.spi.Technology}, + {@link org.netbeans.html.json.spi.Transfer} and + {@link org.netbeans.html.json.spi.WSTransfer}). + org.netbeans.html:ko-ws-tyrus + module registers its + {@link org.netbeans.html.json.spi.Transfer Java based JSON} and + {@link org.netbeans.html.json.spi.WSTransfer WebSocket} implementations + under the name tyrus. Memory model when using Knockout bindings has been improved (required additions of two new methods: {@link org.netbeans.html.json.spi.PropertyBinding#weak()} and @@ -107,6 +123,28 @@ prevent endless debugging when one forgets to do so.

+

+ What's new in older versions? Click the + link + to view even more + historic changes below: +

+ + +
+ +

What's New in Version 0.9?

@@ -173,7 +211,7 @@

-

What's New in Older Versions?

+

What's New in 0.7.x Versions?

{@link net.java.html.js.JavaScriptBody} annotation has new attribute @@ -190,6 +228,7 @@ See example {@link net.java.html.BrwsrCtx#execute(java.lang.Runnable) using Java timer}.

+

Interesting Entry Points