--- old/src/share/classes/java/applet/Applet.java 2009-09-25 17:41:04.000000000 +0400 +++ new/src/share/classes/java/applet/Applet.java 2009-09-25 17:41:03.000000000 +0400 @@ -230,6 +230,21 @@ } /** + * Indicates if this container is a validate root. + *

+ * {@code Applet} objects are the validate roots, and therefore 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 --- old/src/share/classes/java/awt/Component.java 2009-09-25 17:41:05.000000000 +0400 +++ new/src/share/classes/java/awt/Component.java 2009-09-25 17:41:05.000000000 +0400 @@ -2761,8 +2761,11 @@ } /** - * Ensures that this component has a valid layout. This method is - * primarily intended to operate on instances of Container. + * Validates this component. + *

+ * The meaning of the term validating is defined by the ancestors of + * this class. See {@link Container#validate} for more details. + * * @see #invalidate * @see #doLayout() * @see LayoutManager @@ -2791,12 +2794,23 @@ } /** - * 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. + *

+ * All ancestors of this component up to the nearest validate root (or up + * to the root of the hierarchy if there's no a validate root container + * present) are marked invalid also. Marking a container invalid + * indicates that the container needs to be laid out. + *

+ * 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). + *

+ * This method can 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() { @@ -2815,9 +2829,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(); } } --- old/src/share/classes/java/awt/Container.java 2009-09-25 17:41:07.000000000 +0400 +++ new/src/share/classes/java/awt/Container.java 2009-09-25 17:41:06.000000000 +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. - * - *

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. + * Indicates if this container is a validate root. + *

+ * Changes of layout-related information, such as bounds, of descendants of + * a validate root do not affect the layout of the parent of the validate + * root. This enables the {@code invalidate()} method to stop invalidating + * the component hierarchy when the method encounters a validate root. + *

+ * 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. + *

+ * The {@code Window} class and the {@code Applet} class are the validate + * roots in AWT. Swing introduces more validate roots. + * + * @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. + *

+ * 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. + *

+ * 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. *

- * The validate 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. - * - *

If this {@code Container} is not valid, this method invokes + * Validating a container means laying out its subcomponents. Changes in + * layout-related information, 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. + *

+ * Validating the container may be a quite time-consuming operation. 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. + *

+ * 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. - *

- * 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 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(); --- old/src/share/classes/java/awt/Window.java 2009-09-25 17:41:08.000000000 +0400 +++ new/src/share/classes/java/awt/Window.java 2009-09-25 17:41:08.000000000 +0400 @@ -768,7 +768,7 @@ isPacked = true; } - validate(); + validateUnconditionally(); } /** @@ -944,7 +944,7 @@ if (peer == null) { addNotify(); } - validate(); + validateUnconditionally(); isInShow = true; if (visible) { @@ -2601,6 +2601,21 @@ } /** + * Indicates if this container is a validate root. + *

+ * {@code Window} objects are the validate roots, and therefore 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 */ --- old/src/share/classes/javax/swing/JComponent.java 2009-09-25 17:41:09.000000000 +0400 +++ new/src/share/classes/javax/swing/JComponent.java 2009-09-25 17:41:09.000000000 +0400 @@ -4868,7 +4868,9 @@ * @see #revalidate * @see java.awt.Component#invalidate * @see java.awt.Container#validate + * @see java.awt.Container#isValidateRoot */ + @Override public boolean isValidateRoot() { return false; } --- old/src/share/classes/javax/swing/JRootPane.java 2009-09-25 17:41:11.000000000 +0400 +++ new/src/share/classes/javax/swing/JRootPane.java 2009-09-25 17:41:10.000000000 +0400 @@ -725,8 +725,10 @@ * because both classes override isValidateRoot to return true. * * @see JComponent#isValidateRoot + * @see java.awt.Container#isValidateRoot * @return true */ + @Override public boolean isValidateRoot() { return true; } --- old/src/share/classes/javax/swing/JScrollPane.java 2009-09-25 17:41:12.000000000 +0400 +++ new/src/share/classes/javax/swing/JScrollPane.java 2009-09-25 17:41:12.000000000 +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; } --- old/src/share/classes/javax/swing/JSplitPane.java 2009-09-25 17:41:13.000000000 +0400 +++ new/src/share/classes/javax/swing/JSplitPane.java 2009-09-25 17:41:13.000000000 +0400 @@ -947,10 +947,12 @@ * * @return true * @see JComponent#revalidate + * @see java.awt.Container#isValidateRoot * * @beaninfo * hidden: true */ + @Override public boolean isValidateRoot() { return true; } --- old/src/share/classes/javax/swing/JTextField.java 2009-09-25 17:41:15.000000000 +0400 +++ new/src/share/classes/javax/swing/JTextField.java 2009-09-25 17:41:14.000000000 +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; } --- old/src/share/classes/javax/swing/JViewport.java 2009-09-25 17:41:16.000000000 +0400 +++ new/src/share/classes/javax/swing/JViewport.java 2009-09-25 17:41:15.000000000 +0400 @@ -469,49 +469,12 @@ * is the synchronous version of a revalidate. */ 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(); --- old/src/share/classes/javax/swing/RepaintManager.java 2009-09-25 17:41:17.000000000 +0400 +++ new/src/share/classes/javax/swing/RepaintManager.java 2009-09-25 17:41:17.000000000 +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. --- old/src/share/classes/javax/swing/SwingUtilities.java 2009-09-25 17:41:18.000000000 +0400 +++ new/src/share/classes/javax/swing/SwingUtilities.java 2009-09-25 17:41:18.000000000 +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}. + *

+ * 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}. + *

+ * 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 2009-08-28 00:18:48.855006940 +0400 +++ new/test/java/awt/Container/ValidateRoot/InvalidateMustRespectValidateRoots.java 2009-09-25 17:41:19.000000000 +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"); + } + } + }); + } +}