6852592: invalidate() must be smarter
Summary: Introduce validate roots in AWT
Reviewed-by: alexp, art, dcherepanov
--- 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");
+ }
+ }
+ });
+ }
+}