From d5f9bdbecf7c5a13a286ea41d6710287d8bd5780 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Fri, 4 Jul 2014 15:16:02 +0400 Subject: [PATCH] 8048887: SortingFocusTraversalPolicy throws IllegalArgumentException from the sort method Reviewed-by: azvegint, alexsch --- .../swing/SortingFocusTraversalPolicy.java | 57 +++++++- .../java/awt/Focus/SortingFPT/JDK8048887.java | 132 ++++++++++++++++++ 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 jdk/test/java/awt/Focus/SortingFPT/JDK8048887.java diff --git a/jdk/src/share/classes/javax/swing/SortingFocusTraversalPolicy.java b/jdk/src/share/classes/javax/swing/SortingFocusTraversalPolicy.java index 2c4850b8ff4..c2907a39df7 100644 --- a/jdk/src/share/classes/javax/swing/SortingFocusTraversalPolicy.java +++ b/jdk/src/share/classes/javax/swing/SortingFocusTraversalPolicy.java @@ -30,6 +30,11 @@ import java.awt.Window; import java.util.*; import java.awt.FocusTraversalPolicy; import sun.util.logging.PlatformLogger; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import sun.security.action.GetPropertyAction; +import java.security.AccessController; +import java.security.PrivilegedAction; /** * A FocusTraversalPolicy that determines traversal order by sorting the @@ -89,6 +94,34 @@ public class SortingFocusTraversalPolicy final private int FORWARD_TRAVERSAL = 0; final private int BACKWARD_TRAVERSAL = 1; + /* + * When true (by default), the legacy merge-sort algo is used to sort an FTP cycle. + * When false, the default (tim-sort) algo is used, which may lead to an exception. + * See: JDK-8048887 + */ + private static final boolean legacySortingFTPEnabled; + private static final Method legacyMergeSortMethod; + + static { + legacySortingFTPEnabled = "true".equals(AccessController.doPrivileged( + new GetPropertyAction("swing.legacySortingFTPEnabled", "true"))); + legacyMergeSortMethod = legacySortingFTPEnabled ? + AccessController.doPrivileged(new PrivilegedAction() { + public Method run() { + try { + Class c = Class.forName("java.util.Arrays"); + Method m = c.getDeclaredMethod("legacyMergeSort", new Class[]{Object[].class, Comparator.class}); + m.setAccessible(true); + return m; + } catch (ClassNotFoundException | NoSuchMethodException e) { + // using default sorting algo + return null; + } + } + }) : + null; + } + /** * Constructs a SortingFocusTraversalPolicy without a Comparator. * Subclasses must set the Comparator using setComparator @@ -133,10 +166,32 @@ public class SortingFocusTraversalPolicy private void enumerateAndSortCycle(Container focusCycleRoot, List cycle) { if (focusCycleRoot.isShowing()) { enumerateCycle(focusCycleRoot, cycle); - Collections.sort(cycle, comparator); + if (!legacySortingFTPEnabled || + !legacySort(cycle, comparator)) + { + Collections.sort(cycle, comparator); + } } } + private boolean legacySort(List l, Comparator c) { + if (legacyMergeSortMethod == null) + return false; + + Object[] a = l.toArray(); + try { + legacyMergeSortMethod.invoke(null, a, c); + } catch (IllegalAccessException | InvocationTargetException e) { + return false; + } + ListIterator i = l.listIterator(); + for (Object e : a) { + i.next(); + i.set((Component)e); + } + return true; + } + private void enumerateCycle(Container container, List cycle) { if (!(container.isVisible() && container.isDisplayable())) { return; diff --git a/jdk/test/java/awt/Focus/SortingFPT/JDK8048887.java b/jdk/test/java/awt/Focus/SortingFPT/JDK8048887.java new file mode 100644 index 00000000000..72f0ada7d5b --- /dev/null +++ b/jdk/test/java/awt/Focus/SortingFPT/JDK8048887.java @@ -0,0 +1,132 @@ +/* + * 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 8048887 + @summary Tests SortingFTP for an exception caused by the tim-sort algo. + @author anton.tarasov: area=awt.focus + @run main JDK8040632 +*/ + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import java.awt.Dimension; +import java.awt.Color; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class JDK8048887 { + + static volatile boolean passed = true; + + public static void main(String[] args) { + JDK8048887 app = new JDK8048887(); + app.start(); + } + + public void start() { + final CountDownLatch latch = new CountDownLatch(1); + + SwingUtilities.invokeLater(() -> { + // Catch the original exception which sounds like: + // java.lang.IllegalArgumentException: Comparison method violates its general contract! + Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread t, Throwable e) { + e.printStackTrace(); + if (e instanceof IllegalArgumentException) { + passed = false; + latch.countDown(); + } + } + }); + + TestDialog d = new TestDialog(); + // It's expected that the dialog is focused on start. + // The listener is called after the FTP completes processing and the bug is reproduced or not. + d.addWindowFocusListener(new WindowAdapter() { + public void windowGainedFocus(WindowEvent e) { + latch.countDown(); + } + }); + d.setVisible(true); + }); + + try { + latch.await(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (passed) + System.out.println("Test passed."); + else + throw new RuntimeException("Test failed!"); + } +} + +class TestDialog extends JFrame { + + // The layout of the components reproduces the transitivity issue + // with SortingFocusTraversalPolicy relying on the tim-sort algo. + + private static int[] Xs = new int[] {71, 23, 62, 4, 79, 39, 34, 9, 84, 58, 30, 34, 38, 15, 69, 10, 44, 95, 70, 54, + 44, 62, 77, 64, 70, 83, 31, 48, 96, 54, 40, 3, 60, 58, 3, 20, 94, 54, 26, 19, 48, 47, 12, 70, 86, 43, 71, 97, 19, + 69, 90, 22, 43, 76, 10, 60, 29, 49, 9, 9, 15, 73, 85, 80, 81, 35, 87, 43, 17, 57, 38, 44, 29, 86, 96, 15, 57, 26, + 27, 78, 26, 87, 43, 6, 4, 16, 57, 99, 32, 86, 96, 5, 50, 69, 12, 4, 36, 84, 71, 60, 22, 46, 11, 44, 87, 3, 23, 14, + 43, 25, 32, 44, 11, 18, 77, 2, 51, 87, 88, 53, 69, 37, 14, 10, 25, 73, 39, 33, 91, 51, 96, 9, 74, 66, 70, 42, 72, + 7, 82, 40, 91, 33, 83, 54, 33, 50, 83, 1, 81, 32, 66, 11, 75, 56, 53, 45, 1, 69, 46, 31, 79, 58, 12, 20, 92, 49, + 50, 90, 33, 8, 43, 93, 72, 78, 9, 56, 84, 60, 30, 39, 33, 88, 84, 56, 49, 47, 4, 90, 57, 6, 23, 96, 37, 88, 22, 79, + 35, 80, 45, 55}; + + public TestDialog() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + for (int i=0; i < Xs.length; i++) { + gbc.gridx = Xs[i]; + gbc.gridy = 100 - gbc.gridx; + panel.add(new MyComponent(), gbc); + } + getRootPane().getContentPane().add(panel); + pack(); + } + + public static class MyComponent extends JPanel { + private final static Dimension SIZE = new Dimension(1,1); + + public MyComponent() { + setBackground(Color.BLACK); + setOpaque(true); + } + + @Override + public Dimension getPreferredSize() { + return SIZE; + } + } +}