8048887: SortingFocusTraversalPolicy throws IllegalArgumentException from the sort method
authorant
Fri, 04 Jul 2014 15:16:02 +0400
changeset 25570 a72022403893
parent 25569 2bea87828632
child 25571 c65329415365
8048887: SortingFocusTraversalPolicy throws IllegalArgumentException from the sort method Reviewed-by: azvegint, alexsch
jdk/src/share/classes/javax/swing/SortingFocusTraversalPolicy.java
jdk/test/java/awt/Focus/SortingFPT/JDK8048887.java
--- a/jdk/src/share/classes/javax/swing/SortingFocusTraversalPolicy.java	Fri Jul 04 13:40:55 2014 +0400
+++ b/jdk/src/share/classes/javax/swing/SortingFocusTraversalPolicy.java	Fri Jul 04 15:16:02 2014 +0400
@@ -30,6 +30,11 @@
 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 @@
     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<Method>() {
+                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 <code>setComparator</code>
@@ -133,10 +166,32 @@
     private void enumerateAndSortCycle(Container focusCycleRoot, List<Component> cycle) {
         if (focusCycleRoot.isShowing()) {
             enumerateCycle(focusCycleRoot, cycle);
-            Collections.sort(cycle, comparator);
+            if (!legacySortingFTPEnabled ||
+                !legacySort(cycle, comparator))
+            {
+                Collections.sort(cycle, comparator);
+            }
         }
     }
 
+    private boolean legacySort(List<Component> l, Comparator<? super Component> c) {
+        if (legacyMergeSortMethod == null)
+            return false;
+
+        Object[] a = l.toArray();
+        try {
+            legacyMergeSortMethod.invoke(null, a, c);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            return false;
+        }
+        ListIterator<Component> i = l.listIterator();
+        for (Object e : a) {
+            i.next();
+            i.set((Component)e);
+        }
+        return true;
+    }
+
     private void enumerateCycle(Container container, List<Component> cycle) {
         if (!(container.isVisible() && container.isDisplayable())) {
             return;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Focus/SortingFPT/JDK8048887.java	Fri Jul 04 15:16:02 2014 +0400
@@ -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;
+        }
+    }
+}