changeset 5584:88e06514b086

RT-33682: Open source JMX packages Reviewed-by: felipe, msladecek
author kcr
date Wed, 30 Oct 2013 13:48:45 -0700
parents eb21f864f79f
children 868a00affbfe
files build.gradle modules/jmx/src/main/java/com/oracle/javafx/jmx/MXExtensionImpl.java modules/jmx/src/main/java/com/oracle/javafx/jmx/SGMXBean.java modules/jmx/src/main/java/com/oracle/javafx/jmx/SGMXBeanImpl.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/ImmutableJSONDocument.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONDocument.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONException.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONFactory.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONReader.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONWriter.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/impl/JSONMessages.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/impl/JSONScanner.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/impl/JSONStreamReaderImpl.java modules/jmx/src/main/java/com/oracle/javafx/jmx/json/impl/JSONSymbol.java modules/jmx/src/main/resources/com/oracle/javafx/jmx/json/impl/JSONMessagesBundle.properties modules/jmx/src/main/resources/com/oracle/javafx/jmx/json/impl/JSONMessagesBundle_ja.properties modules/jmx/src/main/resources/com/oracle/javafx/jmx/json/impl/JSONMessagesBundle_zh_CN.properties modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_CSS_Test.java modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_bounds_Test.java modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_sceneGraph_Test.java modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_state_Test.java modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_windows_Test.java modules/jmx/src/test/java/com/oracle/javafx/jmx/TestUtils.java settings.gradle
diffstat 24 files changed, 4006 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/build.gradle	Wed Oct 30 15:53:41 2013 +0000
+++ b/build.gradle	Wed Oct 30 13:48:45 2013 -0700
@@ -1422,6 +1422,18 @@
     }
 }
 
+project(":jmx") {
+    dependencies {
+        compile project(":base")
+        compile project(":graphics")
+        compile project(":swing")
+        compile project(":media")
+    }
+
+    // Tests are disabled until RT-33926 can be fixed
+    test.enabled = false
+}
+
 // This project is for system tests that need to run with a full SDK.
 // Most of them display a stage or do other things that preclude running
 // them in a shared JVM or as part of the "smoke test" run (which must
@@ -2377,6 +2389,16 @@
     }
     jfxrt.dependsOn(jfxswtIndexTask)
 
+    def jmxTask = task ("jmx${t.capital}", type: Jar) {
+        group = "Basic"
+        description = "Creates the javafx-mx.jar"
+        archiveName = "build/${t.name}-sdk/lib/javafx-mx.jar";
+        includeEmptyDirs = false
+        from "modules/jmx/build/classes/main"
+        from "modules/jmx/build/resources/main"
+        dependsOn project(":jmx").assemble
+    }
+
     // The 'sdk' task will build the rest of the SDK, and depends on the 'jfxrtTask' task. After
     // executing this task the sdk bundle for the current COMPILE_TARGETS will be fully created.
     def sdkTask = task("sdk$t.capital") {
@@ -2489,6 +2511,7 @@
                 }
             }
         }
+        dependsOn(jmxTask);
         dependsOn(jfxrtTask)
         dependsOn(javadoc)
         dependsOn(src)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/MXExtensionImpl.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+import com.sun.javafx.jmx.MXExtension;
