changeset 2287:d67bf0b88354

Fix for RT-26386: change in node orientation does not traverse the entire tree
author Lubomir Nerad <lubomir.nerad@oracle.com>
date Fri, 18 Jan 2013 17:14:59 +0100
parents 580420b7aeb4
children 9630723ae1a7
files javafx-ui-common/src/com/sun/javafx/scene/SceneHelper.java javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java javafx-ui-common/src/javafx/scene/Node.java javafx-ui-common/src/javafx/scene/Parent.java javafx-ui-common/src/javafx/scene/Scene.java javafx-ui-common/src/javafx/stage/PopupWindow.java javafx-ui-common/src/javafx/stage/Stage.java javafx-ui-common/test/unit/com/sun/javafx/test/OrientationHelper.java javafx-ui-common/test/unit/javafx/scene/Node_effectiveOrientation_Test.java javafx-ui-common/test/unit/javafx/scene/Node_hasMirroring_Test.java
diffstat 10 files changed, 747 insertions(+), 89 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/com/sun/javafx/scene/SceneHelper.java	Fri Jan 18 17:14:59 2013 +0100
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 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.sun.javafx.scene;
+
+import javafx.scene.Scene;
+
+/**
+ * Used to access internal scene methods.
+ */
+public final class SceneHelper {
+    private static SceneAccessor sceneAccessor;
+
+    static {
+        forceInit(Scene.class);
+    }
+
+    private SceneHelper() {
+    }
+
+    public static void setPaused(final boolean paused) {
+        sceneAccessor.setPaused(paused);
+    }
+
+    public static void parentEffectiveOrientationChanged(final Scene scene) {
+        sceneAccessor.parentEffectiveOrientationChanged(scene);
+    }
+
+    public static void setSceneAccessor(final SceneAccessor newAccessor) {
+        if (sceneAccessor != null) {
+            throw new IllegalStateException();
+        }
+
+        sceneAccessor = newAccessor;
+    }
+
+    public interface SceneAccessor {
+        void setPaused(boolean paused);
+
+        void parentEffectiveOrientationChanged(Scene scene);
+    }
+
+    private static void forceInit(final Class<?> classToInit) {
+        try {
+            Class.forName(classToInit.getName(), true,
+                          classToInit.getClassLoader());
+        } catch (final ClassNotFoundException e) {
+            throw new AssertionError(e);  // Can't happen
+        }
+    }
+}
--- a/javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java	Fri Jan 18 16:05:36 2013 +0100
+++ b/javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java	Fri Jan 18 17:14:59 2013 +0100
@@ -25,6 +25,7 @@
 
 package com.sun.javafx.tk;
 
+import com.sun.javafx.scene.SceneHelper;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -827,9 +828,7 @@
             }
         }
         this.getMasterTimer().pause();
-        if (sceneAccessor != null) {
-            sceneAccessor.setPaused(true);
-        }
+        SceneHelper.setPaused(true);
     }
 
     /*
@@ -837,9 +836,7 @@
      * It is used by Scenegraph-JMX bean.
      */
     public void resumeScenes() {
-        if (sceneAccessor != null) {
-            sceneAccessor.setPaused(false);
-        }
+        SceneHelper.setPaused(false);
         this.getMasterTimer().resume();
         Iterator<Window> i = Window.impl_getWindows();
         while (i.hasNext()) {
@@ -883,18 +880,6 @@
         return highlightRegions;
     }
 
-    // Accessor for scene class
-    public interface SceneAccessor {
-        // Pause all scenes
-        public void setPaused(boolean paused);
-    }
-
-    private static SceneAccessor sceneAccessor = null;
-
-    public static void setSceneAccessor(SceneAccessor accessor) {
-        sceneAccessor = accessor;
-    }
-
     public interface WritableImageAccessor {
         public void loadTkImage(WritableImage wimg, Object loader);
         public Object getTkImageLoader(WritableImage wimg);
--- a/javafx-ui-common/src/javafx/scene/Node.java	Fri Jan 18 16:05:36 2013 +0100
+++ b/javafx-ui-common/src/javafx/scene/Node.java	Fri Jan 18 17:14:59 2013 +0100
@@ -704,6 +704,7 @@
                     updateTreeVisible();
                     oldParent = newParent;
                     invalidateLocalToSceneTransform();
+                    parentEffectiveOrientationChanged();
                 }
 
                 @Override
@@ -771,6 +772,11 @@
                     peer.release();
                 }
             }
