Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8133039

Provide public API to sun.swing.UIAction#isEnabled(Object)

XMLWordPrintable

    • b105
    • x86_64
    • windows_7

      A DESCRIPTION OF THE REQUEST :
      This is in response to the request Public API for internal Swing classes
      http://mail.openjdk.java.net/pipermail/swing-dev/2015-July/004696.html

      To check whether a sun.swing.UIAction is really enabled, you need to cast to this internal JDK API class and call UIAction#isEnabled(Object) where the object is the JComponent the action will be executed.

      UIAction.isEnabled() does not always return the correct value.

      This is because a single instance of UIAction is shared between all components with the same UI class, I assume originally for performance reasons.
      If you just want to perform the action when really enabled, you can use SwingUtilities#notifyAction(Action, KeyStroke, KeyEvent, Object, int) where the Object is the 'sender' (the action event source component) which is passed to UIAction#isEnabled(Object).
      It is technically possible to work around it with current public API, but that means not using any of the Swing UI classes at all and rewrite them without using UIAction.

      Solutions:
      1) Fix UIAction by removing #isEnabled(Object) and have an instance per component Lots of work and large impact.
      Swing API users can remove references to UIAction and just call Action#isEnabled()

      2) Make UIAction public API (move to javax.swing.plaf?).
      Simple refactor but lots of files changed Swing API users only need to update their imports

      3) Do nothing and force Swing API to do point 1 themselves by re-implementing the components and UI classes using only public API.
      Even more work than first solution but only for Swing API users Or they can access through reflection if that is not blocked by the module system? It will probably depend on the SecurityManager I guess.

      4) Provide boolean SwingUtilities.canNotifyAction(Action, Object) which returns true if #notifyAction(Action, KeyStroke, KeyEvent, Object, int) would call action performed.
      Minimal work and minimal impact Swing API users need to change code from ((sun.swing.UIAction) action).isEnabled(component) to SwingUtilities.canNotifyAction(component, action)

      5) Something else?

      My preference would be solution 4 due to minimal impact - solution 2 also has not much impact but then you have public API which does not implement the Action interface correctly. Solution 1 would be the correct one but a lot of work.


      JUSTIFICATION :
      Fixing the UIAction.isEnabled() to return the correct value might require a large amount of work, so adding the new method to be added to SwingUtilities is a reasonable compromise.
      In general a Swing developers will only need this method when developing custom Swing components which re-use standard components and need to check whether an UIAction of those standard components is really enabled or not.

      A more detailed use case can be found at http://mail.openjdk.java.net/pipermail/swing-dev/2015-August/004729.html

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      UIAction#isEnabled() return correctly whether the action is enabled (actionPerformed will called by SwingUtilities#notifyAction).

      Expected output of test case:
      JTable#isEditing() = false
      Action#isEnabled() = false
      UIAction#isEnabled(Object) = false
      JTable#isEditing() = true
      Action#isEnabled() = true
      UIAction#isEnabled(Object) = true

      ACTUAL -
      UIAction#isEnabled() can return the wrong value and #isEnabled(Object) need to be called to be sure.

      Actual output:
      JTable#isEditing() = false
      Action#isEnabled() = true
      UIAction#isEnabled(Object) = false
      JTable#isEditing() = true
      Action#isEnabled() = true
      UIAction#isEnabled(Object) = true


      ---------- BEGIN SOURCE ----------
      import java.awt.EventQueue;

      import javax.swing.Action;
      import javax.swing.JTable;
      import javax.swing.table.DefaultTableModel;

      public class TestUIAction {

          public static void main(String[] args) {
              EventQueue.invokeLater(new Runnable() {

                  @Override
                  public void run() {
                      JTable table = new JTable(new DefaultTableModel(1, 1));

                      Action action = table.getActionMap().get("cancel");
                      System.out.println("JTable#isEditing() = " + table.isEditing());
                      System.out.println("Action#isEnabled() = " + action.isEnabled()); System.out.println("UIAction#isEnabled(Object) = " + ((sun.swing.UIAction) action).isEnabled(table));
                      
                      table.editCellAt(0, 0);
                      
                      System.out.println("JTable#isEditing() = " + table.isEditing());
                      System.out.println("Action#isEnabled() = " + action.isEnabled()); System.out.println("UIAction#isEnabled(Object) = " + ((sun.swing.UIAction) action).isEnabled(table));
                  }
              });
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Cast to sun.swing.UIAction and call isEnabled(Object) but this workaround will no longer work with the Java 9 module system

            alexsch Alexandr Scherbatiy
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: