6852592: invalidate() must be smarter
authoranthony
Wed, 21 Oct 2009 17:06:41 +0400
changeset 4261 126dc6fe0d7b
parent 4260 913d5ec97629
child 4262 3aee79f8498a
6852592: invalidate() must be smarter Summary: Introduce validate roots in AWT Reviewed-by: alexp, art, dcherepanov
jdk/src/share/classes/java/applet/Applet.java
jdk/src/share/classes/java/awt/Component.java
jdk/src/share/classes/java/awt/Container.java
jdk/src/share/classes/java/awt/Window.java
jdk/src/share/classes/javax/swing/JComponent.java
jdk/src/share/classes/javax/swing/JRootPane.java
jdk/src/share/classes/javax/swing/JScrollPane.java
jdk/src/share/classes/javax/swing/JSplitPane.java
jdk/src/share/classes/javax/swing/JTextField.java
jdk/src/share/classes/javax/swing/JViewport.java
jdk/src/share/classes/javax/swing/RepaintManager.java
jdk/src/share/classes/javax/swing/SwingUtilities.java
jdk/test/java/awt/Container/ValidateRoot/InvalidateMustRespectValidateRoots.java
--- a/jdk/src/share/classes/java/applet/Applet.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/java/applet/Applet.java	Wed Oct 21 17:06:41 2009 +0400
@@ -230,6 +230,21 @@
     }
 
     /**
+     * Indicates if this container is a validate root.
+     * <p>
+     * {@code Applet} objects are the validate roots, and, therefore, they
+     * override this method to return {@code true}.
+     *
+     * @return {@code true}
+     * @since 1.7
+     * @see java.awt.Container#isValidateRoot
+     */
+    @Override
+    public boolean isValidateRoot() {
+        return true;
+    }
+
+    /**
      * Requests that the argument string be displayed in the
      * "status window". Many browsers and applet viewers
      * provide such a window, where the application can inform users of
--- a/jdk/src/share/classes/java/awt/Component.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/java/awt/Component.java	Wed Oct 21 17:06:41 2009 +0400
@@ -2764,8 +2764,11 @@
     }
 
     /**
-     * Ensures that this component has a valid layout.  This method is
-     * primarily intended to operate on instances of <code>Container</code>.
+     * Validates this component.
+     * <p>
+     * The meaning of the term <i>validating</i> is defined by the ancestors of
+     * this class. See {@link Container#validate} for more details.
+     *
      * @see       #invalidate
      * @see       #doLayout()
      * @see       LayoutManager
@@ -2794,12 +2797,24 @@
     }
 
     /**
-     * Invalidates this component.  This component and all parents
-     * above it are marked as needing to be laid out.  This method can
-     * be called often, so it needs to execute quickly.
+     * Invalidates this component and its ancestors.
+     * <p>
+     * All the ancestors of this component up to the nearest validate root are
+     * marked invalid also. If there is no a validate root container for this
+     * component, all of its ancestors up to the root of the hierarchy are
+     * marked invalid as well. Marking a container <i>invalid</i> indicates
+     * that the container needs to be laid out.
+     * <p>
+     * This method is called automatically when any layout-related information
+     * changes (e.g. setting the bounds of the component, or adding the
+     * component to a container).
+     * <p>
+     * This method might be called often, so it should work fast.
+     *
      * @see       #validate
      * @see       #doLayout
      * @see       LayoutManager
+     * @see       java.awt.Container#isValidateRoot
      * @since     JDK1.0
      */
     public void invalidate() {
@@ -2818,9 +2833,18 @@
             if (!isMaximumSizeSet()) {
                 maxSize = null;
             }
-            if (parent != null) {
-                parent.invalidateIfValid();
-            }
+            invalidateParent();
+        }
+    }
+
+    /**
+     * Invalidates the parent of this component if any.
+     *
+     * This method MUST BE invoked under the TreeLock.
+     */
+    void invalidateParent() {
+        if (parent != null) {
+            parent.invalidateIfValid();
         }
     }
 
--- a/jdk/src/share/classes/java/awt/Container.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/java/awt/Container.java	Wed Oct 21 17:06:41 2009 +0400
@@ -1492,20 +1492,59 @@
     }
 
     /**
-     * Invalidates the container.  The container and all parents
-     * above it are marked as needing to be laid out.  This method can
-     * be called often, so it needs to execute quickly.
+     * Indicates if this container is a <i>validate root</i>.
+     * <p>
+     * Layout-related changes, such as bounds of the validate root descendants,
+     * do not affect the layout of the validate root parent. This peculiarity
+     * enables the {@code invalidate()} method to stop invalidating the
+     * component hierarchy when the method encounters a validate root.
+     * <p>
+     * If a component hierarchy contains validate roots, the {@code validate()}
+     * method must be invoked on the validate root of a previously invalidated
+     * component, rather than on the top-level container (such as a {@code
+     * Frame} object) to restore the validity of the hierarchy later.
+     * <p>
+     * The {@code Window} class and the {@code Applet} class are the validate
+     * roots in AWT.  Swing introduces more validate roots.
      *
-     * <p> If the {@code LayoutManager} installed on this container is
-     * an instance of {@code LayoutManager2}, then
-     * {@link LayoutManager2#invalidateLayout(Container)} is invoked on
-     * it supplying this {@code Container} as the argument.
+     * @return whether this container is a validate root
+     * @see #invalidate
+     * @see java.awt.Component#invalidate
+     * @see javax.swing.JComponent#isValidateRoot
+     * @see javax.swing.JComponent#revalidate
+     * @since 1.7
+     */
+    public boolean isValidateRoot() {
+        return false;
+    }
+
+    /**
+     * Invalidates the parent of the container unless the container
+     * is a validate root.
+     */
+    @Override
+    void invalidateParent() {
+        if (!isValidateRoot()) {
+            super.invalidateParent();
+        }
+    }
+
+    /**
+     * Invalidates the container.
+     * <p>
+     * If the {@code LayoutManager} installed on this container is an instance
+     * of the {@code LayoutManager2} interface, then
+     * the {@link LayoutManager2#invalidateLayout(Container)} method is invoked
+     * on it supplying this {@code Container} as the argument.
+     * <p>
+     * Afterwards this method marks this container invalid, and invalidates its
+     * ancestors. See the {@link Component#invalidate} method for more details.
      *
      * @see #validate
      * @see #layout
-     * @see LayoutManager
-     * @see LayoutManager2#invalidateLayout(Container)
+     * @see LayoutManager2
      */