+            if (getParent() == null) {
+                // if we are the root we need to handle scene change
+                parentEffectiveOrientationChanged();
+            }
+
             oldScene = _scene;
             // we need to check if a override has been set, if so we need to apply it to the node now
             // that it may have a valid scene
@@ -4083,31 +4089,24 @@
             }
 
             // Check to see whether the node requires mirroring
-            if (getParent() == null) {
-                Scene scene = getScene();
-                if (scene != null && equals(scene.getRoot())) {
-                    // Mirror the root node
-                    if (hasMirroring()) {
-                        double xOffset = scene.getWidth() / 2;
-                        localToParentTx.translate(xOffset, 0, 0);
-                        localToParentTx.scale(-1, 1);
-                        localToParentTx.translate(-xOffset, 0, 0);
-                    }
-                }
-            } else {
-                // Mirror a leaf node
-                if (hasMirroring()) {
-                    double xOffset = impl_getPivotX();
-                    localToParentTx.translate(xOffset, 0, 0);
-                    localToParentTx.scale(-1, 1);
-                    localToParentTx.translate(-xOffset, 0, 0);
-                }
+            if (hasMirroring()) {
+                final double xOffset = getMirroringCenter();
+                localToParentTx.translate(xOffset, 0, 0);
+                localToParentTx.scale(-1, 1);
+                localToParentTx.translate(-xOffset, 0, 0);
             }
-            
+
             transformDirty = false;
         }
     }
 
+    private double getMirroringCenter() {
+        final Scene sceneValue = getScene();
+        return ((sceneValue != null) && (sceneValue.getRoot() == this))
+                   ? sceneValue.getWidth() / 2
+                   : impl_getPivotX();
+    }
+
     /**
      * Transforms in place the specified point from parent coords to local
      * coords. Made package private for the sake of testing.
@@ -5193,7 +5192,10 @@
      **************************************************************************/
     
     private ObjectProperty<NodeOrientation> nodeOrientation;
-    
+
+    private NodeOrientation effectiveNodeOrientation;
+    private NodeOrientation automaticNodeOrientation;
+
     public final void setNodeOrientation(NodeOrientation orientation) {
         nodeOrientationProperty().set(orientation);
     }