+import com.sun.scenario.animation.AnimationPulse;
+import java.lang.management.ManagementFactory;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+public final class MXExtensionImpl extends MXExtension {
+    @Override
+    public void intialize() throws Exception {
+        final MBeanServer mbeanServer =
+                ManagementFactory.getPlatformMBeanServer();
+
+        mbeanServer.registerMBean(
+                new SGMXBeanImpl(),
+                new ObjectName("com.oracle.javafx.jmx:type=SGBean"));
+
+        mbeanServer.registerMBean(
+                AnimationPulse.getDefaultBean(),
+                new ObjectName(":type=AnimationPulse"));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/SGMXBean.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+/**
+ * The <code>SGMXBean</code> represents a standard JMX interface to the JavaFX application.
+ * The interface is supposed to be used by tools (debugger) and provides functionality
+ * to obtain Scene-graph related information and information about nodes.
+ */
+public interface SGMXBean {
+
+    /**
+     * Pauses the Scene-graph which means it pause all animations, media players, etc.
+     * To resume the Scene-graph to normal operation call {@link #resume}.
+     *
+     * If the Scene-graph is already "PAUSED" then this method has no effect.
+     */
+    void pause();
+
+    /**
+     * Resumes the previously paused Scene-graph into the normal operation.
+     *
+     * If the Scene-graph is running normally (not "PAUSED") then this method has no effect.
+     */
+    void resume();
+
+    /**
+     * Produces single JavaFX pulse and pauses the Scene-graph again. It is
+     * similar to STEP feature in debuggers.
+     *
+     * The Scene-graph should be already "PAUSED" (by calling {@link #pause()} method)
+     * prior to calling this function otherwise the {@link IllegalStateException} is thrown.
+     *
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    void step() throws IllegalStateException;
+
+    /**
+     * Returns the list of JavaFX windows. Each window is identified by unique
+     * number identifier. The identifier is used as an input to
+     * {@link #getSGTree(int)} method.
+     *
+     * The {@link #pause()} method should be called prior to calling this method.
+     * Otherwise the {@link IllegalStateException} is thrown.
+     *
+     * The result is in the format of JSON string.
+     *
+     * @return the list of all JavaFX windows in JSON format
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    String getWindows() throws IllegalStateException;
+
+    /**
+     * Returns the Scene-graph hierarchy in a simple tree-like model for
+     * given window. Every node is identified by unique number identifier.
+     * The identifier is used to query node's properties with methods like
+     * {@link #getCSSInfo(int)}.
+     *
+     * The {@link #pause()} method should be called prior to calling this method.
+     * Otherwise the {@link IllegalStateException} is thrown.
+     *
+     * The result is in the format of JSON string.
+     *
+     * @param windowId unique window identifier obtained by {@link #getWindows()}
+     * @return the simple tree-like model of the Scene-graph in JSON format
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    String getSGTree(int windowId) throws IllegalStateException;
+
+    /**
+     * Retrieves the CSS information about the particular node.
+     *
+     * The {@link #pause()} method should be called prior to calling this method.
+     * Otherwise the {@link IllegalStateException} is thrown.
+     *
+     * @param nodeId node identifier obtained by {@link #getSGTree(int)}
+     * @return list of key-value pairs holding CSS information, result is in JSON format
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    String getCSSInfo(int nodeId) throws IllegalStateException;
+
+    /**
+     * Retrieves the bounds information about the particular node. Bounds are in scene's
+     * coordinate system.
+     *
+     * @param nodeId node identifier obtained by {@link #getSGTree(int)}
+     * @return bounds information (x, y, width and height) for given node in scene's coordinate system,
+     * result is in JSON format
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    String getBounds(int nodeId) throws IllegalStateException;
+
+    /**
+     * Adds the node with the nodeId to the list of nodes that are to be
+     * highlighted in the scene. The nodeId is to be retrieved using
+     * the {@link #getSGTree(int)} method.
+     *
+     * @param nodeId the id of the node to be highlighted
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    void addHighlightedNode(int nodeId) throws IllegalStateException;
+
+    /**
+     * Removes the nodeId node from the list of nodes that are to be
+     * highlighted in the scene. The nodeId is to be retrieved using
+     * the {@link #getSGTree(int)} method.
+     *
+     * @param nodeId the id of the node to be removed
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    void removeHighlightedNode(int nodeId) throws IllegalStateException;
+
+    /**
+     * Adds the specified region to the list of regions to be highlighted
+     * in the scene. The windowId is to be retrieved using the
+     * {@link #getWindows()} method.
+     *
+     * @param windowId unique window identifier obtained by {@link #getWindows()}
+     * @param x x coordinate of the region
+     * @param y y coordinate of the region
+     * @param w width of the region
+     * @param h height of the region
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    void addHighlightedRegion(int windowId, double x, double y, double w, double h)
+        throws IllegalStateException;
+
+    /**
+     * Removes the specified region from the list of regions to be highlighted
+     * in the scene. The windowId is to be retrieved using the
+     * {@link #getWindows()} method.
+     *
+     * @param windowId unique window identifier obtained by {@link #getWindows()}
+     * @param x x coordinate of the region
+     * @param y y coordinate of the region
+     * @param w width of the region
+     * @param h height of the region
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    void removeHighlightedRegion(int windowId, double x, double y, double w, double h)
+        throws IllegalStateException;
+
+    /**
+     * Makes a screen-shot of the selected node in the scene's coordinates
+     * and stores it into a temporary file in the PNG format.
+     * Absolute path to this file is returned.
+     *
+     * The {@link #pause()} method should be called prior to calling this method.
+     * Otherwise the {@link IllegalStateException} is thrown.
+     *
+     * @param nodeId node identifier obtained by {@link #getSGTree(int)}
+     * @return the absolute path to the PNG file with the image
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    String makeScreenShot(int nodeId) throws IllegalStateException;
+
+    /**
+     * Makes a screen-shot of the specified region in the scene's coordinates
+     * and stores it into a temporary file in the PNG format.
+     * Absolute path to this file is returned.
+     *
+     * The {@link #pause()} method should be called prior to calling this method.
+     * Otherwise the {@link IllegalStateException} is thrown.
+     *
+     * @param windowId unique window identifier obtained by {@link #getWindows()}
+     * @param x x coordinate of the region
+     * @param y y coordinate of the region
+     * @param w width of the region
+     * @param h height of the region
+     * @return the absolute path to the PNG file with the image
+     * @throws IllegalStateException when Scene-graph is not "PAUSED"
+     */
+    String makeScreenShot(int windowId, double x, double y, double w, double h)
+        throws IllegalStateException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/SGMXBeanImpl.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,537 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import javax.imageio.ImageIO;
+
+import javafx.collections.ObservableList;
+import javafx.geometry.Bounds;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import javafx.stage.Window;
+import javafx.embed.swing.SwingFXUtils;
+
+import com.oracle.javafx.jmx.json.JSONDocument;
+import javafx.css.CssMetaData;
+import javafx.css.Styleable;
+import javafx.css.StyleableProperty;
+import com.sun.javafx.jmx.HighlightRegion;
+import com.sun.javafx.jmx.MXNodeAlgorithm;
+import com.sun.javafx.jmx.MXNodeAlgorithmContext;
+import com.sun.javafx.tk.TKPulseListener;
+import com.sun.javafx.tk.TKScene;
+import com.sun.javafx.tk.Toolkit;
+import com.sun.media.jfxmedia.AudioClip;
+import com.sun.media.jfxmedia.MediaManager;
+import com.sun.media.jfxmedia.MediaPlayer;
+import com.sun.media.jfxmedia.events.PlayerStateEvent.PlayerState;
+
+/**
+ * Default implementation of {@link SGMXBean} interface.
+ */
+public class SGMXBeanImpl implements SGMXBean, MXNodeAlgorithm {
+
+    private static final String SGMX_NOT_PAUSED_TEXT = "Scene-graph is not PAUSED.";
+    private static final String SGMX_CALL_GETSGTREE_FIRST = "You need to call getSGTree() first.";
+
+    private boolean paused = false;
+
+    private Map<Integer, Window> windowMap = null;
+    private JSONDocument jwindows = null;
+    private Map<Integer, Node> nodeMap = null;
+    private JSONDocument[] jsceneGraphs = null;
+    private Map<Scene, BufferedImage> scene2Image = null;
+
+    private List<MediaPlayer> playersToResume = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void pause() {
+        if (paused) {
+            return;
+        }
+        paused = true;
+        releaseAllStateObject();
+        Toolkit tk = Toolkit.getToolkit();
+        tk.pauseScenes();
+        pauseMedia();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void resume() {
+        if (!paused) {
+            return;
+        }
+        paused = false;
+        releaseAllStateObject();
+        Toolkit tk = Toolkit.getToolkit();
+        tk.resumeScenes();
+        resumeMedia();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void step() throws IllegalStateException {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+
+        releaseAllStateObject();
+
+        Toolkit tk = Toolkit.getToolkit();
+        final CountDownLatch onePulseLatch = new CountDownLatch(1);
+
+        tk.setLastTkPulseListener(new TKPulseListener() {
+            @Override public void pulse() {
+                onePulseLatch.countDown();
+            }
+        });
+
+        tk.resumeScenes();
+
+        try {
+            onePulseLatch.await();
+        } catch (InterruptedException e) { }
+
+        tk.pauseScenes();
+        tk.setLastTkPulseListener(null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getWindows() throws IllegalStateException {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        importWindowsIfNeeded();
+        return jwindows.toJSON();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getSGTree(int windowId) throws IllegalStateException {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        importWindowsIfNeeded();
+        if (nodeMap == null) {
+             nodeMap = new LinkedHashMap<Integer, Node>();
+        }
+        if (jsceneGraphs[windowId] == null) {
+            final Window window = windowMap.get(windowId);
+            this.importSGTree(window.getScene().getRoot(), windowId);
+        }
+        return jsceneGraphs[windowId].toJSON();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addHighlightedNode(int nodeId) throws IllegalStateException {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        Toolkit.getToolkit().getHighlightedRegions().add(
+                                createHighlightRegion(nodeId));
+        getNode(nodeId).getScene().impl_getPeer().markDirty();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void removeHighlightedNode(int nodeId) throws IllegalStateException {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        Toolkit.getToolkit().getHighlightedRegions().remove(
+                                createHighlightRegion(nodeId));
+        getNode(nodeId).getScene().impl_getPeer().markDirty();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addHighlightedRegion(int windowId, double x, double y, double w, double h)
+        throws IllegalStateException
+    {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        TKScene scenePeer = getScene(windowId).impl_getPeer();
+        Toolkit.getToolkit().getHighlightedRegions().add(
+                          new HighlightRegion(scenePeer, x, y, w, h));
+        scenePeer.markDirty();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void removeHighlightedRegion(int windowId, double x, double y, double w, double h)
+        throws IllegalStateException
+    {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        TKScene scenePeer = getScene(windowId).impl_getPeer();
+        Toolkit.getToolkit().getHighlightedRegions().remove(
+                          new HighlightRegion(scenePeer, x, y, w, h));
+        scenePeer.markDirty();
+    }
+
+    private Node getNode(int nodeId) {
+        if (nodeMap == null) {
+            throw new IllegalStateException(SGMX_CALL_GETSGTREE_FIRST);
+        }
+        Node node = nodeMap.get(nodeId);
+        if (node == null) {
+            throw new IllegalArgumentException("Wrong node id.");
+        }
+        return node;
+    }
+
+    private Scene getScene(int windowId) {
+        if (windowMap == null) {
+            throw new IllegalStateException(SGMX_CALL_GETSGTREE_FIRST);
+        }
+        Window window = windowMap.get(windowId);
+        if (window == null) {
+            throw new IllegalArgumentException("Wrong window id.");
+        }
+        return window.getScene();
+    }
+
+    private HighlightRegion createHighlightRegion(int nodeId) {
+        Node node = getNode(nodeId);
+        Bounds bounds = node.localToScene(node.getBoundsInLocal());
+        return new HighlightRegion(node.getScene().impl_getPeer(),
+                                   bounds.getMinX(),
+                                   bounds.getMinY(),
+                                   bounds.getWidth(),
+                                   bounds.getHeight());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String makeScreenShot(int nodeId) throws IllegalStateException {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        if (nodeMap == null) {
+            throw new IllegalStateException(SGMX_CALL_GETSGTREE_FIRST);
+        }
+
+        Node node = nodeMap.get(nodeId);
+        if (node == null) {
+            return null;
+        }
+
+        Scene scene = node.getScene();
+        Bounds sceneBounds = node.localToScene(node.getBoundsInLocal());
+        return getScreenShotPath(scene, sceneBounds.getMinX(), sceneBounds.getMinY(),
+                sceneBounds.getWidth(), sceneBounds.getHeight());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String makeScreenShot(int windowId, double x, double y, double w, double h)
+        throws IllegalStateException
+    {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        if (nodeMap == null) {
+            throw new IllegalStateException(SGMX_CALL_GETSGTREE_FIRST);
+        }
+        Scene scene = getScene(windowId);
+        return getScreenShotPath(scene, x, y, w, h);
+    }
+
+    private String getScreenShotPath(Scene scene, double x, double y, double w, double h) {
+        if (scene2Image == null) {
+            scene2Image = new LinkedHashMap<Scene, BufferedImage>();
+        }
+
+        BufferedImage bufferedImage = scene2Image.get(scene);
+        if (bufferedImage == null) {
+            Image fxImage = scene.snapshot(null);
+            bufferedImage  = SwingFXUtils.fromFXImage(fxImage, null);
+            scene2Image.put(scene, bufferedImage);
+        }
+
+        BufferedImage nodeImage = bufferedImage.getSubimage((int)x, (int)y, (int)w, (int)h);
+
+        File tmpFile = null;
+        try {
+            tmpFile = File.createTempFile("jfx", ".png");
+            ImageIO.write(nodeImage, "PNG", tmpFile);
+            tmpFile.deleteOnExit();
+            return tmpFile.getAbsolutePath();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    private void releaseAllStateObject() {
+        clearWindowMap();
+        jwindows = null;
+        clearNodeMap();
+        jsceneGraphs = null;
+        clearScene2Image();
+    }
+
+    private void clearWindowMap() {
+        if (windowMap != null) {
+            windowMap.clear();
+            windowMap = null;
+        }
+    }
+
+    private void clearNodeMap() {
+        if (nodeMap != null) {
+            nodeMap.clear();
+            nodeMap = null;
+        }
+     }
+
+    private void clearScene2Image() {
+        if (scene2Image != null) {
+            scene2Image.clear();
+            scene2Image = null;
+        }
+    }
+
+    private void importWindowsIfNeeded() {
+        if (windowMap == null) {
+            windowMap = new LinkedHashMap<Integer, Window>();
+            this.importWindows();
+        }
+    }
+
+    private void importWindows() {
+        int windowCount = 0;
+        final Iterator<Window> it = Window.impl_getWindows();
+
+        jwindows = JSONDocument.createArray();
+        while (it.hasNext()) {
+            final Window window = it.next();
+
+            windowMap.put(windowCount, window);
+
+            final JSONDocument jwindow = JSONDocument.createObject();
+            jwindow.setNumber("id", windowCount);
+            jwindow.setString("type", window.impl_getMXWindowType());
+            jwindows.array().add(jwindow);
+            windowCount++;
+        }
+
+        jsceneGraphs = new JSONDocument[windowCount];
+    }
+
+    private void importSGTree(Node sgRoot, int windowId) {
+        if (sgRoot == null) {
+            return;
+        }
+        jsceneGraphs[windowId] = (JSONDocument)sgRoot.impl_processMXNode(this,
+            new MXNodeAlgorithmContext(nodeMap.size()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getCSSInfo(int nodeId) throws IllegalStateException {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        if (nodeMap == null) {
+            throw new IllegalStateException(SGMX_CALL_GETSGTREE_FIRST);
+        }
+
+        Node node = nodeMap.get(nodeId);
+        if (node == null) {
+            return null;
+        }
+
+        JSONDocument d = new JSONDocument(JSONDocument.Type.OBJECT);
+
+        List<CssMetaData<? extends Styleable, ?>> styleables = node.getCssMetaData();
+
+        for (CssMetaData sp: styleables) {
+            processCssMetaData(sp, node, d);
+        }
+
+        return d.toJSON();
+    }
+
+    private static void processCssMetaData(CssMetaData sp, Node node, JSONDocument d) {
+
+        List<CssMetaData> subProps = sp.getSubProperties();
+        if (subProps != null && !subProps.isEmpty()) {
+            for (CssMetaData subSp: subProps) {
+                processCssMetaData(subSp, node, d);
+            }
+        }
+
+        try {
+            StyleableProperty writable = sp.getStyleableProperty(node);
+            Object value = writable != null ? writable.getValue() : null;
+            if (value != null) {
+                d.setString(sp.getProperty(), value.toString());
+            } else {
+                d.setString(sp.getProperty(), "null");
+            }
+        } catch (Exception e) {
+            System.out.println(e);
+            e.printStackTrace();
+        }
+    }
+
+    private static String upcaseFirstLetter(String s) {
+        return s.substring(0, 1).toUpperCase() + s.substring(1);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getBounds(int nodeId) throws IllegalStateException {
+        if (!paused) {
+            throw new IllegalStateException(SGMX_NOT_PAUSED_TEXT);
+        }
+        if (nodeMap == null) {
+            throw new IllegalStateException(SGMX_CALL_GETSGTREE_FIRST);
+        }
+
+        Node node = nodeMap.get(nodeId);
+        if (node == null) {
+            return null;
+        }
+
+        Bounds sceneBounds = node.localToScene(node.getBoundsInLocal());
+        JSONDocument d = JSONDocument.createObject();
+        d.setNumber("x", sceneBounds.getMinX());
+        d.setNumber("y", sceneBounds.getMinY());
+        d.setNumber("w", sceneBounds.getWidth());
+        d.setNumber("h", sceneBounds.getHeight());
+
+        return d.toJSON();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Object processLeafNode(Node node, MXNodeAlgorithmContext ctx) {
+        return createJSONDocument(node, ctx);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Object processContainerNode(Parent parent, MXNodeAlgorithmContext ctx) {
+        JSONDocument d = createJSONDocument(parent, ctx);
+
+        final ObservableList<Node> children = parent.getChildrenUnmodifiable();
+
+        JSONDocument childrenDoc = JSONDocument.createArray(children.size());
+        d.set("children", childrenDoc);
+
+        for (int i = 0; i < children.size(); i++) {
+            childrenDoc.set(i, (JSONDocument)children.get(i).impl_processMXNode(this, ctx));
+        }
+        return d;
+    }
+
+    private JSONDocument createJSONDocument(Node n, MXNodeAlgorithmContext ctx) {
+        int id = ctx.getNextInt();
+
+        nodeMap.put(id, n);
+
+        JSONDocument d = JSONDocument.createObject();
+        d.setNumber("id", id);
+        d.setString("class", n.getClass().getSimpleName());
+        return d;
+    }
+
+    private void pauseMedia() {
+        AudioClip.stopAllClips();
+
+        List<MediaPlayer> allPlayers = MediaManager.getAllMediaPlayers();
+        if (allPlayers == null) {
+            return;
+        }
+        if ((!allPlayers.isEmpty()) && (playersToResume == null)) {
+            playersToResume = new ArrayList<MediaPlayer>();
+        }
+        for (MediaPlayer player: allPlayers) {
+            if (player.getState() == PlayerState.PLAYING) {
+                player.pause();
+                playersToResume.add(player);
+            }
+        }
+    }
+
+    private void resumeMedia() {
+        if (playersToResume == null) {
+            return;
+        }
+        for (MediaPlayer player: playersToResume) {
+            player.play();
+        }
+        playersToResume.clear();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/ImmutableJSONDocument.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @treatAsPrivate
+ */
+final class ImmutableJSONDocument extends JSONDocument {
+
+    public ImmutableJSONDocument(Type type) {
+        super(type);
+    }
+
+    @Override
+    public List<Object> array() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public Map<String, Object> object() {
+        return Collections.emptyMap();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONDocument.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,760 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json;
+
+import java.io.Writer;
+import java.io.StringWriter;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+/**
+ * An in-memory representation of a JSON node. Can represent an entire
+ * JSON document, or a fragment. Can represent the root node, or
+ * intermediate child nodes. Can represent either an OBJECT or an
+ * ARRAY at a time, never both. The type is specified at construction
+ * time and is immutable.
+ *
+ */
+public class JSONDocument implements Iterable<JSONDocument> {
+
+    /**
+     * URI for use in {@link javax.xml.xpath.XPathFactory} to specify
+     * a subset of {@link javax.xml.xpath.XPath} over JSON.
+     */
+    public static final String JSON_XPATH_URI = "http://javafx.com/json/xpath";
+
+    /**
+     * An empty array.
+     */
+    public static final JSONDocument EMPTY_ARRAY = new ImmutableJSONDocument(Type.ARRAY);
+
+    /**
+     * An empty object.
+     */
+    public static final JSONDocument EMPTY_OBJECT = new ImmutableJSONDocument(Type.OBJECT);
+
+    /**
+     * The type of a JSON node. Can be an ARRAY or an OBJECT.
+     */
+    public enum Type { ARRAY, OBJECT }
+
+    private final Type type;
+    private final List<Object> array;
+    private final Map<String, Object> object;
+
+    public static JSONDocument createObject() {
+        return new JSONDocument(Type.OBJECT, 0);
+    }
+
+    public static JSONDocument createArray() {
+        return JSONDocument.createArray(0);
+    }
+
+    public static JSONDocument createArray(int length) {
+        return new JSONDocument(Type.ARRAY, length);
+    }
+
+    /**
+     * Constructs an empty node of the specified type. Child elements may be
+     * added subsequently by adding them to the corresponding collection
+     * obtained by calling array() or object().
+     *
+     * @param type the type of the node, an ARRAY or an OBJECT.
+     */
+    public JSONDocument(final Type type) {
+        this(type, 0);
+    }
+
+    private JSONDocument(final Type type, int length) {
+        this.type = type;
+        if (type == Type.ARRAY) {
+            final Vector<Object> v = new Vector<Object>(length);
+            v.setSize(length);
+            array = v;
+            object = null;
+        } else if (type == Type.OBJECT) {
+            array = null;
+            object = new LinkedHashMap<String, Object>();
+        } else {
+            array = null;
+            object = null;
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Returns the type of the node
+     *
+     * @return the type of the node, an ARRAY or an OBJECT
+     */
+    public Type type() {
+        return type;
+    }
+
+    /**
+     * Returns the array representation of the node.
+     *
+     * @return the node as a List. Returns an empty List
+     * if this node is not an ARRAY type
+     */
+    public List<Object> array() {
+        if (type != Type.ARRAY) {
+            return EMPTY_ARRAY.array();
+        }
+        return array;
+    }
+
+    /**
+     * Returns the map representation of the node.
+     *
+     * @return the node as a Map. Returns an empty Map
+     * if this node is not an OBJECT type
+     */
+    public Map<String, Object> object() {
+        if (type != Type.OBJECT) {
+            return EMPTY_OBJECT.object();
+        }
+        return object;
+    }
+
+    /**
+     * Test if the node is an ARRAY.
+     *
+     * @return true if the node is an ARRAY; false otherwise.
+     */
+    public boolean isArray() {
+        return type == Type.ARRAY && array != null;
+    }
+
+    /**
+     * Test if the node is an OBJECT.
+     *
+     * @return true if the node is an OBJECT; false otherwise.
+     */
+    public boolean isObject() {
+        return type == Type.OBJECT && object != null;
+    }
+
+    /**
+     * Writes a string representation of the node and its children,
+     * without newlines or whitespace, to the supplied Writer.
+     *
+     * @see #toJSON() for a variant that returns a String
+     * @see #toString() for a human-readable representation
+     * @throws IOException if there are errors writing to the Writer
+     */
+    public void toJSON(Writer writer) throws IOException {
+        printJSON(writer, isArray() ? array() : object(), 0, false);
+    }
+
+    /**
+     * Returns a string representation of the node and its children,
+     * without newlines or whitespace.
+     *
+     * @return the node and its children as a string.
+     * @see #toString() for a human-readable representation
+     */
+    public String toJSON() {
+        StringWriter sb = new StringWriter(4096);
+        try {
+            printJSON(sb, isArray() ? array() : object(), 0, false);
+        } catch (IOException ignore) {
+        }
+        return sb.getBuffer().toString();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void printJSON(Writer sb, Object obj, int depth, boolean pretty) throws IOException {
+        if (obj instanceof JSONDocument) {
+            final JSONDocument doc = (JSONDocument) obj;
+            if (doc.isArray()) {
+                printArray(sb, doc.array(), depth, pretty);
+            } else if (doc.isObject()) {
+                printObject(sb, doc.object(), depth, pretty);
+            } else {
+                assert false;
+            }
+        } else if (obj instanceof List) {
+            printArray(sb, (List<Object>) obj, depth, pretty);
+        } else if (obj instanceof Map) {
+            printObject(sb, (Map<String, Object>) obj, depth, pretty);
+        } else if (obj instanceof String) {
+            sb.append("\"");
+            printEscapedString(sb, (String)obj);
+            sb.append("\"");
+        } else if (obj != null) {
+            sb.append(obj.toString());
+        } else {
+            sb.append("null");
+        }
+    }
+
+    private void printArray(Writer sb, List<Object> obj, int depth, boolean pretty) throws IOException {
+        Iterator<Object> i = obj.iterator();
+        int size = obj.size();
+        int count = 0;
+
+        sb.append("[");
+        if (size > 1) {
+            depth++;
+            prettyPrint(sb, depth, pretty);
+        }
+        while (i.hasNext()) {
+            printJSON(sb, i.next(), depth, pretty);
+            if (count < size - 1) {
+                sb.append(",");
+                prettyPrint(sb, depth, pretty);
+            }
+            count++;
+        }
+        if (size > 1) {
+            depth--;
+            prettyPrint(sb, depth, pretty);
+        }
+        sb.append("]");
+    }
+
+    private void printObject(Writer sb, Map<String, Object> obj, int depth, boolean pretty) throws IOException {
+        Iterator<Map.Entry<String, Object>> i = obj.entrySet().iterator();
+        int size = obj.size();
+        int count = 0;
+
+        sb.append("{");
+        if (size > 1) {
+            depth++;
+            prettyPrint(sb, depth, pretty);
+        }
+
+        while (i.hasNext()) {
+            Map.Entry<String, Object> me = i.next();
+            sb.append("\"");
+            sb.append(me.getKey());
+            sb.append("\":");
+
+            final Object value = me.getValue();
+            if (pretty && size > 1) {
+                int objSize = 0;
+                if (value instanceof JSONDocument) {
+                    final JSONDocument doc = (JSONDocument) value;
+                    if (doc.isArray()) {
+                        objSize = doc.array().size();
+                    } else if (doc.isObject()) {
+                        objSize = doc.object().size();
+                    } else {
+                        assert false;
+                    }
+                }
+                if (objSize > 0) {
+                    prettyPrint(sb, depth, pretty);
+                }
+            }
+            printJSON(sb, value, depth, pretty);
+            if (count < size - 1) {
+                sb.append(",");
+                prettyPrint(sb, depth, pretty);
+            }
+            count++;
+        }
+
+        if (size > 1) {
+            depth--;
+            prettyPrint(sb, depth, pretty);
+        }
+        sb.append("}");
+    }
+
+    static void printEscapedString(Writer sb, String s) throws IOException {
+        char[] ca = s.toCharArray();
+        for (int i = 0; i < ca.length; i++) {
+            if (ca[i] == '"') {
+                sb.append("\\\"");
+            } else if (ca[i] == '\'') {
+                sb.append("'");
+            } else if (ca[i] == '\\') {
+                sb.append("\\\\");
+            } else if (ca[i] == '/') {
+                sb.append("\\/");
+            } else if (ca[i] == 0x7) {
+                sb.append("\\a");
+            } else if (ca[i] == 0x8) {
+                sb.append("\\b");
+            } else if (ca[i] == 0x9) {
+                sb.append("\\t");
+            } else if (ca[i] == 0xA) {
+                sb.append("\\n");
+            } else if (ca[i] == 0xB) {
+                sb.append("\\v");
+            } else if (ca[i] == 0xC) {
+                sb.append("\\f");
+            } else if (ca[i] == 0xD) {
+                sb.append("\\r");
+            } else if (ca[i] == 0x0) {
+                sb.append("\\0");
+            } else if (ca[i] > 0x7F && ca[i] < 0xFFFF) {
+                sb.append("\\u");
+                sb.append(String.format("%04X", (int) ca[i]));
+            } else {
+                sb.append(ca[i]);
+            }
+        }
+    }
+
+    private void prettyPrint(Writer sb, int depth, boolean pretty) throws IOException {
+        if (pretty) {
+            sb.append("\n");
+            for (int i = 0; i < depth; i++) {
+                sb.append(" ");
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringWriter sb = new StringWriter(4096);
+        try {
+            printJSON(sb, isArray() ? array() : object(), 0, true);
+        } catch (IOException ignore) {
+        }
+        return sb.getBuffer().toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof JSONDocument) {
+            final JSONDocument doc = (JSONDocument) obj;
+            return isArray() ? array().equals(doc.array()) : isObject() ? object().equals(doc.object()) : false;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 89 * hash + (isArray() ? array().hashCode() : isObject() ? object().hashCode() : 0);
+        return hash;
+    }
+
+    /**
+     * Get child of this object node. Returns an empty object if this node
+     * is not an OBJECT, if the specified child is not found, or if the
+     * specified child is not itself a node. This method can be used for
+     * recursive descent into the object / array hierarchy.
+     *
+     * @param key the key for the current object node whose value
+     * is to be retrieved
+     * @return child node if found, an empty OBJECT node otherwise
+     */
+    public JSONDocument get(String key) {
+        if (object != null) {
+            final Object value = object().get(key);
+            if (value instanceof JSONDocument) {
+                return (JSONDocument) value;
+            }
+        }
+        return EMPTY_OBJECT;
+    }
+
+    /**
+     * Get child of this array node. Returns an empty array if this node
+     * is not an ARRAY, if the specified child is not found, or if the
+     * specified child is not itself a node. This method can be used for
+     * recursive descent into the object / array hierarchy.
+     *
+     * @param index the index for the current array node at which the node
+     * is to be retrieved
+     * @return child node if found, an empty ARRAY node otherwise
+     */
+    public JSONDocument get(int index) {
+        if (array != null) {
+            final Object value = array().get(index);
+            if (value instanceof JSONDocument) {
+                return (JSONDocument) value;
+            }
+        }
+        return EMPTY_ARRAY;
+    }
+
+    /**
+     * Get child object of this object node as a Map. Returns an empty
+     * object if this node is not an OBJECT, if the specified child is
+     * not found, or if the specified child is not an object.
+     *
+     * @param key the key for the current object node whose value
+     * is to be retrieved
+     * @return child node if found, an empty OBJECT node otherwise
+     */
+    public Map<String, Object> getMap(String key) {
+        final Object obj = get(key, JSONDocument.class);
+        if (obj instanceof JSONDocument) {
+            return getMap((JSONDocument) obj);
+        }
+        return EMPTY_OBJECT.object();
+    }
+
+    /**
+     * Get child object of this array node as a Map. Returns an empty
+     * object if this node is not an ARRAY, if the specified child is
+     * not found, or if the specified child is not an object.
+     *
+     * @param index the index for the current array node at which the node
+     * is to be retrieved
+     * @return child node if found, an empty OBJECT node otherwise
+     */
+    public Map<String, Object> getMap(int index) {
+        final Object obj = get(index, JSONDocument.class);
+        if (obj instanceof JSONDocument) {
+            return getMap((JSONDocument) obj);
+        }
+        return EMPTY_OBJECT.object();
+    }
+
+    /**
+     * Get child array of this object node as a List. Returns an empty
+     * list if this node is not an OBJECT, if the specified child is
+     * not found, or if the specified child is not an array.
+     *
+     * @param key the key for the current object node whose value
+     * is to be retrieved
+     * @return child node if found, an empty ARRAY node otherwise
+     */
+    public List<Object> getList(String key) {
+        final Object obj = get(key, JSONDocument.class);
+        if (obj instanceof JSONDocument) {
+            return getList((JSONDocument) obj);
+        }
+        return EMPTY_ARRAY.array();
+    }
+
+    /**
+     * Get child array of this array node as a List. Returns an empty
+     * array if this node is not an ARRAY, if the specified child is
+     * not found, or if the specified child is not an array.
+     *
+     * @param index the index for the current array node at which the node
+     * is to be retrieved
+     * @return child node if found, an empty ARRAY node otherwise
+     */
+    public List<Object> getList(int index) {
+        final Object obj = get(index, JSONDocument.class);
+        if (obj instanceof JSONDocument) {
+            return getList((JSONDocument) obj);
+        }
+        return EMPTY_ARRAY.array();
+    }
+
+    /**
+     * Get the object node's named value as a String.
+     *
+     * @param key the name of the value
+     * @return the value as String
+     */
+    public String getString(String key) {
+        return (String) get(key, String.class);
+    }
+
+    /**
+     * Get the array node's value at specified index as a String.
+     *
+     * @param index the array index whose value is to be returned
+     * @return the array value as String
+     */
+    public String getString(int index) {
+        return (String) get(index, String.class);
+    }
+
+    /**
+     * Get the object node's named value as a Boolean.
+     *
+     * @param key the name of the value
+     * @return the value as Boolean
+     */
+    public Boolean getBoolean(String key) {
+        return (Boolean) get(key, Boolean.class);
+    }
+
+    /**
+     * Get the array node's value at specified index as a Boolean.
+     *
+     * @param index the array index whose value is to be returned
+     * @return the array value as Boolean
+     */
+    public Boolean getBoolean(int index) {
+        return (Boolean) get(index, Boolean.class);
+    }
+
+    /**
+     * Get the object node's named value as a Number.
+     *
+     * @param key the name of the value
+     * @return the value as Number
+     */
+    public Number getNumber(String key) {
+        return (Number) get(key, Number.class);
+    }
+
+    /**
+     * Get the array node's value at specified index as a Number.
+     *
+     * @param index the array index whose value is to be returned
+     * @return the array value as Number
+     */
+    public Number getNumber(int index) {
+        return (Number) get(index, Number.class);
+    }
+
+    /**
+     * Get if the object node's named value is null.
+     *
+     * @param key the name of the value
+     * @return true if the value is null, false otherwise
+     */
+    public boolean isNull(String key) {
+        if (object != null) {
+            return null == object().get(key);
+        }
+        return false;
+    }
+
+    /**
+     * Get if the array node's value at specified index is null.
+     *
+     * @param index the array index whose value is to be checked
+     * @return true if the value is null, false otherwise
+     */
+    public boolean isNull(int index) {
+        if (array != null) {
+            return null == array().get(index);
+        }
+        return false;
+    }
+
+    private Map<String, Object> getMap(final JSONDocument doc) {
+        if (doc != null) {
+            return doc.object();
+        }
+        return EMPTY_OBJECT.object();
+    }
+
+    private List<Object> getList(final JSONDocument doc) {
+        if (doc != null) {
+            return doc.array();
+        }
+        return EMPTY_ARRAY.array();
+    }
+
+    private Object get(final String key, final Class<?> type) {
+        if (object != null) {
+            final Object value = object().get(key);
+            if (type.isInstance(value)) {
+                return type.cast(value);
+            }
+        }
+        return null;
+    }
+
+    private Object get(final int index, final Class<?> type) {
+        if (array != null) {
+            final Object value = array().get(index);
+            if (type.isInstance(value)) {
+                return type.cast(value);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Set child object of this node.
+     *
+     * @param key the name of the child
+     * @param value the node containing the new values
+     * @return the old value, null if there was no value
+     */
+    public JSONDocument set(String key, JSONDocument child) {
+        return (JSONDocument) set(key, child, JSONDocument.class);
+    }
+
+    /**
+     * Set child object of this node.
+     *
+     * @param index the index at which the child is to be set
+     * @param value the node containing the new values
+     * @return the old value, null if there was no value
+     */
+    public JSONDocument set(int index, JSONDocument value) {
+        return (JSONDocument) set(index, value, JSONDocument.class);
+    }
+
+    /**
+     * Set value of this node as String.
+     *
+     * @param key the name of the value
+     * @param value the new value
+     * @return the old value, null if there was no value
+     */
+    public String setString(String key, String value) {
+        return (String) set(key, value, String.class);
+    }
+
+    /**
+     * Set value of this node as String.
+     *
+     * @param index the index at which the value is to be set
+     * @param value the new value
+     * @return the old value, null if there was no value
+     */
+    public String setString(int index, String value) {
+        return (String) set(index, value, String.class);
+    }
+
+    /**
+     * Set value of this node as Boolean.
+     *
+     * @param key the name of the value
+     * @param value the new value
+     * @return the old value, null if there was no value
+     */
+    public Boolean setBoolean(String key, Boolean value) {
+        return (Boolean) set(key, value, Boolean.class);
+    }
+
+    /**
+     * Set value of this node as Boolean.
+     *
+     * @param index the index at which the value is to be set
+     * @param value the new value
+     * @return the old value, null if there was no value
+     */
+    public Boolean setBoolean(int index, Boolean value) {
+        return (Boolean) set(index, value, Boolean.class);
+    }
+
+    /**
+     * Set value of this node as Number.
+     *
+     * @param key the name of the value
+     * @param value the new value
+     * @return the old value, null if there was no value
+     */
+    public Number setNumber(String key, Number value) {
+        return (Number) set(key, value, Number.class);
+    }
+
+    /**
+     * Set value of this node as Number.
+     *
+     * @param index the index at which the value is to be set
+     * @param value the new value
+     * @return the old value, null if there was no value
+     */
+    public Number setNumber(int index, Number value) {
+        return (Number) set(index, value, Number.class);
+    }
+
+    /**
+     * Set value of this node to null.
+     *
+     * @param key the name of the value
+     * @return the old value, null if there was no value
+     */
+    public Object setNull(String key) {
+        if (object != null) {
+            return object.put(key, null);
+        }
+        return null;
+    }
+
+    /**
+     * Set value of this node to null.
+     *
+     * @param index the index at which the value is to be set
+     * @param value the new value
+     * @return the old value, null if there was no value
+     */
+    public Object setNull(int index) {
+        if (array != null) {
+            return array.set(index, null);
+        }
+        return null;
+    }
+
+    private Object set(final String key, final Object value, final Class<?> type) {
+        if (object != null) {
+            if (type.isInstance(value)) {
+                return type.cast(object().put(key, value));
+            }
+        }
+        return null;
+    }
+
+    private Object set(final int index, final Object value, final Class<?> type) {
+        if (array != null) {
+            if (type.isInstance(value)) {
+                return type.cast(array().set(index, value));
+            }
+        }
+        return null;
+    }
+
+   static class IteratorWrapper implements Iterator<JSONDocument> {
+
+       final Iterator<Object> iterator;
+
+       IteratorWrapper(final Iterator<Object> iterator) {
+           this.iterator = iterator;
+       }
+
+       @Override
+       public void remove() {
+           iterator.remove();
+       }
+
+       @Override
+       public JSONDocument next() {
+           final Object value = iterator.next();
+           if (value instanceof JSONDocument) {
+               return (JSONDocument) value;
+           }
+           return null;
+       }
+
+       @Override
+       public boolean hasNext() {
+           return iterator.hasNext();
+       }
+   }
+
+    @Override
+    public IteratorWrapper iterator() {
+        return new IteratorWrapper((isObject() ?
+                object().values().iterator() :
+                array().iterator()));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONException.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json;
+
+/**
+ * Thrown when an error occurs while reading a JSON document.
+ */
+@SuppressWarnings("serial")
+public class JSONException extends RuntimeException {
+
+    private final int line;
+    private final int column;
+
+    /**
+     * Construct an exception with the exception, line and column number.
+     *
+     * @param cause - the cause of the error
+     * @param line - the line number of the error
+     * @param column - the column number of the error
+     */
+    public JSONException(Throwable cause, int line, int column) {
+        super(cause);
+        this.line = line;
+        this.column = column;
+    }
+
+    /**
+     * Construct an exception with the message, line and column number.
+     *
+     * @param message - the message to report
+     * @param line - the line number of the error
+     * @param column - the column number of the error
+     */
+    public JSONException(String message, int line, int column) {
+        super(message);
+        this.line = line;
+        this.column = column;
+    }
+
+    /**
+     * Returns the line number of where the error occurred.
+     *
+     * @return the line number of the error
+     */
+    public int line() {
+        return line;
+    }
+
+    /**
+     * Returns the column number of where the error occurred.
+     *
+     * @return the column number of the error
+     */
+    public int column() {
+        return column;
+    }
+
+    /**
+     * Returns a string describing the error.
+     *
+     * @return the string describing the error
+     */
+    public String toString() {
+        return "(" + line + "," + column + ") " + super.toString();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONFactory.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json;
+
+import com.oracle.javafx.jmx.json.impl.JSONStreamReaderImpl;
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * A factory class for creating a {@link JSONReader} or {@link JSONWriter}.
+ *
+ */
+public class JSONFactory {
+
+    private static final JSONFactory INSTANCE = new JSONFactory();
+
+    private JSONFactory() {
+    }
+
+    /**
+     * Gets an instance of JSONFactory.
+     *
+     * @return the JSONFactory
+     */
+    public static JSONFactory instance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Creates a new {@link JSONReader} from a reader.
+     *
+     * @param reader - the reader to read from
+     * @return a {@link JSONReader}
+     * @throws JSONException if reader cannot be read
+     */
+    public JSONReader makeReader(final Reader reader) throws JSONException {
+        return new JSONStreamReaderImpl(reader);
+    }
+
+    /**
+     * Creates a new {@link JSONWriter} from a writer
+     *
+     * @param writer - the writer to write to
+     * @return a {@link JSONWriter}
+     */
+    public JSONWriter makeWriter(final Writer writer) {
+        return new JSONWriter(writer);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONReader.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json;
+
+import com.oracle.javafx.jmx.json.JSONReader.EventType;
+import java.util.Iterator;
+
+/**
+ * Interface for reading a JSON document.
+ *
+ * An example of how to read a simple JSON document.
+ * <pre>
+ * JSON: { "abc": "123" }
+ *
+ * JSONFactory FACTORY = JSONFactory.instance();
+ * JSONWriter r = FACTORY.makeReader(reader);
+ * r.next();  // START_DOCUMENT
+ * r.depth(); // depth = -1
+ * r.next();  // START_OBJECT
+ * r.next();  // START_VALUE
+ * r.key();   // key = "abc"
+ * r.next();  // STRING
+ * r.value(); // value = "123"
+ * r.next();  // END_VALUE
+ * r.next();  // END_OBJECT
+ * r.next();  // END_DOCUMENT
+ * </pre>
+ *
+ */
+public interface JSONReader extends Iterable<EventType>, Iterator<EventType> {
+
+    /**
+     * The type of JSON events.
+     */
+    public enum EventType {
+        /**
+         * An unknown event, possibly a syntax error in a JSON document.
+         */
+        ERROR,
+        /**
+         * Indicates the start of a JSON object.
+         */
+        START_OBJECT,
+        /**
+         * Indicates the end of a JSON object.
+         */
+        END_OBJECT,
+        /**
+         * Indicates the string value of a JSON element.
+         */
+        STRING,
+        /**
+         * The start of a JSON document.
+         */
+        START_DOCUMENT,
+        /**
+         * The end of a JSON document.
+         */
+        END_DOCUMENT,
+        /**
+         * Indicates the start of a JSON array.
+         */
+        START_ARRAY,
+        /**
+         * Indicates the start of a JSON array element.
+         */
+        START_ARRAY_ELEMENT,
+        /**
+         * Indicates the end of a JSON array element.
+         */
+        END_ARRAY_ELEMENT,
+        /**
+         * Indicates the end of a JSON array.
+         */
+        END_ARRAY,
+        /**
+         * Indicates a JSON floating point number.
+         */
+        NUMBER,
+        /**
+         * Indicates a JSON integer.
+         */
+        INTEGER,
+        /**
+         * Indicates a JSON true value.
+         */
+        TRUE,
+        /**
+         * Indicates a JSON false value.
+         */
+        FALSE,
+        /**
+         * Indicates a JSON null value.
+         */
+        NULL,
+        /**
+         * Indicates the start of a JSON object value.
+         */
+        START_VALUE,
+        /**
+         * Indicates the end of a JSON object value.
+         */
+        END_VALUE
+    };
+
+    /**
+     * The name of the current JSON object.
+     *
+     * @return the name of a JSON object.
+     */
+    public String key();
+
+    /**
+     * The value of the current JSON object or array element.
+     *
+     * @return the value of the JSON object or array element.
+     */
+    public String value();
+
+    /**
+     * Skips until the specified object is found at the specified depth.
+     * If depth is negative, find the first occurrence of the specified object.
+     * If objectName is null, skip until the specified depth is reached.
+     *
+     * @param key the name of the object to find, may be null
+     * @param depth stop at the first element at this depth, ignored if negative
+     * @return the event at which this method stops, EventType.END_DOCUMENT if not found
+     */
+    public EventType next(String key, int depth);
+
+    /**
+     * Close the underlying Reader when done reading.
+     */
+    public void close();
+
+    /**
+     * The current line number in the JSON file.
+     *
+     * @return the current line number in the JSON file
+     */
+    public int line();
+
+    /**
+     * The current column number in the JSON file.
+     *
+     * @return the current column number in the JSON file.
+     */
+    public int column();
+
+    /**
+     * The current byte offset in the underlying Reader. This
+     * is useful for computing percent completion.
+     *
+     * @return the current byte offset
+     */
+    public long offset();
+
+    /**
+     * The reader's current depth in the JSON file.
+     *
+     * @return the current depth in the JSON file.
+     */
+    public int depth();
+
+    /**
+     * Returns the path from the root to the current position of the JSON file.
+     *
+     * @return a String array containing the path.
+     */
+    public String[] path();
+
+    /**
+     * Build an in-memory representation of the input JSON. JSON Objects are
+     * represented with Maps and JSON Arrays are represented with Lists.
+     * @return a JSONDocument that contains a Map or a List representing the
+     * root of the input object
+     */
+    public JSONDocument build();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/JSONWriter.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json;
+
+import com.oracle.javafx.jmx.json.impl.JSONMessages;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * Class for writing a JSON document.
+ *
+ * An example of how to write a simple JSON document.
+ * <pre>
+ * JSONFactory FACTORY = JSONFactory.instance();
+ * JSONWriter w = FACTORY.makeWriter(writer);
+ * w.startObject();
+ * w.objectValue("abc", "123");
+ * w.startObject("def");
+ * w.startArray();
+ * w.endArray();
+ * w.endObject();
+ * w.endObject();
+ * w.close();
+ * </pre>
+ * will produce
+ * <pre>
+ * { "abc":"123", def:[] }}
+ * </pre>
+ * These methods can be chained together, so the above
+ * could also be written as
+ * <pre>
+ * w.startObject()
+ *    .objectValue("abc", "123")
+ *    .startObject("def")
+ *      .startArray()
+ *      .endArray()
+ *    .endObject()
+ *  .endObject();
+ * </pre>
+ */
+
+public class JSONWriter {
+
+    private enum ContainerType { ARRAY, OBJECT }
+
+    private static class Container {
+        ContainerType type;
+        boolean first;
+        Container(ContainerType type) {
+            this.type = type;
+            this.first = true;
+        }
+    }
+
+    private final Writer writer;
+    private Stack<Container> where = new Stack<Container>();
+
+    JSONWriter(final Writer writer) {
+        this.writer = writer;
+    }
+
+    /**
+     * Writes the opening curly brace for a JSON object.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter startObject() throws IOException {
+        writeSeparatorIfNeeded();
+        writer.write("{");
+        where.push(new Container(ContainerType.OBJECT));
+        return this;
+    }
+
+    /**
+     * Writes a JSON object with the specified name.
+     *
+     * @param key - the name of the JSON object
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter startObject(String key) throws IOException {
+        if (where.peek().type != ContainerType.OBJECT) {
+            throw new IllegalStateException(JSONMessages.localize(null, "not_inside_object"));
+        }
+        writeSeparatorIfNeeded();
+        writeEscapedString(key);
+        writer.write(":{");
+        where.push(new Container(ContainerType.OBJECT));
+        return this;
+    }
+
+    /**
+     * Writes the closing curly brace for a JSON object.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter endObject() throws IOException {
+        if (where.peek().type != ContainerType.OBJECT) {
+            throw new IllegalStateException(JSONMessages.localize(null, "mismatched_call_to_endObject"));
+        }
+        where.pop();
+        writer.write("}");
+        return this;
+    }
+
+    /**
+     * Write the opening square brace for a JSON array.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter startArray() throws IOException {
+        writeSeparatorIfNeeded();
+        writer.write("[");
+        where.push(new Container(ContainerType.ARRAY));
+        return this;
+    }
+
+    /**
+     * Write a JSON array with the specified name.
+     *
+     * @param key - the name of the array.
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter startArray(String key) throws IOException {
+        if (where.peek().type != ContainerType.OBJECT) {
+            throw new IllegalStateException(JSONMessages.localize(null, "not_inside_object"));
+        }
+        writeSeparatorIfNeeded();
+        writeEscapedString(key);
+        writer.write(":[");
+        where.push(new Container(ContainerType.ARRAY));
+        return this;
+    }
+
+    /**
+     * Writes the closing square brace for a JSON array.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter endArray() throws IOException {
+        if (where.peek().type != ContainerType.ARRAY) {
+            throw new IllegalStateException(JSONMessages.localize(null, "mismatched_call_to_endArray"));
+        }
+        writer.write("]");
+        where.pop();
+        return this;
+    }
+
+    /**
+     * Writes a name/value pair for a JSON object.
+     *
+     * @param key - the name of the JSON object.
+     * @param value - the value of the JSON object.
+     * The value may be a Map, in which case entries in the Map
+     * are written as elements of the current JSON Object.
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter objectValue(String key, Object value) throws IOException {
+        if (where.peek().type != ContainerType.OBJECT) {
+            throw new IllegalStateException(JSONMessages.localize(null, "not_inside_object"));
+        }
+        writeSeparatorIfNeeded();
+        writeEscapedString(key);
+        writer.write(":");
+        writeValue(value);
+        return this;
+    }
+
+    /**
+     * Writes a value into a JSON array.
+     *
+     * @param value - the value of an array element.
+     * The value may be a List, in which case elements of the List
+     * are written as elements of the current JSON array.
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter arrayValue(Object value) throws IOException {
+        if (where.peek().type != ContainerType.ARRAY) {
+            throw new IllegalStateException(JSONMessages.localize(null, "not_inside_array"));
+        }
+        writeSeparatorIfNeeded();
+        writeValue(value);
+        return this;
+    }
+
+    /**
+     * Convenience method to write the entries in a Map as a JSON Object.
+     *
+     * @param values the Map entries to be written
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter writeObject(Map<String, Object> values) throws IOException {
+        startObject();
+        Iterator<Map.Entry<String, Object>> vi = values.entrySet().iterator();
+        while (vi.hasNext()) {
+            Map.Entry<String, Object> value = vi.next();
+            objectValue(value.getKey(), value.getValue());
+        }
+        endObject();
+        return this;
+    }
+
+    /**
+     * Convenience method to write the elements of a List as a JSON array.
+     *
+     * @param values the List of values to be written
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter writeArray(List<Object> values) throws IOException {
+        startArray();
+        for (Object value : values) {
+            arrayValue(value);
+        }
+        endArray();
+        return this;
+    }
+
+    /**
+     * Flushes JSONWriter to writer.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public JSONWriter flush() throws IOException {
+        writer.flush();
+        return this;
+    }
+
+    /**
+     * Closes the JSONWriter.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public void close() throws IOException {
+        writer.close();
+    }
+
+    @SuppressWarnings({"rawtypes","unchecked"})
+    private void writeValue(Object value) throws IOException {
+        if (value == null) {
+            writer.write("null");
+        } else if (value instanceof Map) {
+            startObject();
+            Iterator<Map.Entry> mi = ((Map) value).entrySet().iterator();
+            while (mi.hasNext()) {
+                Map.Entry e = mi.next();
+                objectValue((String) e.getKey(), e.getValue());
+            }
+            endObject();
+        } else if (value instanceof List) {
+            startArray();
+            Iterator ai = ((List) value).iterator();
+            while (ai.hasNext()) {
+                arrayValue(ai.next());
+            }
+            endArray();
+        } else if (value instanceof String) {
+            writeEscapedString((String) value);
+        } else {
+            writer.write(value.toString());
+        }
+    }
+
+    private void writeSeparatorIfNeeded() throws IOException {
+        if (where.empty()) {
+            return;
+        }
+
+        if (where.peek().first) {
+            where.peek().first = false;
+        } else {
+            writer.write(",");
+        }
+    }
+
+    private void writeEscapedString(String value) throws IOException {
+        writer.write("\"");
+        JSONDocument.printEscapedString(writer, value);
+        writer.write("\"");
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/impl/JSONMessages.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json.impl;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+public final class JSONMessages {
+
+    private static final Locale LOCALE = Locale.getDefault();
+
+    private static final ResourceBundle RESOURCES =
+            ResourceBundle.getBundle(JSONMessages.class.getPackage().getName() +
+            ".JSONMessagesBundle", LOCALE);
+
+    public static String localize(Object[] args, String key) {
+        if (args == null) {
+            return RESOURCES.getString(key);
+        } else {
+            return new MessageFormat(RESOURCES.getString(key), LOCALE).format(args);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/impl/JSONScanner.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json.impl;
+
+import com.oracle.javafx.jmx.json.JSONException;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * JSON scanner. Converts character data into stream of JSON symbols providing
+ * their location in document and the respective string values.
+ */
+final class JSONScanner {
+
+    private Reader reader;
+    private String value;
+    private int prevChar;
+    private int x,  y = 1;
+
+    @SuppressWarnings("unused")
+    private int checkpointX;
+    @SuppressWarnings("unused")
+    private int checkpointY;
+    private long offset;
+
+    private boolean isInt;
+
+    JSONScanner(Reader reader) throws IOException {
+        this.reader = reader;
+        offset = 0;
+        prevChar = reader.read();
+    }
+
+    JSONSymbol nextSymbol() throws JSONException, IOException {
+        int ch = prevChar;
+        JSONSymbol s;
+
+    /*
+     * space character ' ' (0x20),
+     * tab character (hex 0x09),
+     * form feed character (hex 0x0c),
+     * line separators characters newline (hex 0x0a)
+     * carriage return (hex 0x0d)
+     */
+
+        // strip witespace
+        while (ch != -1 && (ch == 0x20 ||
+               ch == 0x09 || ch == 0x0c ||
+               ch == 0x0a || ch == 0x0d)) {
+            ch = readChar(true);
+        }
+
+        value = null;
+        switch (ch) {
+            case -1:
+                s = JSONSymbol.EOS;
+                prevChar = -1;
+                break;
+            case '{':
+                s = JSONSymbol.CURLYOPEN;
+                prevChar = readChar(true);
+                break;
+            case '}':
+                s = JSONSymbol.CURLYCLOSE;
+                prevChar = readChar(true);
+                break;
+            case '[':
+                s = JSONSymbol.SQUAREOPEN;
+                prevChar = readChar(true);
+                break;
+            case ']':
+                s = JSONSymbol.SQUARECLOSE;
+                prevChar = readChar(true);
+                break;
+            case ':':
+                s = JSONSymbol.COLON;
+                prevChar = readChar(true);
+                break;
+            case ',':
+                s = JSONSymbol.COMMA;
+                prevChar = readChar(true);
+                break;
+            case 't':
+                readKeyword("true");
+                s = JSONSymbol.KEYWORD;
+                break;
+            case 'f':
+                readKeyword("false");
+                s = JSONSymbol.KEYWORD;
+                break;
+            case 'n':
+                readKeyword("null");
+                s = JSONSymbol.KEYWORD;
+                break;
+            case '"':
+                value = readString();
+                s = JSONSymbol.STRING;
+                break;
+            case '-':
+                value = readNumber(ch);
+                s = JSONSymbol.NUMBER;
+                break;
+            default:
+                if (ch >= '0' && ch <= '9') {
+                    value = readNumber(ch);
+                    s = JSONSymbol.NUMBER;
+                } else {
+                    Object[] args = {(char) ch};
+                    throw new JSONException(JSONMessages.localize(args, "unexpected_char"), line(), column());
+                }
+        }
+
+        return s;
+    }
+
+    String getValue() {
+        return value;
+    }
+
+    private void readKeyword(final String text) throws JSONException, IOException {
+        int i = 1;
+        int ch = 0; /* to satisfy compiler. not needed actually */
+
+        for (; i < text.length() && (ch = readChar(false)) == text.charAt(i); i++);
+
+        if (i < text.length()) {
+            Object[] args = {(char) ch};
+            throw new JSONException(JSONMessages.localize(args, "unexpected_char"), line(), column());
+        }
+
+        value = text;
+
+        prevChar = readChar(true);
+    }
+
+    private String readString() throws JSONException, IOException {
+        StringBuilder val = new StringBuilder();
+        int ch;
+
+        do {
+            ch = readChar(false);
+            switch (ch) {
+                case -1:
+                    throw new JSONException(JSONMessages.localize(null, "unexpected_end_of_stream"), line(), column());
+                case '\\':
+                     {
+                        int ch2 = readChar(false);
+                        switch (ch2) {
+                            case '"':
+                            case '\\':
+                            case '/':
+                                val.append((char) ch2);
+                                break;
+                            case 'b':
+                                val.append('\b');
+                                break;
+                            case 'f':
+                                val.append('\f');
+                                break;
+                            case 'n':
+                                val.append('\n');
+                                break;
+                            case 'r':
+                                val.append('\r');
+                                break;
+                            case 't':
+                                val.append('\t');
+                                break;
+                            case 'u': {
+                                char unicode = 0;
+                                for (int i = 4; --i >= 0;) {
+                                    ch2 = readChar(false);
+                                    unicode <<= 4;
+                                    if (ch2 >= '0' && ch2 <= '9') {
+                                        unicode |= ((char) ch2) - '0';
+                                    } else if (ch2 >= 'a' && ch2 <= 'f') {
+                                        unicode |= (((char) ch2) - 'a') + 0xA;
+                                    } else if (ch2 >= 'A' && ch2 <= 'F') {
+                                        unicode |= (((char) ch2) - 'A') + 0xA;
+                                    } else {
+                                        Object[] args = {(char) ch2};
+                                        throw new JSONException(JSONMessages.localize(args, "unexpected_char"), line(), column());
+                                    }
+                                }
+                                val.append((char) (unicode & 0xffff));
+                                break;
+                            }
+                            default:
+                                Object[] args = {(char) ch2};
+                                throw new JSONException(JSONMessages.localize(args, "unexpected_char"), line(), column());
+                        }
+                    }
+                    break;
+                case '"':
+                    break;
+                default:
+                    if ((ch >= 0x0000 && ch <= 0x001F) ||
+                        (ch >= 0x007F && ch <= 0x009F)) {
+                        throw new JSONException(JSONMessages.localize(null, "control_character_in_string"), line(), column());
+                    }
+                    val.append((char) ch);
+            }
+        } while (ch != '"');
+
+        prevChar = readChar(true);
+
+        return val.toString();
+    }
+
+    private String readNumber(final int prefetch) throws JSONException, IOException {
+        int ch = prefetch;
+        StringBuilder val = new StringBuilder();
+
+        val.append((char) ch);
+
+        // sign
+        if (ch == '-') {
+            ch = readChar(true);
+            val.append((char) ch);
+            if (ch < '0' || ch > '9') {
+                Object[] args = {(char) ch};
+                throw new JSONException(JSONMessages.localize(args, "unexpected_char"), line(), column());
+            }
+        }
+
+        isInt = true;
+        // int
+        if (ch == '0') {
+            ch = readChar(true);
+            val.append((char) ch);
+        } else {
+            do {
+                ch = readChar(true);
+                val.append((char) ch);
+            } while (ch >= '0' && ch <= '9');
+        }
+
+        // frac
+        if (ch == '.') {
+            isInt = false;
+            int count = 0;
+            do {
+                ch = readChar(true);
+                val.append((char) ch);
+                count++;
+            } while (ch >= '0' && ch <= '9');
+            if (count == 1) {
+                Object[] args = {(char) ch};
+                throw new JSONException(JSONMessages.localize(args, "unexpected_char"), line(), column());
+            }
+        }
+
+        // exp
+        if (ch == 'e' || ch == 'E') {
+            isInt = false;
+            ch = readChar(false);
+            val.append((char) ch);
+            if (ch == '+' || ch == '-') {
+                ch = readChar(false);
+                val.append((char) ch);
+            }
+            int count;
+            for (count = 0; ch >= '0' && ch <= '9'; count++) {
+                ch = readChar(true);
+                val.append((char) ch);
+            }
+            if (count == 0) {
+                Object[] args = {(char) ch};
+                throw new JSONException(JSONMessages.localize(args, "unexpected_char"), line(), column());
+            }
+        }
+
+        prevChar = ch;
+
+        return val.toString().substring(0, val.length() - 1);
+    }
+
+    private int readChar(boolean checkpoint) throws IOException {
+        int ch = reader.read();
+
+        if (checkpoint) {
+            checkpointX = x;
+            checkpointY = y;
+        }
+
+        if (ch == '\n') {
+            y++;
+            x = 0;
+        } else {
+            x++;
+        }
+        offset++;
+
+        return ch;
+    }
+
+    boolean isInteger() {
+        return isInt;
+    }
+
+    void close() throws IOException {
+        reader.close();
+    }
+
+    int line() {
+        return y;
+    }
+
+    int column() {
+        return x;
+    }
+
+    long getCharacterOffset() {
+        return offset;
+    }
+
+    @Override
+    public String toString() {
+        return y + ":" + x +
+            (offset >= 0 ? "@" + offset:"");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/impl/JSONStreamReaderImpl.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json.impl;
+
+import com.oracle.javafx.jmx.json.JSONDocument;
+import com.oracle.javafx.jmx.json.JSONException;
+import com.oracle.javafx.jmx.json.JSONReader;
+import java.util.Stack;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Iterator;
+
+public class JSONStreamReaderImpl implements JSONReader {
+
+    private JSONScanner scanner;
+    private JSONSymbol currentSymbol;
+    private EventType currentEvent;
+    private volatile boolean nullValue;
+    private String key;
+    private Stack<String> stack;
+    private boolean eod = false;
+    private JSONDocument collection = null;
+    private String collectionKey = null;
+    private int depth = 0;
+
+    public JSONStreamReaderImpl(Reader reader) throws JSONException {
+        stack = new Stack<String>();
+        currentSymbol = JSONSymbol.EOS;
+        try {
+            scanner = new JSONScanner(reader);
+            JSONSymbol.init(scanner);
+        } catch(IOException e) {
+            throw new JSONException(e, scanner.line(), scanner.column());
+        }
+    }
+
+    @Override
+    public Iterator<EventType> iterator() {
+        return this;
+    }
+
+    /**
+     * Skips until the specified object is found at the specified depth.
+     * If depth is negative, find the first occurrence of the specified object.
+     * If objectName is null, skip until the specified depth is reached.
+     * @param objectName the name of the object to find, may be null
+     * @param depth stop at the first element at this depth, ignored if negative
+     * @return the event at which this method stops, EventType.END_DOCUMENT if not found
+     * @throws JSONException in case of parse errors
+     */
+    @Override
+    public EventType next(String objectName, int depth) throws JSONException {
+        if (objectName == null && depth < 0) {
+            throw new IllegalArgumentException(JSONMessages.localize(null, "either_objectName_or_level_must_be_specified"));
+        }
+        EventType type = EventType.END_DOCUMENT;
+        for (type = next(); type != EventType.END_DOCUMENT && type != EventType.ERROR; type = next()) {
+            if (type == EventType.START_VALUE) {
+                if (objectName != null && depth >= 0) {
+                    if (checkName(objectName) && checkLevel(depth)) {
+                        break;
+                    }
+                } else {
+                    if (objectName != null && checkName(objectName)) {
+                        break;
+                    } else if (depth >= 0 && checkLevel(depth)) {
+                        break;
+                    }
+                }
+            }
+        }
+        return type;
+    }
+
+    private boolean checkName(String objectName) {
+        return objectName.equals(key());
+    }
+
+    private boolean checkLevel(int level) {
+        return level == depth;
+    }
+
+    @Override
+    public EventType next() throws JSONException {
+        boolean found = false;
+
+        if (eod) {
+           return currentEvent = EventType.END_DOCUMENT;
+        }
+
+        nullValue = false;
+
+        while (!found) {
+            try {
+                currentSymbol = JSONSymbol.next();
+            } catch(IOException e) {
+                throw new JSONException(e, scanner.line(), scanner.column());
+            }
+
+            switch(currentSymbol) {
+                case O:    //start object
+                    key = null;
+                    depth++;
+                    return currentEvent = EventType.START_OBJECT;
+                case O_:  // end object
+                    key = null;
+                    depth--;
+                    return currentEvent = EventType.END_OBJECT;
+                case OV:   //start object value
+                    if (next() == EventType.STRING) {
+                        key = JSONSymbol.getValue();
+                    } // if next is not a string an exception is thrown by the JSON parser
+                    stack.push(key);
+                    nullValue = true;
+                    return currentEvent = EventType.START_VALUE;
+                case OV_: // end object value
+                    key = stack.pop();
+                    nullValue = true;
+                    return currentEvent = EventType.END_VALUE;
+                case X:   // start document
+                    depth = -1;
+                    stack.removeAllElements();
+                    return currentEvent = EventType.START_DOCUMENT;
+                case X_:  // end document
+                    eod = true;
+                    stack.removeAllElements();
+                case EOS: // end of stream
+                    if (!eod) {
+                        throw new JSONException(JSONMessages.localize(null, "unexpected_end_of_stream"), scanner.line(), scanner.column());
+                    }
+                    depth = -1;
+                    return currentEvent = EventType.END_DOCUMENT;
+                case KEYWORD:
+                    String s = JSONSymbol.getValue();
+                    if (s.equals("true")) {
+                        nullValue = true;
+                        return currentEvent = EventType.TRUE;
+                    } else if (s.equals("false")) {
+                        nullValue = true;
+                        return currentEvent = EventType.FALSE;
+                    } else if (s.equals("null")) {
+                        nullValue = true;
+                        return currentEvent = EventType.NULL;
+                    }
+                    return currentEvent = EventType.ERROR;
+                case STRING:
+                    return currentEvent = EventType.STRING;
+                case NUMBER:
+                    if (scanner.isInteger()) {
+                        return currentEvent = EventType.INTEGER;
+                    }
+                    return currentEvent = EventType.NUMBER;
+                case A:    // start array
+                    return currentEvent = EventType.START_ARRAY;
+                case A_:    // end array
+                    return currentEvent = EventType.END_ARRAY;
+                case VA:   // start array element
+                    return currentEvent = EventType.START_ARRAY_ELEMENT;
+                case VA_:   // end array element
+                    nullValue = true;
+                    return currentEvent = EventType.END_ARRAY_ELEMENT;
+                default:
+                    found = false;
+            }
+        }
+        return currentEvent = EventType.ERROR;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return currentSymbol != JSONSymbol.X_;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String value() {
+        return nullValue ? null : JSONSymbol.getValue();
+    }
+
+    @Override
+    public int line() {
+        return scanner.line();
+    }
+
+    @Override
+    public int column() {
+        return scanner.column();
+    }
+
+    @Override
+    public long offset() {
+        return scanner.getCharacterOffset();
+    }
+
+    @Override
+    public int depth() {
+        return depth;
+    }
+
+    @Override
+    public String[] path() {
+        String[] path = new String[stack.size()];
+        Iterator<String> si = stack.iterator();
+        int i = 0;
+        while (si.hasNext()) {
+            path[i++] = si.next();
+        }
+        return path;
+    }
+
+    @Override
+    public String key() {
+        return key;
+    }
+
+    @Override
+    public String toString() {
+        String keyString = key() == null ? "" : key();
+        String valueString = value() == null ? "" : value();
+        return "(" + scanner.line() + ":" + scanner.column() + ") " +
+                currentEvent + " " +
+                ("".equals(keyString) && "".equals(valueString) ? "" : "(" + keyString + ", " + valueString + ")");
+    }
+
+    @Override
+    public JSONDocument build() {
+        if (currentEvent != EventType.START_ARRAY && currentEvent != EventType.START_OBJECT) {
+            // skip to start of array or object in order to start building
+            for (EventType type = next();
+                type != EventType.END_DOCUMENT && type != EventType.ERROR &&
+                type != EventType.START_ARRAY && type != EventType.START_OBJECT;
+                type = next()) {
+            }
+        }
+        final Stack<JSONDocument> collectionStack = new Stack<JSONDocument>();
+        switch (currentEvent) {
+            case END_DOCUMENT:
+                return null;
+            case START_ARRAY:
+                collection = new JSONDocument(JSONDocument.Type.ARRAY);
+                break;
+            case START_OBJECT:
+                collection = new JSONDocument(JSONDocument.Type.OBJECT);
+                break;
+        }
+        final JSONDocument root = collectionStack.push(collection);
+        for (EventType type = next();
+            type != EventType.END_DOCUMENT && type != EventType.ERROR && !collectionStack.empty();
+            type = next()) {
+            insert(type, collectionStack);
+        }
+        return root;
+    }
+
+    private void insert(final EventType type, Stack<JSONDocument> collectionStack) throws NumberFormatException {
+        switch (type) {
+            case TRUE:
+                insert(Boolean.TRUE);
+                break;
+            case FALSE:
+                insert(Boolean.FALSE);
+                break;
+            case NULL:
+                insert(null);
+                break;
+            case STRING:
+                insert(value());
+                break;
+            case INTEGER:
+                insert(Long.parseLong(value()));
+                break;
+            case NUMBER:
+                insert(Double.parseDouble(value()));
+                break;
+            case START_ARRAY:
+                collectionStack.push(collection);
+                final JSONDocument array = new JSONDocument(JSONDocument.Type.ARRAY);
+                insert(array);
+                collection = array;
+                break;
+            case END_ARRAY:
+                collection = collectionStack.pop();
+                break;
+            case START_VALUE:
+                collectionKey = key();
+                break;
+            case END_VALUE:
+                collectionKey = null;
+                break;
+            case START_OBJECT:
+                collectionStack.push(collection);
+                final JSONDocument map = new JSONDocument(JSONDocument.Type.OBJECT);
+                insert(map);
+                collection = map;
+                break;
+            case END_OBJECT:
+                collection = collectionStack.pop();
+                break;
+        }
+    }
+
+    private void insert(Object value) {
+        if (collection.isArray()) {
+            collection.array().add(value);
+        } else if (collection.isObject()) {
+            collection.object().put(collectionKey, value);
+        } else {
+            assert false;
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            scanner.close();
+        } catch (IOException ignore) {
+            // TODO: log the exception
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/java/com/oracle/javafx/jmx/json/impl/JSONSymbol.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx.json.impl;
+
+import com.oracle.javafx.jmx.json.JSONException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Stack;
+
+
+/**
+ * JSON syntax. Collection of symbols linked with grammar processor. Grammar
+ * tree trversal generate events fed into JSONParserHandler instance.
+ */
+enum JSONSymbol {
+
+    X, O, O1, O3, OV, A, A1, A2, V, VA,
+    CURLYOPEN, CURLYCLOSE, COLON, COMMA, SQUAREOPEN, SQUARECLOSE, KEYWORD, STRING, NUMBER, EOS,
+    X_, O_, O1_, O3_, OV_, A_, A1_, A2_, V_, VA_;
+
+    static final boolean DEBUG = false;
+
+    static {
+        final JSONSymbol[] epsilon = new JSONSymbol[0];
+        X.transition(CURLYOPEN, new JSONSymbol[]{O});
+        X.transition(SQUAREOPEN, new JSONSymbol[]{A});
+        O.transition(CURLYOPEN, new JSONSymbol[]{CURLYOPEN, O1, CURLYCLOSE});
+        O1.transition(CURLYCLOSE, epsilon);
+        O1.transition(STRING, new JSONSymbol[]{OV, O3});
+        O3.transition(CURLYCLOSE, epsilon);
+        O3.transition(COMMA, new JSONSymbol[]{COMMA, OV, O3});
+        OV.transition(STRING, new JSONSymbol[]{STRING, COLON, V});
+        A.transition(SQUAREOPEN, new JSONSymbol[]{SQUAREOPEN, A1, SQUARECLOSE});
+        A1.transition(CURLYOPEN, new JSONSymbol[]{VA, A2});
+        A1.transition(SQUAREOPEN, new JSONSymbol[]{VA, A2});
+        A1.transition(SQUARECLOSE, epsilon);
+        A1.transition(KEYWORD, new JSONSymbol[]{VA, A2});
+        A1.transition(STRING, new JSONSymbol[]{VA, A2});
+        A1.transition(NUMBER, new JSONSymbol[]{VA, A2});
+        A2.transition(COMMA, new JSONSymbol[]{COMMA, VA, A2});
+        A2.transition(SQUARECLOSE, epsilon);
+        VA.transition(CURLYOPEN, new JSONSymbol[]{V});
+        VA.transition(SQUAREOPEN, new JSONSymbol[]{V});
+        VA.transition(STRING, new JSONSymbol[]{V});
+        VA.transition(NUMBER, new JSONSymbol[]{V});
+        VA.transition(KEYWORD, new JSONSymbol[]{V});
+        V.transition(CURLYOPEN, new JSONSymbol[]{O});
+        V.transition(SQUAREOPEN, new JSONSymbol[]{A});
+        V.transition(KEYWORD, new JSONSymbol[]{KEYWORD});
+        V.transition(STRING, new JSONSymbol[]{STRING});
+        V.transition(NUMBER, new JSONSymbol[]{NUMBER});
+        X.marker(X_);
+        O.marker(O_);
+        O1.marker(O1_);
+        O3.marker(O3_);
+        OV.marker(OV_);
+        A.marker(A_);
+        A1.marker(A1_);
+        A2.marker(A2_);
+        VA.marker(VA_);
+        V.marker(V_);
+    }
+
+    boolean isTerminal = true;
+    boolean isMarker = false;
+    HashMap<JSONSymbol, JSONSymbol[]> transitions;
+    JSONSymbol markerSymbol;
+
+    private void transition(JSONSymbol s, JSONSymbol[] sequence) {
+        if (isTerminal) {
+            isTerminal = false;
+            transitions = new HashMap<JSONSymbol, JSONSymbol[]>();
+        }
+        transitions.put(s, sequence);
+    }
+
+    private void marker(JSONSymbol s) {
+        this.markerSymbol = s;
+        s.isMarker = true;
+    }
+
+    private static Stack<JSONSymbol> stack;
+    private static JSONSymbol        terminal;
+    private static JSONSymbol        current;
+    private static JSONScanner       scanner;
+    private static String            value;
+
+    static void init(JSONScanner js) throws JSONException, IOException  {
+        scanner = js;
+        stack = new Stack<JSONSymbol>();
+
+        stack.push(X);
+        terminal = scanner.nextSymbol();
+    }
+
+    static JSONSymbol next() throws JSONException, IOException {
+        current = stack.pop();
+        if (DEBUG) {
+            Object[] args = {current};
+            System.out.println(JSONMessages.localize(args, "parser_current"));
+        }
+
+        if (current.isMarker) {
+            if (current == X_) {
+                // reached bottom of processing stack
+                return current;
+            }
+        } else if (current.isTerminal) {
+            if (current != terminal) {
+                Object[] args = {current, terminal};
+                throw new JSONException(JSONMessages.localize(args, "expected_but_found"), scanner.line(), scanner.column());
+            }
+            value = scanner.getValue();
+            if (DEBUG) {
+                Object[] args = {current, value};
+                System.out.println(JSONMessages.localize(args, "parser_type"));
+            }
+            terminal = scanner.nextSymbol();
+            if (DEBUG) {
+                Object[] args = {terminal, scanner.line(), scanner.column()};
+                System.out.println(JSONMessages.localize(args, "parser_next_terminal"));
+            }
+        } else {
+            JSONSymbol[] target = current.transitions.get(terminal);
+            if (target == null) {
+                Object[] args = {terminal, current};
+                throw new JSONException(JSONMessages.localize(args, "unexpected_terminal"), scanner.line(), scanner.column());
+            }
+
+            if (DEBUG) {
+                Object[] args = {current.markerSymbol};
+                System.out.print(JSONMessages.localize(args, "parser_target") + ", ");
+            }
+            stack.push(current.markerSymbol);
+
+            for (int i = target.length; --i >= 0;) {
+                final JSONSymbol s = target[i];
+
+                if (DEBUG) {
+                    System.out.print(s.toString() + (i > 0 ? ", " : "\n"));
+                }
+
+                stack.push(s);
+            }
+        }
+        return current;
+    }
+
+    static String getValue() {
+        return value;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/resources/com/oracle/javafx/jmx/json/impl/JSONMessagesBundle.properties	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,17 @@
+unexpected_char = Unexpected character: {0}
+unexpected_end_of_stream = Unexpected end of stream
+control_character_in_string = Control character is not allowed inside string
+parser_current = parser: current = {0}
+expected_but_found = Expected {0} but found {1}
+parser_type = parser: type {0} value = {1}
+parser_next_terminal = parser: read next terminal {0} at ({1}, {2})
+unexpected_terminal = Unexpected {0} while parsing {1}
+parser_target = parser: target = {0}
+either_objectName_or_level_must_be_specified = Either objectName or level must be specified
+not_inside_object = not inside object
+not_inside_array = not inside array
+mismatched_call_to_endObject = mismatched call to endObject
+mismatched_call_to_endArray = mismatched call to endArray
+expected_got = expected {0}, got {1}
+array_syntax_cannot_be_used_for_objects = array syntax [] cannot be used for objects
+unexpected_array_at = unexpected array at: {0}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/resources/com/oracle/javafx/jmx/json/impl/JSONMessagesBundle_ja.properties	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,17 @@
+unexpected_char = \u4E88\u671F\u3057\u306A\u3044\u6587\u5B57: {0}
+unexpected_end_of_stream = \u4E88\u671F\u3057\u306A\u3044\u30B9\u30C8\u30EA\u30FC\u30E0\u306E\u7D42\u308F\u308A\u3067\u3059
+control_character_in_string = \u6587\u5B57\u5217\u306E\u5185\u90E8\u3067\u306F\u5236\u5FA1\u6587\u5B57\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093
+parser_current = \u30D1\u30FC\u30B5\u30FC: \u73FE\u5728 = {0}
+expected_but_found = {0}\u3092\u4E88\u671F\u3057\u3066\u3044\u307E\u3057\u305F\u304C\u3001{1}\u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F
+parser_type = \u30D1\u30FC\u30B5\u30FC: \u30BF\u30A4\u30D7{0}\u306E\u5024 = {1}
+parser_next_terminal = \u30D1\u30FC\u30B5\u30FC: ({1}, {2})\u3067\u6B21\u306E\u30BF\u30FC\u30DF\u30CA\u30EB{0}\u3092\u8AAD\u307F\u53D6\u308A\u307E\u3059
+unexpected_terminal = {1}\u306E\u89E3\u6790\u6642\u306B\u4E88\u671F\u3057\u306A\u3044{0}\u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F
+parser_target = \u30D1\u30FC\u30B5\u30FC: \u30BF\u30FC\u30B2\u30C3\u30C8 = {0}
+either_objectName_or_level_must_be_specified = objectName\u307E\u305F\u306F\u30EC\u30D9\u30EB\u3092\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+not_inside_object = \u5185\u90E8\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u306F\u3042\u308A\u307E\u305B\u3093
+not_inside_array = \u5185\u90E8\u914D\u5217\u3067\u306F\u3042\u308A\u307E\u305B\u3093
+mismatched_call_to_endObject = endObject\u306B\u5BFE\u3059\u308B\u4E0D\u4E00\u81F4\u306E\u30B3\u30FC\u30EB
+mismatched_call_to_endArray = endArray\u306B\u5BFE\u3059\u308B\u4E0D\u4E00\u81F4\u306E\u30B3\u30FC\u30EB
+expected_got = {0}\u3092\u4E88\u671F\u3057\u3066\u3044\u307E\u3057\u305F\u304C\u3001{1}\u3092\u53D6\u5F97\u3057\u307E\u3057\u305F
+array_syntax_cannot_be_used_for_objects = \u914D\u5217\u69CB\u6587[]\u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306B\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093
+unexpected_array_at = {0}\u3067\u4E88\u671F\u3057\u306A\u3044\u914D\u5217\u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/main/resources/com/oracle/javafx/jmx/json/impl/JSONMessagesBundle_zh_CN.properties	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,17 @@
+unexpected_char = \u610F\u5916\u7684\u5B57\u7B26: {0}
+unexpected_end_of_stream = \u610F\u5916\u7684\u6D41\u7ED3\u5C3E
+control_character_in_string = \u5728\u5B57\u7B26\u4E32\u5185\u4E0D\u5141\u8BB8\u4F7F\u7528\u63A7\u5236\u5B57\u7B26
+parser_current = \u89E3\u6790\u5668: \u5F53\u524D = {0}
+expected_but_found = \u5E94\u4E3A{0}, \u4F46\u627E\u5230{1}
+parser_type = \u89E3\u6790\u5668: \u7C7B\u578B {0} \u503C = {1}
+parser_next_terminal = \u89E3\u6790\u5668: \u5728 ({1}, {2}) \u8BFB\u53D6\u4E0B\u4E00\u4E2A\u7EC8\u6B62\u5B57\u7B26 {0}
+unexpected_terminal = \u89E3\u6790{1}\u65F6\u9047\u5230\u610F\u5916\u7684{0}
+parser_target = \u89E3\u6790\u5668: \u76EE\u6807 = {0}
+either_objectName_or_level_must_be_specified = \u5FC5\u987B\u6307\u5B9A objectName \u6216 level \u4E4B\u4E00
+not_inside_object = \u4E0D\u5728\u5BF9\u8C61\u5185
+not_inside_array = \u4E0D\u5728\u6570\u7EC4\u5185
+mismatched_call_to_endObject = \u5BF9 endObject \u7684\u8C03\u7528\u4E0D\u5339\u914D
+mismatched_call_to_endArray = \u5BF9 endArray \u7684\u8C03\u7528\u4E0D\u5339\u914D
+expected_got = \u5E94\u4E3A{0}, \u4F46\u5F97\u5230{1}
+array_syntax_cannot_be_used_for_objects = \u4E0D\u80FD\u5C06\u6570\u7EC4\u8BED\u6CD5 [] \u7528\u4E8E\u5BF9\u8C61
+unexpected_array_at = {0}\u5904\u51FA\u73B0\u610F\u5916\u7684\u6570\u7EC4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_CSS_Test.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+
+import com.oracle.javafx.jmx.json.JSONDocument;
+import javafx.scene.Group;
+import javafx.scene.shape.Shape;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class SGMXBean_CSS_Test {
+
+    private final SGMXBean mxBean = new SGMXBeanImpl();
+
+    private static Group root;
+
+    @BeforeClass
+    public static void setUp() {
+        Stage stage = new Stage();
+        root = new Group();
+        Scene scene = new Scene(root);
+        stage.setScene(scene);
+        stage.show();
+    }
+
+    @Test
+    public void cssTest() {
+        Shape node = new Rectangle(10, 20, 100, 50);
+        this.testNode(node);
+    }
+
+
+    private void testNode(Shape node) {
+        root.getChildren().clear();
+        root.getChildren().add(node);
+
+        mxBean.pause();
+
+        JSONDocument jwindows = TestUtils.getJSONDocument(mxBean.getWindows());
+        int[] windowIDs = TestUtils.getWindowIDs(jwindows);
+        assertTrue(windowIDs.length > 0);
+        JSONDocument jsceneGraph = TestUtils.getJSONDocument(mxBean.getSGTree(windowIDs[0]));
+        assertTrue(jsceneGraph.isObject());
+        JSONDocument jchildren = jsceneGraph.get("children");
+        assertTrue(jchildren.isArray());
+        assertTrue(jchildren.array().size() > 0);
+        JSONDocument jnode = (JSONDocument)jchildren.array().get(0);
+        int nodeId = jnode.getNumber("id").intValue();
+        JSONDocument jcss = TestUtils.getJSONDocument(mxBean.getCSSInfo(nodeId));
+
+
+        assertEquals(node.getStrokeWidth(), Double.parseDouble(jcss.getString("-fx-stroke-width")), 0.001);
+        assertEquals(node.isVisible(), Boolean.parseBoolean(jcss.getString("visibility")));
+        assertEquals(node.getScaleX(), Double.parseDouble(jcss.getString("-fx-scale-x")), 0.001);
+
+
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_bounds_Test.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+
+import com.oracle.javafx.jmx.json.JSONDocument;
+import javafx.geometry.Bounds;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class SGMXBean_bounds_Test {
+
+    private final SGMXBean mxBean = new SGMXBeanImpl();
+
+    private static Group root;
+
+    @BeforeClass
+    public static void setUp() {
+        Stage stage = new Stage();
+        root = new Group();
+        Scene scene = new Scene(root);
+        stage.setScene(scene);
+        stage.show();
+    }
+
+    @Test
+    public void boundsEqualTest() {
+        Node node = new Rectangle(10, 20, 100, 50);
+        this.testNode(node);
+    }
+
+    @Test
+    public void transformedNodeBoundsEqualTest() {
+        Node node = new Rectangle(0, 0, 100, 50);
+        node.setTranslateX(50);
+        node.setTranslateY(80);
+        node.setScaleX(0.8);
+        node.setScaleX(1.2);
+        node.setRotate(10);
+        this.testNode(node);
+    }
+
+    @Test
+    public void transformedRootBoundsEqualTest() {
+        Node node = new Rectangle(10, 10, 100, 50);
+        node.setTranslateX(10);
+        node.setTranslateY(100);
+        node.setScaleX(1.5);
+        node.setScaleX(1.5);
+        node.setRotate(45);
+        root.setTranslateX(50);
+        root.setTranslateY(80);
+        root.setScaleX(0.8);
+        root.setScaleX(1.2);
+        root.setRotate(10);
+        this.testNode(node);
+    }
+
+    @Test
+    public void transformedRootNodeBoundsEqualTest() {
+        Node node = new Rectangle(10, 10, 100, 50);
+        root.setTranslateX(50);
+        root.setTranslateY(80);
+        root.setScaleX(0.8);
+        root.setScaleX(1.2);
+        root.setRotate(10);
+        this.testNode(node);
+    }
+
+    private void testNode(Node node) {
+        root.getChildren().clear();
+        root.getChildren().add(node);
+
+        Bounds bounds = node.localToScene(node.getBoundsInLocal());
+
+        mxBean.pause();
+
+        JSONDocument jwindows = TestUtils.getJSONDocument(mxBean.getWindows());
+        int[] windowIDs = TestUtils.getWindowIDs(jwindows);
+        assertTrue(windowIDs.length > 0);
+        JSONDocument jsceneGraph = TestUtils.getJSONDocument(mxBean.getSGTree(windowIDs[0]));
+        assertTrue(jsceneGraph.isObject());
+        JSONDocument jchildren = jsceneGraph.get("children");
+        assertTrue(jchildren.isArray());
+        assertTrue(jchildren.array().size() > 0);
+        JSONDocument jnode = (JSONDocument)jchildren.array().get(0);
+        int nodeId = jnode.getNumber("id").intValue();
+        JSONDocument jbounds = TestUtils.getJSONDocument(mxBean.getBounds(nodeId));
+
+        assertEquals(bounds.getMinX(), jbounds.getNumber("x"));
+        assertEquals(bounds.getMinY(), jbounds.getNumber("y"));
+        assertEquals(bounds.getWidth(), jbounds.getNumber("w"));
+        assertEquals(bounds.getHeight(), jbounds.getNumber("h"));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_sceneGraph_Test.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+import com.oracle.javafx.jmx.json.JSONDocument;
+import javafx.collections.ObservableList;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+public class SGMXBean_sceneGraph_Test {
+
+    private final SGMXBean mxBean = new SGMXBeanImpl();
+
+    private static Stage stage1;
+    private static Stage stage2;
+
+    private static int nodesCount;
+
+    @BeforeClass
+    public static void setUp() {
+        stage1 = new Stage();
+        Scene scene1 = new Scene(new Group(
+                new Rectangle(100, 100),
+                new Circle(30),
+                new Group(
+                    new Rectangle(30, 30),
+                    new Line(0, 0, 100, 30)
+                )));
+        stage1.setScene(scene1);
+        stage1.show();
+
+        stage2 = new Stage();
+        Scene scene2 = new Scene(new Group(
+                new Circle(100),
+                new Line(30, 30, 20, 100)));
+        stage2.setScene(scene2);
+        stage2.show();
+
+        final Iterator<Window> it = Window.impl_getWindows();
+        nodesCount = 0;
+        while (it.hasNext()) {
+            final Window w = it.next();
+            nodesCount += countNodes(w.getScene().getRoot());
+
+        }
+    }
+
+    private static int countNodes(Parent p) {
+        ObservableList<Node> children = p.getChildrenUnmodifiable();
+        int res = 1; // the node itself
+        for (int i = 0; i < children.size(); i++) {
+            final Node n = children.get(i);
+            if (n instanceof Parent) {
+                res += countNodes((Parent)n);
+            } else {
+                res++;
+            }
+        }
+        return res;
+    }
+
+    @Test
+    public void sceneGraphsStructureTest() {
+        mxBean.pause();
+        JSONDocument jwindows = TestUtils.getJSONDocument(mxBean.getWindows());
+        int[] ids = TestUtils.getWindowIDs(jwindows);
+        for (int i = 0; i < ids.length; i++) {
+            JSONDocument sg = TestUtils.getJSONDocument(mxBean.getSGTree(ids[i]));
+            checkSGStructure(sg);
+        }
+    }
+
+    private static void checkSGStructure(JSONDocument d) {
+        assertEquals(JSONDocument.Type.OBJECT, d.type());
+        JSONDocument jchildren = d.get("children");
+        if (jchildren.equals(JSONDocument.EMPTY_OBJECT)) {
+            return;
+        }
+        assertEquals(JSONDocument.Type.ARRAY, jchildren.type());
+        for (int i = 0; i < jchildren.array().size(); i++) {
+            checkSGStructure(jchildren.get(i));
+        }
+    }
+
+    @Test
+    public void nodesCountTest() {
+        mxBean.pause();
+        JSONDocument jwindows = TestUtils.getJSONDocument(mxBean.getWindows());
+        int[] ids = TestUtils.getWindowIDs(jwindows);
+        int jNodeCount = 0;
+        for (int i = 0; i < ids.length; i++) {
+            JSONDocument sg = TestUtils.getJSONDocument(mxBean.getSGTree(ids[i]));
+            jNodeCount += countNodes(sg);
+        }
+        assertEquals(nodesCount, jNodeCount);
+    }
+
+    private static int countNodes(JSONDocument d) {
+        JSONDocument jchildren = d.get("children");
+        if (jchildren.equals(JSONDocument.EMPTY_OBJECT)) {
+            return 1;
+        }
+        int res = 1; // the node (container) itself
+        for (int i = 0; i < jchildren.array().size(); i++) {
+            res += countNodes(jchildren.get(i));
+        }
+        return res;
+    }
+
+    @Test
+    public void nodesHaveIDsTest() {
+        mxBean.pause();
+        JSONDocument jwindows = TestUtils.getJSONDocument(mxBean.getWindows());
+        int[] ids = TestUtils.getWindowIDs(jwindows);
+        for (int i = 0; i < ids.length; i++) {
+            JSONDocument sg = TestUtils.getJSONDocument(mxBean.getSGTree(ids[i]));
+            checkNodeHasID(sg);
+        }
+    }
+
+    private static void checkNodeHasID(JSONDocument d) {
+        Number id = d.getNumber("id");
+        assertNotNull(id);
+        JSONDocument jchildren = d.get("children");
+        if (jchildren.equals(JSONDocument.EMPTY_OBJECT)) {
+            return;
+        }
+        for (int i = 0; i < jchildren.array().size(); i++) {
+            checkNodeHasID(jchildren.get(i));
+        }
+    }
+
+    @Test
+    public void nodesHaveUniqueIDsTest() {
+        Set<Number> ids = new HashSet<Number>();
+        mxBean.pause();
+        JSONDocument jwindows = TestUtils.getJSONDocument(mxBean.getWindows());
+        int[] windowIDs = TestUtils.getWindowIDs(jwindows);
+        for (int i = 0; i < windowIDs.length; i++) {
+            JSONDocument sg = TestUtils.getJSONDocument(mxBean.getSGTree(windowIDs[i]));
+            checkNodeHasUniqueID(sg, ids);
+        }
+    }
+
+    private static void checkNodeHasUniqueID(JSONDocument d, Set<Number> ids) {
+        Number id = d.getNumber("id");
+        assertFalse(ids.contains(id));
+        ids.add(id);
+        JSONDocument jchildren = d.get("children");
+        if (jchildren.equals(JSONDocument.EMPTY_OBJECT)) {
+            return;
+        }
+        for (int i = 0; i < jchildren.array().size(); i++) {
+            checkNodeHasUniqueID(jchildren.get(i), ids);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_state_Test.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+import org.junit.Test;
+
+public class SGMXBean_state_Test {
+
+    private final SGMXBean mxBean = new SGMXBeanImpl();
+
+    @Test(expected=IllegalStateException.class)
+    public void stepNotPausedTest() {
+        mxBean.step();
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void getCSSInfoNotPausedTest() {
+        mxBean.getCSSInfo(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void getBoundsNotPausedTest() {
+        mxBean.getBounds(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void getSGTreeNotPausedTest() {
+        mxBean.getSGTree(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void getWindowsNotPausedTest() {
+        mxBean.getWindows();
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void addHighlightedNodeNotPausedTest() {
+        mxBean.addHighlightedNode(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void removeHighlightedNodeNotPausedTest() {
+        mxBean.removeHighlightedNode(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void addHighlightedRegionNotPausedTest() {
+        mxBean.addHighlightedRegion(0, 0, 0, 1, 1);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void removeHighlightedRegionNotPausedTest() {
+        mxBean.removeHighlightedRegion(0, 0, 0, 1, 1);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void makeScreenShotNodeNotPausedTest() {
+        mxBean.makeScreenShot(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void makeScreenShotRegionNotPausedTest() {
+        mxBean.makeScreenShot(0, 0, 0, 1, 1);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void stepNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.step();
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void getCSSInfoNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.getCSSInfo(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void getBoundsNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.getBounds(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void getSGTreeNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.getSGTree(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void getWindowsNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.getWindows();
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void addHighlightedNodeNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.addHighlightedNode(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void removeHighlightedNodeNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.removeHighlightedNode(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void addHighlightedRegionNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.addHighlightedRegion(0, 0, 0, 1, 1);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void removeHighlightedRegionNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.removeHighlightedRegion(0, 0, 0, 1, 1);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void makeScreenShotNodeNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.makeScreenShot(0);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void makeScreenShotRegionNotPaused2Test() {
+        mxBean.pause();
+        mxBean.resume();
+        mxBean.makeScreenShot(0, 0, 0, 1, 1);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/test/java/com/oracle/javafx/jmx/SGMXBean_windows_Test.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+import com.oracle.javafx.jmx.json.JSONDocument;
+import com.oracle.javafx.jmx.json.JSONFactory;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+public class SGMXBean_windows_Test {
+
+    private final SGMXBean mxBean = new SGMXBeanImpl();
+
+    private static Stage stage1;
+    private static Stage stage2;
+
+    private static int windowsCount;
+
+    @BeforeClass
+    public static void setUp() {
+        stage1 = new Stage();
+        Rectangle r1 = new Rectangle(100, 100);
+        Scene scene1 = new Scene(new Group(r1));
+        stage1.setScene(scene1);
+        stage1.show();
+
+        stage2 = new Stage();
+        Circle c1 = new Circle(100);
+        Scene scene2 = new Scene(new Group(c1));
+        stage2.setScene(scene2);
+        stage2.show();
+
+        final Iterator<Window> it = Window.impl_getWindows();
+        windowsCount = 0;
+        while (it.hasNext()) {
+            windowsCount++;
+            it.next();
+        }
+    }
+
+    private static JSONDocument getJSONDocument(String source) {
+        return JSONFactory.instance().makeReader(new StringReader(source)).build();
+    }
+
+    @Test
+    public void windowsStructureTest() {
+        mxBean.pause();
+        JSONDocument d = getJSONDocument(mxBean.getWindows());
+        assertEquals(JSONDocument.Type.ARRAY, d.type());
+        int count = d.array().size();
+        for (int i = 0; i < count; i++) {
+            JSONDocument jwindow = d.get(i);
+            assertEquals(JSONDocument.Type.OBJECT, jwindow.type());
+        }
+    }
+
+    @Test
+    public void windowsCountTest() {
+        mxBean.pause();
+        JSONDocument d = getJSONDocument(mxBean.getWindows());
+        final int count = d.array().size();
+        assertEquals(windowsCount, count);
+    }
+
+    @Test
+    public void windowsHaveIDsTest() {
+        mxBean.pause();
+        JSONDocument d = getJSONDocument(mxBean.getWindows());
+        int count = d.array().size();
+        for (int i = 0; i < count; i++) {
+            JSONDocument jwindow = d.get(i);
+            Number id = jwindow.getNumber("id");
+            assertNotNull(id);
+        }
+    }
+
+    @Test
+    public void windowsHaveUniqueIDsTest() {
+        Set<Number> ids = new HashSet<Number>();
+        mxBean.pause();
+        JSONDocument d = getJSONDocument(mxBean.getWindows());
+        int count = d.array().size();
+        for (int i = 0; i < count; i++) {
+            JSONDocument jwindow = d.get(i);
+            Number id = jwindow.getNumber("id");
+            assertFalse(ids.contains(id));
+            ids.add(id);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/jmx/src/test/java/com/oracle/javafx/jmx/TestUtils.java	Wed Oct 30 13:48:45 2013 -0700
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.javafx.jmx;
+
+import com.oracle.javafx.jmx.json.JSONDocument;
+import com.oracle.javafx.jmx.json.JSONFactory;
+
+import java.io.StringReader;
+
+public class TestUtils {
+
+    static JSONDocument getJSONDocument(String source) {
+        return JSONFactory.instance().makeReader(new StringReader(source)).build();
+    }
+
+    static int[] getWindowIDs(JSONDocument jwindows) {
+        final int windowsCount = jwindows.array().size();
+        int res[] = new int[windowsCount];
+        for (int i = 0; i < windowsCount; i++) {
+            res[i] = jwindows.get(i).getNumber("id").intValue();
+        }
+        return res;
+    }
+
+}
--- a/settings.gradle	Wed Oct 30 15:53:41 2013 +0000
+++ b/settings.gradle	Wed Oct 30 13:48:45 2013 -0700
@@ -23,7 +23,7 @@
  * questions.
  */
 
-include "base", "graphics", "controls", "swing", "swt", "fxml", "builders", "designTime", "fxpackager", "web", "media", "systemTests"
+include "base", "graphics", "controls", "swing", "swt", "fxml", "builders", "designTime", "fxpackager", "jmx", "web", "media", "systemTests"
 
 project(":base").projectDir = file("modules/base")
 project(":graphics").projectDir = file("modules/graphics")
@@ -34,6 +34,7 @@
 project(":builders").projectDir = file("modules/builders")
 project(":designTime").projectDir = file("modules/designTime")
 project(":fxpackager").projectDir = file("modules/fxpackager")
+project(":jmx").projectDir = file("modules/jmx")
 project(":web").projectDir = file("modules/web")
 project(":media").projectDir = file("modules/media")
 project(":systemTests").projectDir = file("tests/system")