From 1c202c4574b58a811376895a60d67ee70588693b Mon Sep 17 00:00:00 2001 From: anthony Date: Wed, 21 Oct 2009 17:06:41 +0400 Subject: [PATCH] 6852592: invalidate() must be smarter Summary: Introduce validate roots in AWT Reviewed-by: alexp, art, dcherepanov --- src/share/classes/java/applet/Applet.java | 15 ++ src/share/classes/java/awt/Component.java | 40 ++++- src/share/classes/java/awt/Container.java | 144 ++++++++++++++---- src/share/classes/java/awt/Window.java | 19 ++- src/share/classes/javax/swing/JComponent.java | 2 + src/share/classes/javax/swing/JRootPane.java | 2 + .../classes/javax/swing/JScrollPane.java | 2 + src/share/classes/javax/swing/JSplitPane.java | 2 + src/share/classes/javax/swing/JTextField.java | 2 + src/share/classes/javax/swing/JViewport.java | 39 +---- .../classes/javax/swing/RepaintManager.java | 38 +---- .../classes/javax/swing/SwingUtilities.java | 50 ++++++ .../InvalidateMustRespectValidateRoots.java | 114 ++++++++++++++ 13 files changed, 355 insertions(+), 114 deletions(-) create mode 100644 test/java/awt/Container/ValidateRoot/InvalidateMustRespectValidateRoots.java diff --git a/src/share/classes/java/applet/Applet.java b/src/share/classes/java/applet/Applet.java index 0e2bcb87b..b12e6fb30 100644 --- a/src/share/classes/java/applet/Applet.java +++ b/src/share/classes/java/applet/Applet.java @@ -229,6 +229,21 @@ public class Applet extends Panel { resize(d.width, d.height); } + /** + * Indicates if this container is a validate root. + *

+ * {@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 diff --git a/src/share/classes/java/awt/Component.java b/src/share/classes/java/awt/Component.java index 42093d16c..6a942e9ec 100644 --- a/src/share/classes/java/awt/Component.java +++ b/src/share/classes/java/awt/Component.java @@ -2764,8 +2764,11 @@ public abstract class Component implements ImageObserver, MenuContainer, } /** - * 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 @@ -2794,12 +2797,24 @@ public abstract class Component implements ImageObserver, MenuContainer, } /** - * 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 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 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 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 @@ public abstract class Component implements ImageObserver, MenuContainer, 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(); } } diff --git a/src/share/classes/java/awt/Container.java b/src/share/classes/java/awt/Container.java index 706cffa89..84334c997 100644 --- a/src/share/classes/java/awt/Container.java +++ b/src/share/classes/java/awt/Container.java @@ -1492,20 +1492,59 @@ public class Container extends Component { } /** - * 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 validate root. + *

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

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

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

+ * 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 @@ public class Container extends Component { /** * 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. + * 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. + *

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

+ * 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 @@ public class Container extends Component { 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(); + } } } } @@ -1567,6 +1614,39 @@ public class Container extends Component { } } + /** + * 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 @@ -1578,16 +1658,20 @@ public class Container extends Component { */ 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(); diff --git a/src/share/classes/java/awt/Window.java b/src/share/classes/java/awt/Window.java index 8ea9ec821..c039a7179 100644 --- a/src/share/classes/java/awt/Window.java +++ b/src/share/classes/java/awt/Window.java @@ -767,7 +767,7 @@ public class Window extends Container implements Accessible { isPacked = true; } - validate(); + validateUnconditionally(); } /** @@ -943,7 +943,7 @@ public class Window extends Container implements Accessible { if (peer == null) { addNotify(); } - validate(); + validateUnconditionally(); isInShow = true; if (visible) { @@ -2599,6 +2599,21 @@ public class Window extends Container implements Accessible { super.addPropertyChangeListener(propertyName, listener); } + /** + * Indicates if this container is a validate root. + *

+ * {@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 diff --git a/src/share/classes/javax/swing/JComponent.java b/src/share/classes/javax/swing/JComponent.java index 6c97ebd5e..37f7e7d47 100644 --- a/src/share/classes/javax/swing/JComponent.java +++ b/src/share/classes/javax/swing/JComponent.java @@ -4878,7 +4878,9 @@ public abstract class JComponent extends Container implements Serializable, * @see #revalidate * @see java.awt.Component#invalidate * @see java.awt.Container#validate + * @see java.awt.Container#isValidateRoot */ + @Override public boolean isValidateRoot() { return false; } diff --git a/src/share/classes/javax/swing/JRootPane.java b/src/share/classes/javax/swing/JRootPane.java index a210c3791..f6964f728 100644 --- a/src/share/classes/javax/swing/JRootPane.java +++ b/src/share/classes/javax/swing/JRootPane.java @@ -725,8 +725,10 @@ public class JRootPane extends JComponent implements Accessible { * because both classes override isValidateRoot to return true. * * @see JComponent#isValidateRoot + * @see java.awt.Container#isValidateRoot * @return true */ + @Override public boolean isValidateRoot() { return true; } diff --git a/src/share/classes/javax/swing/JScrollPane.java b/src/share/classes/javax/swing/JScrollPane.java index b2be43cd1..b813d4fcc 100644 --- a/src/share/classes/javax/swing/JScrollPane.java +++ b/src/share/classes/javax/swing/JScrollPane.java @@ -453,10 +453,12 @@ public class JScrollPane extends JComponent implements ScrollPaneConstants, Acce * @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; } diff --git a/src/share/classes/javax/swing/JSplitPane.java b/src/share/classes/javax/swing/JSplitPane.java index b5d8ec7d2..f3baff664 100644 --- a/src/share/classes/javax/swing/JSplitPane.java +++ b/src/share/classes/javax/swing/JSplitPane.java @@ -947,10 +947,12 @@ public class JSplitPane extends JComponent implements Accessible * * @return true * @see JComponent#revalidate + * @see java.awt.Container#isValidateRoot * * @beaninfo * hidden: true */ + @Override public boolean isValidateRoot() { return true; } diff --git a/src/share/classes/javax/swing/JTextField.java b/src/share/classes/javax/swing/JTextField.java index de408914d..12fc0a70c 100644 --- a/src/share/classes/javax/swing/JTextField.java +++ b/src/share/classes/javax/swing/JTextField.java @@ -288,7 +288,9 @@ public class JTextField extends JTextComponent implements SwingConstants { * * @see JComponent#revalidate * @see JComponent#isValidateRoot + * @see java.awt.Container#isValidateRoot */ + @Override public boolean isValidateRoot() { return SwingUtilities2.getViewport(this) == null; } diff --git a/src/share/classes/javax/swing/JViewport.java b/src/share/classes/javax/swing/JViewport.java index 734e80a05..5073a891a 100644 --- a/src/share/classes/javax/swing/JViewport.java +++ b/src/share/classes/javax/swing/JViewport.java @@ -469,49 +469,12 @@ public class JViewport extends JComponent implements Accessible * 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(); diff --git a/src/share/classes/javax/swing/RepaintManager.java b/src/share/classes/javax/swing/RepaintManager.java index a988b99fb..c8607d2e4 100644 --- a/src/share/classes/javax/swing/RepaintManager.java +++ b/src/share/classes/javax/swing/RepaintManager.java @@ -310,47 +310,13 @@ public class RepaintManager 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. diff --git a/src/share/classes/javax/swing/SwingUtilities.java b/src/share/classes/javax/swing/SwingUtilities.java index d7a26a0dd..54ac2f556 100644 --- a/src/share/classes/javax/swing/SwingUtilities.java +++ b/src/share/classes/javax/swing/SwingUtilities.java @@ -1967,4 +1967,54 @@ public class SwingUtilities implements SwingConstants 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; + } } diff --git a/test/java/awt/Container/ValidateRoot/InvalidateMustRespectValidateRoots.java b/test/java/awt/Container/ValidateRoot/InvalidateMustRespectValidateRoots.java new file mode 100644 index 000000000..7bf34012d --- /dev/null +++ b/test/java/awt/Container/ValidateRoot/InvalidateMustRespectValidateRoots.java @@ -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"); + } + } + }); + } +} -- GitLab