@@ -5218,16 +5220,7 @@
             nodeOrientation = new StyleableObjectProperty<NodeOrientation>(NodeOrientation.INHERIT) {
                 @Override
                 protected void invalidated() {
-                    impl_transformsChanged();
-                    // Apply the transform to all the children of this node
-                    if (Node.this instanceof Parent) {
-                        Parent p = (Parent)Node.this;
-                        List<Node> children = p.getChildren();
-                        for (int i = 0, max = children.size(); i < max; i++) {
-                            Node n = p.getChildren().get(i);
-                            n.impl_transformsChanged();
-                        }
-                    }
+                    nodeEffectiveOrientationChanged();
                 }
                 
                 @Override
@@ -5259,9 +5252,13 @@
      * </p>
      */
     public final NodeOrientation getEffectiveNodeOrientation() {
-        return getEffectiveNodeOrientation(false);
-    }
-    
+        if (effectiveNodeOrientation == null) {
+            effectiveNodeOrientation = calcEffectiveNodeOrientation();
+        }
+
+        return effectiveNodeOrientation;
+    }
+
     /**
      * Determines whether a node should be mirrored when node orientation
      * is right-to-left.
@@ -5277,36 +5274,90 @@
     public boolean isAutomaticallyMirrored() {
         return true;
     }
-    
-    NodeOrientation getNodeOrientation(boolean actual) {
-        if (actual && !isAutomaticallyMirrored()) return NodeOrientation.LEFT_TO_RIGHT;
-        return nodeOrientation == null ? NodeOrientation.INHERIT : nodeOrientation.get();
-    }
-    
-    final NodeOrientation getEffectiveNodeOrientation(boolean actual) {
-        NodeOrientation orientation = getNodeOrientation(actual);
-        if (orientation == NodeOrientation.INHERIT) {
-            Parent parent = getParent();
-            if (parent != null) return parent.getEffectiveNodeOrientation(actual);
-            Scene scene = getScene();
-            if (scene != null) return scene.getEffectiveNodeOrientation();
+
+    NodeOrientation getAutomaticNodeOrientation() {
+        if (automaticNodeOrientation == null) {
+            automaticNodeOrientation = calcAutomaticNodeOrientation();
+        }
+
+        return automaticNodeOrientation;
+    }
+
+    final void parentEffectiveOrientationChanged() {
+        if (getNodeOrientation() == NodeOrientation.INHERIT) {
+            nodeEffectiveOrientationChanged();
+        } else {
+            // mirroring changed
+            impl_transformsChanged();
+        }
+    }
+
+    void nodeEffectiveOrientationChanged() {
+        effectiveNodeOrientation = null;
+        automaticNodeOrientation = null;
+        // mirroring changed
+        impl_transformsChanged();
+    }
+
+    private NodeOrientation calcEffectiveNodeOrientation() {
+        final NodeOrientation nodeOrientationValue = getNodeOrientation();
+        if (nodeOrientationValue != NodeOrientation.INHERIT) {
+            return nodeOrientationValue;
+        }
+
+        final Node parentValue = getParent();
+        if (parentValue != null) {
+            return parentValue.getEffectiveNodeOrientation();
+        }
+
+        final Scene sceneValue = getScene();
+        if (sceneValue != null) {
+            return sceneValue.getEffectiveNodeOrientation();
+        }
+
+        return NodeOrientation.LEFT_TO_RIGHT;
+    }
+
+    private NodeOrientation calcAutomaticNodeOrientation() {
+        if (!isAutomaticallyMirrored()) {
             return NodeOrientation.LEFT_TO_RIGHT;
         }
-        return orientation;
-    }
-    
+
+        final NodeOrientation nodeOrientationValue = getNodeOrientation();
+        if (nodeOrientationValue != NodeOrientation.INHERIT) {
+            return nodeOrientationValue;
+        }
+
+        final Node parentValue = getParent();
+        if (parentValue != null) {
+            // automatic node orientation is inherited
+            return parentValue.getAutomaticNodeOrientation();
+        }
+
+        final Scene sceneValue = getScene();
+        if (sceneValue != null) {
+            return sceneValue.getEffectiveNodeOrientation();
+        }
+
+        return NodeOrientation.LEFT_TO_RIGHT;
+    }
+
     // Return true if the node needs to be mirrored.
     // A node has mirroring if the orientation differs from the parent
-    private boolean hasMirroring() {
-        Parent parent = getParent();
-        if (parent == null) {
-            return getEffectiveNodeOrientation(true) == NodeOrientation.RIGHT_TO_LEFT;
-        }
-        NodeOrientation orientation = getNodeOrientation(true);
-        if (orientation == NodeOrientation.INHERIT) return false;
-        return orientation != parent.getEffectiveNodeOrientation(true);
-    }
-    
+    // package private for testing
+    boolean hasMirroring() {
+        final Parent parentValue = getParent();
+
+        final NodeOrientation thisOrientation =
+                getAutomaticNodeOrientation();
+        final NodeOrientation parentOrientation =
+                (parentValue != null)
+                    ? parentValue.getAutomaticNodeOrientation()
+                    : NodeOrientation.LEFT_TO_RIGHT;
+
+        return thisOrientation != parentOrientation;
+    }
+
     /***************************************************************************
      *                                                                         *
      *                       Misc Seldom Used Properties                       *
--- a/javafx-ui-common/src/javafx/scene/Parent.java	Fri Jan 18 16:05:36 2013 +0100
+++ b/javafx-ui-common/src/javafx/scene/Parent.java	Fri Jan 18 17:14:59 2013 +0100
@@ -1222,6 +1222,14 @@
         return ((PGGroup) impl_getPGNode());
     }
 
+    @Override
+    void nodeEffectiveOrientationChanged() {
+        super.nodeEffectiveOrientationChanged();
+
+        for (int i = 0, max = children.size(); i < max; ++i) {
+            children.get(i).parentEffectiveOrientationChanged();
+        }
+    }
 
     /***************************************************************************
      *                                                                         *
--- a/javafx-ui-common/src/javafx/scene/Scene.java	Fri Jan 18 16:05:36 2013 +0100
+++ b/javafx-ui-common/src/javafx/scene/Scene.java	Fri Jan 18 17:14:59 2013 +0100
@@ -26,6 +26,7 @@
 
 package javafx.scene;
 
+import com.sun.javafx.scene.SceneHelper;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
@@ -331,11 +332,19 @@
                     return scene.snapshot(null).impl_getPlatformImage();
                 }
             });
-            Toolkit.setSceneAccessor(new Toolkit.SceneAccessor() {
-                @Override public void setPaused(boolean paused) {
-                    Scene.paused = paused;
-                }
-            });
+            SceneHelper.setSceneAccessor(
+                    new SceneHelper.SceneAccessor() {
+                        @Override
+                        public void setPaused(boolean paused) {
+                            Scene.paused = paused;
+                        }
+                        
+                        @Override
+                        public void parentEffectiveOrientationChanged(
+                                final Scene scene) {
+                            scene.parentEffectiveOrientationChanged();
+                        }
+                    });
         }
 
         // Reserve space for 30 nodes in the dirtyNodes set.
@@ -599,6 +608,7 @@
                     if (newWindow != null) {
                         impl_initPeer();
                     }
+                    parentEffectiveOrientationChanged();
 
                     oldWindow = newWindow;
                 }
@@ -5574,7 +5584,8 @@
 
     
     private ObjectProperty<NodeOrientation> nodeOrientation;
-    
+    private NodeOrientation effectiveNodeOrientation;
+
     public final void setNodeOrientation(NodeOrientation orientation) {
         nodeOrientationProperty().set(orientation);
     }
@@ -5597,12 +5608,12 @@
      */
     public final ObjectProperty<NodeOrientation> nodeOrientationProperty() {
         if (nodeOrientation == null) {
-            nodeOrientation = new StyleableObjectProperty<NodeOrientation>(NodeOrientation.INHERIT) {       
+            nodeOrientation = new StyleableObjectProperty<NodeOrientation>(defaultNodeOrientation) {
                 @Override
                 protected void invalidated() {
-                    
-                }
-                
+                    sceneEffectiveOrientationChanged();
+                }
+
                 @Override
                 public Object getBean() {
                     return Scene.this;
@@ -5624,6 +5635,25 @@
     }
 
     public final NodeOrientation getEffectiveNodeOrientation() {
+        if (effectiveNodeOrientation == null) {
+            effectiveNodeOrientation = calcEffectiveNodeOrientation();
+        }
+
+        return effectiveNodeOrientation;
+    }
+
+    private void parentEffectiveOrientationChanged() {
+        if (getNodeOrientation() == NodeOrientation.INHERIT) {
+            sceneEffectiveOrientationChanged();
+        }
+    }
+
+    private void sceneEffectiveOrientationChanged() {
+        effectiveNodeOrientation = null;
+        getRoot().parentEffectiveOrientationChanged();
+    }
+
+    private NodeOrientation calcEffectiveNodeOrientation() {
         NodeOrientation orientation = getNodeOrientation();
         if (orientation == NodeOrientation.INHERIT) {
             Window window = getWindow();
--- a/javafx-ui-common/src/javafx/stage/PopupWindow.java	Fri Jan 18 16:05:36 2013 +0100
+++ b/javafx-ui-common/src/javafx/stage/PopupWindow.java	Fri Jan 18 17:14:59 2013 +0100
@@ -53,6 +53,7 @@
 import com.sun.javafx.event.EventRedirector;
 import com.sun.javafx.event.EventUtil;
 import com.sun.javafx.perf.PerformanceTracker;
+import com.sun.javafx.scene.SceneHelper;
 import com.sun.javafx.stage.FocusUngrabEvent;
 import com.sun.javafx.stage.PopupWindowPeerListener;
 import com.sun.javafx.stage.WindowCloseRequestHandler;
@@ -360,6 +361,11 @@
             ((PopupWindow)owner).children.add(this);
         }
 
+        final Scene sceneValue = getScene();
+        if (sceneValue != null) {
+            SceneHelper.parentEffectiveOrientationChanged(sceneValue);
+        }
+
         // It is required that the root window exist and be visible to show the popup.
         if (getRootWindow(owner).isShowing()) {
             // We do show() first so that the width and height of the
--- a/javafx-ui-common/src/javafx/stage/Stage.java	Fri Jan 18 16:05:36 2013 +0100
+++ b/javafx-ui-common/src/javafx/stage/Stage.java	Fri Jan 18 17:14:59 2013 +0100
@@ -43,6 +43,7 @@
 import com.sun.javafx.beans.annotations.Default;
 import com.sun.javafx.collections.TrackableObservableList;
 import com.sun.javafx.robot.impl.FXRobotHelper;
+import com.sun.javafx.scene.SceneHelper;
 import com.sun.javafx.stage.StageHelper;
 import com.sun.javafx.stage.StagePeerListener;
 import com.sun.javafx.tk.TKPulseListener;
@@ -493,7 +494,11 @@
         }
 
         this.owner = owner;
-
+        
+        final Scene sceneValue = getScene();
+        if (sceneValue != null) {
+            SceneHelper.parentEffectiveOrientationChanged(sceneValue);
+        }
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/test/unit/com/sun/javafx/test/OrientationHelper.java	Fri Jan 18 17:14:59 2013 +0100
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 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.sun.javafx.test;
+
+import java.util.List;
+import javafx.geometry.NodeOrientation;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+
+public final class OrientationHelper {
+    private OrientationHelper() {
+    }
+
+    public interface StateEncoder {
+        char map(Scene scene);
+        char map(Node node);
+    }
+
+    public static void updateOrientation(final Scene scene,
+                                         final String updateString) {
+        final NodeOrientation update =
+                decode(updateString.charAt(0));
+        if (update != null) {
+            scene.setNodeOrientation(update);
+        }
+
+        final Node rootNode = scene.getRoot();
+        if (rootNode != null) {
+            updateOrientation(rootNode, updateString, 1);
+        }
+    }
+
+    public static String collectState(final Scene scene,
+                                      final StateEncoder encoder) {
+        final StringBuilder dest = new StringBuilder();
+        collectState(dest, scene, encoder);
+        return dest.toString();
+    }
+
+    public static String collectState(final Node node,
+                                      final StateEncoder encoder) {
+        final StringBuilder dest = new StringBuilder();
+        collectState(dest, node, encoder);
+        return dest.toString();
+    }
+
+    private static int updateOrientation(final Node node,
+                                         final String updateString,
+                                         final int index) {
+        final NodeOrientation update =
+                decode(updateString.charAt(index));
+        if (update != null) {
+            node.setNodeOrientation(update);
+        }
+
+        int nextIndex = index + 1;
+        if (node instanceof Parent) {
+            final List<Node> childNodes =
+                    ((Parent) node).getChildrenUnmodifiable();
+            for (final Node childNode: childNodes) {
+                nextIndex = updateOrientation(childNode, updateString,
+                                              nextIndex);
+            }
+        }
+
+        return nextIndex;
+    }
+
+    private static NodeOrientation decode(final char updateChar) {
+        switch (updateChar) {
+            case '.':
+                return null;
+            case 'L':
+                return NodeOrientation.LEFT_TO_RIGHT;
+            case 'R':
+                return NodeOrientation.RIGHT_TO_LEFT;
+            case 'I':
+                return NodeOrientation.INHERIT;
+            default:
+                throw new IllegalArgumentException("Invalid update character");
+        }
+    }
+
+    private static void collectState(final StringBuilder dest,
+                                     final Scene scene,
+                                     final StateEncoder encoder) {
+        dest.append(encoder.map(scene));
+        final Node rootNode = scene.getRoot();
+        if (rootNode != null) {
+            collectState(dest, rootNode, encoder);
+        }
+    }
+
+    private static void collectState(final StringBuilder dest,
+                                     final Node node,
+                                     final StateEncoder encoder) {
+        dest.append(encoder.map(node));
+        if (node instanceof Parent) {
+            final List<Node> childNodes =
+                    ((Parent) node).getChildrenUnmodifiable();
+            for (final Node childNode: childNodes) {
+                collectState(dest, childNode, encoder);
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/test/unit/javafx/scene/Node_effectiveOrientation_Test.java	Fri Jan 18 17:14:59 2013 +0100
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 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 javafx.scene;
+
+import com.sun.javafx.test.OrientationHelper;
+import com.sun.javafx.test.OrientationHelper.StateEncoder;
+import java.util.Arrays;
+import java.util.Collection;
+import javafx.geometry.NodeOrientation;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class Node_effectiveOrientation_Test {
+    private final Scene testScene;
+    private final String orientationUpdate;
+    private final String expectedOrientation;
+
+    private static Scene lriiliScene() {
+        return ltrScene(rtlGroup(inhGroup(inhGroup(ltrGroup(inhGroup())))));
+    }
+
+    private static Scene liirliPrecachedScene() {
+        final Scene scene =
+                ltrScene(inhGroup(inhGroup(rtlGroup(ltrGroup(inhGroup())))));
+        // force caching
+        collectOrientation(scene);
+        return scene;
+    }
+
+    private static Scene riirliPlugedPrecachedScenegraphScene() {
+        final Group root =
+                inhGroup(inhGroup(rtlGroup(ltrGroup(inhGroup()))));
+        // force caching
+        collectOrientation(root);
+
+        final Scene scene = new Scene(new Group());
+        scene.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
+        scene.setRoot(root);
+
+        return scene;
+    }
+
+    /*
+     * Parameters: [testScene], [orientationUpdate], [expectedOrientation]
+     */
+    @Parameters
+    public static Collection data() {
+        return Arrays.asList(
+                new Object[][] {
+                        { lriiliScene(), "......", "LRRRLL" },
+                        { lriiliScene(), ".I....", "LLLLLL" },
+                        { lriiliScene(), "...L..", "LRRLLL" },
+                        { lriiliScene(), "....I.", "LRRRRR" },
+                        { lriiliScene(), "RIIIII", "RRRRRR" },
+
+                        { liirliPrecachedScene(), "......", "LLLRLL" },
+                        { liirliPrecachedScene(), "R.....", "RRRRLL" },
+                        { liirliPrecachedScene(), "...I..", "LLLLLL" },
+                        { liirliPrecachedScene(), "R..IR.", "RRRRRR" },
+
+                        {
+                            riirliPlugedPrecachedScenegraphScene(),
+                            "......", "RRRRLL"
+                        }
+                    });
+    }
+
+    public Node_effectiveOrientation_Test(
+            final Scene testScene,
+            final String orientationUpdate,
+            final String expectedOrientation) {
+        this.testScene = testScene;
+        this.orientationUpdate = orientationUpdate;
+        this.expectedOrientation = expectedOrientation;
+    }
+
+    @Test
+    public void effectiveOrientationTest() {
+        OrientationHelper.updateOrientation(testScene, orientationUpdate);
+        assertOrientation(testScene, expectedOrientation);
+    }
+
+    private static Scene ltrScene(final Parent rootNode) {
+        final Scene scene = new Scene(rootNode);
+        scene.setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
+        return scene;
+    }
+
+    private static Group ltrGroup(final Node... childNodes) {
+        return group(NodeOrientation.LEFT_TO_RIGHT, childNodes);
+    }
+
+    private static Group rtlGroup(final Node... childNodes) {
+        return group(NodeOrientation.RIGHT_TO_LEFT, childNodes);
+    }
+
+    private static Group inhGroup(final Node... childNodes) {
+        return group(NodeOrientation.INHERIT, childNodes);
+    }
+
+    private static Group group(final NodeOrientation nodeOrientation,
+                               final Node... childNodes) {
+        final Group group = new Group();
+        group.setNodeOrientation(nodeOrientation);
+        group.getChildren().setAll(childNodes);
+
+        return group;
+    }
+
+    private static void assertOrientation(
+            final Scene scene,
+            final String expectedOrientation) {
+        final String actualOrientation = collectOrientation(scene);
+        Assert.assertEquals("Orientation mismatch",
+                            expectedOrientation, actualOrientation);
+    }
+
+    private static final StateEncoder EFFECTIVE_ORIENTATION_ENCODER =
+            new StateEncoder() {
+                @Override
+                public char map(final Scene scene) {
+                    return map(scene.getEffectiveNodeOrientation());
+                }
+
+                @Override
+                public char map(final Node node) {
+                    return map(node.getEffectiveNodeOrientation());
+                }
+
+                private char map(final NodeOrientation effectiveOrientation) {
+                    switch (effectiveOrientation) {
+                        case LEFT_TO_RIGHT:
+                            return 'L';
+                        case RIGHT_TO_LEFT:
+                            return 'R';
+                        default:
+                            throw new IllegalArgumentException(
+                                          "Invalid orientation");
+                    }
+                }
+            };
+
+    private static String collectOrientation(final Scene scene) {
+        return OrientationHelper.collectState(scene,
+                                              EFFECTIVE_ORIENTATION_ENCODER);
+    }
+
+    private static String collectOrientation(final Node node) {
+        return OrientationHelper.collectState(node,
+                                              EFFECTIVE_ORIENTATION_ENCODER);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/test/unit/javafx/scene/Node_hasMirroring_Test.java	Fri Jan 18 17:14:59 2013 +0100
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 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 javafx.scene;
+
+import com.sun.javafx.test.OrientationHelper;
+import com.sun.javafx.test.OrientationHelper.StateEncoder;
+import java.util.Arrays;
+import java.util.Collection;
+import javafx.geometry.NodeOrientation;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class Node_hasMirroring_Test {
+    private final Scene testScene;
+    private final String orientationUpdate;
+    private final String expectedMirroring;
+
+    private static Scene lriiliScene() {
+        return ltrScene(
+                   rtlAutGroup(
+                       inhAutGroup(
+                           inhAutGroup(
+                               ltrAutGroup(
+                                   inhAutGroup())))));
+    }
+
+    private static Scene lrIiliScene() {
+        return ltrScene(
+                   rtlAutGroup(
+                       inhManGroup(
+                           inhAutGroup(
+                               ltrAutGroup(
+                                   inhAutGroup())))));
+    }
+
+    private static Scene lrLRlrScene() {
+        return ltrScene(
+                   rtlAutGroup(
+                       ltrManGroup(
+                           rtlManGroup(
+                               ltrAutGroup(
+                                   rtlAutGroup())))));
+    }
+
+    /*
+     * Parameters: [testScene], [orientationUpdate], [expectedMirroring]
+     */
+    @Parameters
+    public static Collection data() {
+        return Arrays.asList(
+                new Object[][] {
+                        { lriiliScene(), "......", ".M..M." }, // LRRRLL
+                        { lriiliScene(), ".I....", "......" }, // LLLLLL
+                        { lriiliScene(), "...L..", ".M.M.." }, // LRRLLL
+                        { lriiliScene(), "....I.", ".M...." }, // LRRRRR
+                        { lriiliScene(), "RIIIII", ".M...." }, // RRRRRR
+
+                        /* effective: LRRRLL, automatic: LRLLLL */
+                        { lrIiliScene(), "......", ".MM..." },
+                        /* effective: LRLRLR, automatic: LRLLLR */
+                        { lrLRlrScene(), "......", ".MM..M" }
+                    });
+    }
+
+    public Node_hasMirroring_Test(
+            final Scene testScene,
+            final String orientationUpdate,
+            final String expectedMirroring) {
+        this.testScene = testScene;
+        this.orientationUpdate = orientationUpdate;
+        this.expectedMirroring = expectedMirroring;
+    }
+
+    @Test
+    public void hasMirroringTest() {
+        OrientationHelper.updateOrientation(testScene, orientationUpdate);
+        assertMirroring(testScene, expectedMirroring);
+    }
+
+    private static Scene ltrScene(final Parent rootNode) {
+        final Scene scene = new Scene(rootNode);
+        scene.setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
+        return scene;
+    }
+
+    private static Group ltrAutGroup(final Node... childNodes) {
+        return autGroup(NodeOrientation.LEFT_TO_RIGHT, childNodes);
+    }
+
+    private static Group rtlAutGroup(final Node... childNodes) {
+        return autGroup(NodeOrientation.RIGHT_TO_LEFT, childNodes);
+    }
+
+    private static Group inhAutGroup(final Node... childNodes) {
+        return autGroup(NodeOrientation.INHERIT, childNodes);
+    }
+
+    private static Group ltrManGroup(final Node... childNodes) {
+        return manGroup(NodeOrientation.LEFT_TO_RIGHT, childNodes);
+    }
+
+    private static Group rtlManGroup(final Node... childNodes) {
+        return manGroup(NodeOrientation.RIGHT_TO_LEFT, childNodes);
+    }
+
+    private static Group inhManGroup(final Node... childNodes) {
+        return manGroup(NodeOrientation.INHERIT, childNodes);
+    }
+
+    private static Group autGroup(final NodeOrientation nodeOrientation,
+                                  final Node... childNodes) {
+        final Group group = new Group();
+        group.setNodeOrientation(nodeOrientation);
+        group.getChildren().setAll(childNodes);
+
+        return group;
+    }
+
+    private static Group manGroup(final NodeOrientation nodeOrientation,
+                                  final Node... childNodes) {
+        final Group group = new Group() {
+                                    @Override
+                                    public boolean isAutomaticallyMirrored() {
+                                        return false;
+                                    }
+                                };
+        group.setNodeOrientation(nodeOrientation);
+        group.getChildren().setAll(childNodes);
+
+        return group;
+    }
+
+    private static void assertMirroring(
+            final Scene scene,
+            final String expectedMirroring) {
+        final String actualMirroring = collectMirroring(scene);
+        Assert.assertEquals("Mirroring mismatch",
+                            expectedMirroring, actualMirroring);
+    }
+
+    private static final StateEncoder HAS_MIRRORING_ENCODER =
+            new StateEncoder() {
+                @Override
+                public char map(final Scene scene) {
+                    // no mirroring on scene
+                    return map(false);
+                }
+
+                @Override
+                public char map(final Node node) {
+                    return map(node.hasMirroring());
+                }
+
+                private char map(final boolean hasMirroring) {
+                    return hasMirroring ? 'M' : '.';
+                }
+            };
+
+    private static String collectMirroring(final Scene scene) {
+        return OrientationHelper.collectState(scene, HAS_MIRRORING_ENCODER);
+    }
+}