+    @Override
     public void invalidate() {
         LayoutManager layoutMgr = this.layoutMgr;
         if (layoutMgr instanceof LayoutManager2) {
@@ -1518,35 +1557,39 @@
     /**
      * Validates this container and all of its subcomponents.
      * <p>
-     * The <code>validate</code> method is used to cause a container
-     * to lay out its subcomponents again. It should be invoked when
-     * this container's subcomponents are modified (added to or
-     * removed from the container, or layout-related information
-     * changed) after the container has been displayed.
-     *
-     * <p>If this {@code Container} is not valid, this method invokes
+     * Validating a container means laying out its subcomponents.
+     * Layout-related changes, such as setting the bounds of a component, or
+     * adding a component to the container, invalidate the container
+     * automatically.  Note that the ancestors of the container may be
+     * invalidated also (see {@link Component#invalidate} for details.)
+     * Therefore, to restore the validity of the hierarchy, the {@code
+     * validate()} method should be invoked on a validate root of an
+     * invalidated component, or on the top-most container if the hierarchy
+     * does not contain validate roots.
+     * <p>
+     * Validating the container may be a quite time-consuming operation. For
+     * performance reasons a developer may postpone the validation of the
+     * hierarchy till a set of layout-related operations completes, e.g. after
+     * adding all the children to the container.
+     * <p>
+     * If this {@code Container} is not valid, this method invokes
      * the {@code validateTree} method and marks this {@code Container}
      * as valid. Otherwise, no action is performed.
-     * <p>
-     * Note that the {@code invalidate()} method may invalidate not only the
-     * component it is called upon, but also the parents of the component.
-     * Therefore, to restore the validity of the hierarchy, the {@code
-     * validate()} method must be invoked on the top-most invalid container of
-     * the hierarchy. For performance reasons a developer may postpone the
-     * validation of the hierarchy till a bunch of layout-related operations
-     * completes, e.g. after adding all the children to the container.
      *
      * @see #add(java.awt.Component)
      * @see #invalidate
+     * @see Container#isValidateRoot
      * @see javax.swing.JComponent#revalidate()
      * @see #validateTree
      */
     public void validate() {
         /* Avoid grabbing lock unless really necessary. */
-        if (!isValid()) {
+        if (!isValid() || descendUnconditionallyWhenValidating) {
             boolean updateCur = false;
             synchronized (getTreeLock()) {
-                if (!isValid() && peer != null) {
+                if ((!isValid() || descendUnconditionallyWhenValidating)
+                        && peer != null)
+                {
                     ContainerPeer p = null;
                     if (peer instanceof ContainerPeer) {
                         p = (ContainerPeer) peer;
@@ -1557,7 +1600,11 @@
                     validateTree();
                     if (p != null) {
                         p.endValidate();
-                        updateCur = isVisible();
+                        // Avoid updating cursor if this is an internal call.
+                        // See validateUnconditionally() for details.
+                        if (!descendUnconditionallyWhenValidating) {
+                            updateCur = isVisible();
+                        }
                     }
                 }
             }
@@ -1568,6 +1615,39 @@
     }
 
     /**
+     * Indicates whether valid containers should also traverse their
+     * children and call the validateTree() method on them.
+     *
+     * Synchronization: TreeLock.
+     *
+     * The field is allowed to be static as long as the TreeLock itself is
+     * static.
+     *
+     * @see #validateUnconditionally()
+     */
+    private static boolean descendUnconditionallyWhenValidating = false;
+
+    /**
+     * Unconditionally validate the component hierarchy.
+     */
+    final void validateUnconditionally() {
+        boolean updateCur = false;
+        synchronized (getTreeLock()) {
+            descendUnconditionallyWhenValidating = true;
+
+            validate();
+            if (peer instanceof ContainerPeer) {
+                updateCur = isVisible();
+            }
+
+            descendUnconditionallyWhenValidating = false;
+        }
+        if (updateCur) {
+            updateCursorImmediately();
+        }
+    }
+
+    /**
      * Recursively descends the container tree and recomputes the
      * layout for any subtrees marked as needing it (those marked as
      * invalid).  Synchronization should be provided by the method
@@ -1578,16 +1658,20 @@
      */
     protected void validateTree() {
         checkTreeLock();
-        if (!isValid()) {
+        if (!isValid() || descendUnconditionallyWhenValidating) {
             if (peer instanceof ContainerPeer) {
                 ((ContainerPeer)peer).beginLayout();
             }
-            doLayout();
+            if (!isValid()) {
+                doLayout();
+            }
             for (int i = 0; i < component.size(); i++) {
                 Component comp = component.get(i);
                 if (   (comp instanceof Container)
                        && !(comp instanceof Window)
-                       && !comp.isValid()) {
+                       && (!comp.isValid() ||
+                           descendUnconditionallyWhenValidating))
+                {
                     ((Container)comp).validateTree();
                 } else {
                     comp.validate();
--- a/jdk/src/share/classes/java/awt/Window.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/java/awt/Window.java	Wed Oct 21 17:06:41 2009 +0400
@@ -767,7 +767,7 @@
             isPacked = true;
         }
 
-        validate();
+        validateUnconditionally();
     }
 
     /**
@@ -943,7 +943,7 @@
         if (peer == null) {
             addNotify();
         }
-        validate();
+        validateUnconditionally();
 
         isInShow = true;
         if (visible) {
@@ -2600,6 +2600,21 @@
     }
 
     /**
+     * Indicates if this container is a validate root.
+     * <p>
+     * {@code Window} objects are the validate roots, and, therefore, they
+     * override this method to return {@code true}.
+     *
+     * @return {@code true}
+     * @since 1.7
+     * @see java.awt.Container#isValidateRoot
+     */
+    @Override
+    public boolean isValidateRoot() {
+        return true;
+    }
+
+    /**
      * Dispatches an event to this window or one of its sub components.
      * @param e the event
      */
--- a/jdk/src/share/classes/javax/swing/JComponent.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/JComponent.java	Wed Oct 21 17:06:41 2009 +0400
@@ -4878,7 +4878,9 @@
      * @see #revalidate
      * @see java.awt.Component#invalidate
      * @see java.awt.Container#validate
-     */
+     * @see java.awt.Container#isValidateRoot
+     */
+    @Override
     public boolean isValidateRoot() {
         return false;
     }
--- a/jdk/src/share/classes/javax/swing/JRootPane.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/JRootPane.java	Wed Oct 21 17:06:41 2009 +0400
@@ -725,8 +725,10 @@
      * because both classes override <code>isValidateRoot</code> to return true.
      *
      * @see JComponent#isValidateRoot
+     * @see java.awt.Container#isValidateRoot
      * @return true
      */
+    @Override
     public boolean isValidateRoot() {
         return true;
     }
--- a/jdk/src/share/classes/javax/swing/JScrollPane.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/JScrollPane.java	Wed Oct 21 17:06:41 2009 +0400
@@ -453,10 +453,12 @@
      * @see java.awt.Container#validate
      * @see JComponent#revalidate
      * @see JComponent#isValidateRoot
+     * @see java.awt.Container#isValidateRoot
      *
      * @beaninfo
      *    hidden: true
      */
+    @Override
     public boolean isValidateRoot() {
         return true;
     }
--- a/jdk/src/share/classes/javax/swing/JSplitPane.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/JSplitPane.java	Wed Oct 21 17:06:41 2009 +0400
@@ -947,10 +947,12 @@
      *
      * @return true
      * @see JComponent#revalidate
+     * @see java.awt.Container#isValidateRoot
      *
      * @beaninfo
      *    hidden: true
      */
+    @Override
     public boolean isValidateRoot() {
         return true;
     }
--- a/jdk/src/share/classes/javax/swing/JTextField.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/JTextField.java	Wed Oct 21 17:06:41 2009 +0400
@@ -288,7 +288,9 @@
      *
      * @see JComponent#revalidate
      * @see JComponent#isValidateRoot
+     * @see java.awt.Container#isValidateRoot
      */
+    @Override
     public boolean isValidateRoot() {
         return SwingUtilities2.getViewport(this) == null;
     }
--- a/jdk/src/share/classes/javax/swing/JViewport.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/JViewport.java	Wed Oct 21 17:06:41 2009 +0400
@@ -469,49 +469,12 @@
      * is the synchronous version of a <code>revalidate</code>.
      */
     private void validateView() {
-        Component validateRoot = null;
+        Component validateRoot = SwingUtilities.getValidateRoot(this, false);
 
-        /* Find the first JComponent ancestor of this component whose
-         * isValidateRoot() method returns true.
-         */
-        for(Component c = this; c != null; c = c.getParent()) {
-            if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
-                return;
-            }
-            if ((c instanceof JComponent) &&
-                (((JComponent)c).isValidateRoot())) {
-                validateRoot = c;
-                break;
-            }
-        }
-
-        // If no validateRoot, nothing to validate from.
         if (validateRoot == null) {
             return;
         }
 
-        // Make sure all ancestors are visible.
-        Component root = null;
-
-        for(Component c = validateRoot; c != null; c = c.getParent()) {
-            // We don't check isVisible here, otherwise if the component
-            // is contained in something like a JTabbedPane when the
-            // component is made visible again it won't have scrolled
-            // to the correct location.
-            if (c.getPeer() == null) {
-                return;
-            }
-            if ((c instanceof Window) || (c instanceof Applet)) {
-                root = c;
-                break;
-            }
-        }
-
-        // Make sure there is a Window ancestor.
-        if (root == null) {
-            return;
-        }
-
         // Validate the root.
         validateRoot.validate();
 
--- a/jdk/src/share/classes/javax/swing/RepaintManager.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/RepaintManager.java	Wed Oct 21 17:06:41 2009 +0400
@@ -310,47 +310,13 @@
             delegate.addInvalidComponent(invalidComponent);
             return;
         }
-        Component validateRoot = null;
+        Component validateRoot =
+            SwingUtilities.getValidateRoot(invalidComponent, true);
 
-        /* Find the first JComponent ancestor of this component whose
-         * isValidateRoot() method returns true.
-         */
-        for(Component c = invalidComponent; c != null; c = c.getParent()) {
-            if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
-                return;
-            }
-            if ((c instanceof JComponent) && (((JComponent)c).isValidateRoot())) {
-                validateRoot = c;
-                break;
-            }
-        }
-
-        /* There's no validateRoot to apply validate to, so we're done.
-         */
         if (validateRoot == null) {
             return;
         }
 
-        /* If the validateRoot and all of its ancestors aren't visible
-         * then we don't do anything.  While we're walking up the tree
-         * we find the root Window or Applet.
-         */
-        Component root = null;
-
-        for(Component c = validateRoot; c != null; c = c.getParent()) {
-            if (!c.isVisible() || (c.getPeer() == null)) {
-                return;
-            }
-            if ((c instanceof Window) || (c instanceof Applet)) {
-                root = c;
-                break;
-            }
-        }
-
-        if (root == null) {
-            return;
-        }
-
         /* Lazily create the invalidateComponents vector and add the
          * validateRoot if it's not there already.  If this validateRoot
          * is already in the vector, we're done.
--- a/jdk/src/share/classes/javax/swing/SwingUtilities.java	Mon Oct 19 16:06:41 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/SwingUtilities.java	Wed Oct 21 17:06:41 2009 +0400
@@ -1967,4 +1967,54 @@
             SwingUtilities.updateComponentTreeUI(component);
         }
     }
+
+    /**
+     * Retrieves the validate root of a given container.
+     *
+     * If the container is contained within a {@code CellRendererPane}, this
+     * method returns {@code null} due to the synthetic nature of the {@code
+     * CellRendererPane}.
+     * <p>
+     * The component hierarchy must be displayable up to the toplevel component
+     * (either a {@code Frame} or an {@code Applet} object.) Otherwise this
+     * method returns {@code null}.
+     * <p>
+     * If the {@code visibleOnly} argument is {@code true}, the found validate
+     * root and all its parents up to the toplevel component must also be
+     * visible. Otherwise this method returns {@code null}.
+     *
+     * @return the validate root of the given container or null
+     * @see java.awt.Component#isDisplayable()
+     * @see java.awt.Component#isVisible()
+     * @since 1.7
+     */
+    static Container getValidateRoot(Container c, boolean visibleOnly) {
+        Container root = null;
+
+        for (; c != null; c = c.getParent())
+        {
+            if (!c.isDisplayable() || c instanceof CellRendererPane) {
+                return null;
+            }
+            if (c.isValidateRoot()) {
+                root = c;
+                break;
+            }
+        }
+
+        if (root == null) {
+            return null;
+        }
+
+        for (; c != null; c = c.getParent()) {
+            if (!c.isDisplayable() || (visibleOnly && !c.isVisible())) {
+                return null;
+            }
+            if (c instanceof Window || c instanceof Applet) {
+                return root;
+            }
+        }
+
+        return null;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Container/ValidateRoot/InvalidateMustRespectValidateRoots.java	Wed Oct 21 17:06:41 2009 +0400
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+  @test
+  @bug 6852592
+  @summary invalidate() must stop when it encounters a validate root
+  @author anthony.petrov@sun.com
+  @run main InvalidateMustRespectValidateRoots
+*/
+
+import javax.swing.*;
+import java.awt.event.*;
+
+public class InvalidateMustRespectValidateRoots {
+    private static volatile JRootPane rootPane;
+
+    public static void main(String args[]) throws Exception {
+        SwingUtilities.invokeAndWait(new Runnable() {
+            public void run() {
+                // The JRootPane is a validate root. We'll check if
+                // invalidate() stops on the root pane, or goes further
+                // up to the frame.
+                JFrame frame = new JFrame();
+                final JButton button = new JButton();
+
+                frame.add(button);
+
+                // To enable running the test manually: use the Ctrl-Shift-F1
+                // to print the component hierarchy to the console
+                button.addActionListener(new ActionListener() {
+                    public void actionPerformed(ActionEvent ev) {
+                        if (button.isValid()) {
+                            button.invalidate();
+                        } else {
+                            button.revalidate();
+                        }
+                    }
+                });
+
+                rootPane = frame.getRootPane();
+
+                // Now the component hierarchy looks like:
+                // frame
+                // --> rootPane
+                //     --> layered pane
+                //         --> content pane
+                //             --> button
+
+                // Make all components valid first via showing the frame
+                // We have to make the frame visible. Otherwise revalidate() is
+                // useless (see RepaintManager.addInvalidComponent()).
+                frame.pack(); // To enable running this test manually
+                frame.setVisible(true);
+
+                if (!frame.isValid()) {
+                    throw new RuntimeException(
+                            "setVisible(true) failed to validate the frame");
+                }
+
+                // Now invalidate the button
+                button.invalidate();
+
+                // Check if the 'valid' status is what we expect it to be
+                if (rootPane.isValid()) {
+                    throw new RuntimeException(
+                            "invalidate() failed to invalidate the root pane");
+                }
+
+                if (!frame.isValid()) {
+                    throw new RuntimeException(
+                            "invalidate() invalidated the frame");
+                }
+
+                // Now validate the hierarchy again
+                button.revalidate();
+
+                // Now let the validation happen on the EDT
+            }
+        });
+
+        Thread.sleep(1000);
+
+        SwingUtilities.invokeAndWait(new Runnable() {
+            public void run() {
+                // Check if the root pane finally became valid
+                if (!rootPane.isValid()) {
+                    throw new RuntimeException(
+                            "revalidate() failed to validate the hierarchy");
+                }
+            }
+        });
+    }
+}