8048110: Using tables in JTextPane leads to infinite loop in FlowLayout.layoutRow
Reviewed-by: alexp, alexsch
diff --git a/src/share/classes/javax/swing/text/FlowView.java b/src/share/classes/javax/swing/text/FlowView.java
index 53e63e2..9c5cf03 100644
--- a/src/share/classes/javax/swing/text/FlowView.java
+++ b/src/share/classes/javax/swing/text/FlowView.java
@@ -800,14 +800,22 @@
@Override
protected void forwardUpdate(DocumentEvent.ElementChange ec,
DocumentEvent e, Shape a, ViewFactory f) {
- calculateUpdateIndexes(e);
- // Send update event to all views followed by the changed place.
- lastUpdateIndex = Math.max((getViewCount() - 1), 0);
- for (int i = firstUpdateIndex; i <= lastUpdateIndex; i++) {
- View v = getView(i);
- if (v != null) {
- Shape childAlloc = getChildAllocation(i, a);
- forwardUpdateToView(v, e, childAlloc, f);
+ // Update the view responsible for the changed element by invocation of
+ // super method.
+ super.forwardUpdate(ec, e, a, f);
+ // Re-calculate the update indexes and update the views followed by
+ // the changed place. Note: we update the views only when insertion or
+ // removal takes place.
+ DocumentEvent.EventType type = e.getType();
+ if (type == DocumentEvent.EventType.INSERT ||
+ type == DocumentEvent.EventType.REMOVE) {
+ firstUpdateIndex = Math.min((lastUpdateIndex + 1), (getViewCount() - 1));
+ lastUpdateIndex = Math.max((getViewCount() - 1), 0);
+ for (int i = firstUpdateIndex; i <= lastUpdateIndex; i++) {
+ View v = getView(i);
+ if (v != null) {
+ v.updateAfterChange();
+ }
}
}
}
diff --git a/src/share/classes/javax/swing/text/GlyphView.java b/src/share/classes/javax/swing/text/GlyphView.java
index 6aef368..fd75fa0 100644
--- a/src/share/classes/javax/swing/text/GlyphView.java
+++ b/src/share/classes/javax/swing/text/GlyphView.java
@@ -971,6 +971,14 @@
}
}
+ /** {@inheritDoc} */
+ @Override
+ void updateAfterChange() {
+ // Drop the break spots. They will be re-calculated during
+ // layout. It is necessary for proper line break calculation.
+ breakSpots = null;
+ }
+
/**
* Class to hold data needed to justify this GlyphView in a PargraphView.Row
*/
diff --git a/src/share/classes/javax/swing/text/View.java b/src/share/classes/javax/swing/text/View.java
index 2b49055..d1e4e13 100644
--- a/src/share/classes/javax/swing/text/View.java
+++ b/src/share/classes/javax/swing/text/View.java
@@ -1199,6 +1199,13 @@
}
/**
+ * Updates the view to reflect the changes.
+ */
+ void updateAfterChange() {
+ // Do nothing by default. Should be overridden in subclasses, if any.
+ }
+
+ /**
* Forwards the <code>DocumentEvent</code> to the give child view. This
* simply messages the view with a call to <code>insertUpdate</code>,
* <code>removeUpdate</code>, or <code>changedUpdate</code> depending
diff --git a/test/javax/swing/text/View/8048110/bug8048110.java b/test/javax/swing/text/View/8048110/bug8048110.java
new file mode 100644
index 0000000..2151e3f
--- /dev/null
+++ b/test/javax/swing/text/View/8048110/bug8048110.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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.
+ */
+
+/* @test
+ * @bug 8048110
+ * @summary Using tables in JTextPane leads to infinite loop in FlowLayout.layoutRow
+ * @author Dmitry Markov
+ * @run main bug8048110
+ */
+
+import sun.awt.SunToolkit;
+
+import javax.swing.*;
+import javax.swing.text.Element;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.HTMLEditorKit;
+import java.awt.*;
+
+public class bug8048110 {
+ private static SunToolkit toolkit = (SunToolkit)Toolkit.getDefaultToolkit();
+ private static Object lock = new Object();
+ private static boolean isRealSyncPerformed = false;
+ private static final String htmlText = "<table width=\"100%\" cellpadding=\"10\" cellspacing=\"5\" align=\"center\">" +
+ "<tr><th align=\"left\" bgcolor=\"#bec3c6\">Devices</th><th align=\"left\" bgcolor=\"#bec3c6\">State</th></tr>" +
+ "<tr><td align=\"left\" bgcolor=\"#bec3c6\">PC</td><td align=\"left\" bgcolor=\"#46a055\">Ok</td></tr></table>";
+
+ public static void main(String[] args) throws Exception {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ createAndShowGUI();
+ }
+ });
+
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ toolkit.realSync();
+ synchronized (lock) {
+ isRealSyncPerformed = true;
+ lock.notifyAll();
+ }
+ }
+ };
+ thread.start();
+
+ synchronized (lock) {
+ if (!isRealSyncPerformed) {
+ lock.wait(5000);
+ }
+ }
+
+ if (!isRealSyncPerformed) {
+ throw new RuntimeException("Test Failed!");
+ }
+ }
+
+ private static void createAndShowGUI() {
+ try {
+ UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ HTMLEditorKit editorKit = new HTMLEditorKit();
+ JTextPane textPane = new JTextPane();
+ textPane.setContentType("text/html");
+ textPane.setEditorKit(editorKit);
+ textPane.setText("Initial text without table");
+
+ JFrame frame = new JFrame("bug8048110");
+ frame.getContentPane().add(textPane, BorderLayout.CENTER);
+ frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+ frame.setSize(500, 200);
+ frame.setVisible(true);
+
+ textPane.setDocument(textPane.getEditorKit().createDefaultDocument());
+ HTMLDocument htmlDocument = (HTMLDocument) textPane.getDocument();
+ Element firstParagraph = findFirstElement(textPane.getDocument().getDefaultRootElement(), "p");
+
+ try {
+ htmlDocument.setInnerHTML(firstParagraph, htmlText);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static Element findFirstElement(Element e, String name) {
+ String elementName = e.getName();
+ if (elementName != null && elementName.equalsIgnoreCase(name)) {
+ return e;
+ }
+ for (int i = 0; i < e.getElementCount(); i++) {
+ Element result = findFirstElement(e.getElement(i), name);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+}
+