JDK-8202175 : Provide public, unsupported JDK API for JavaFX / Swing interop
  • Type: CSR
  • Component: client-libs
  • Sub-Component: javax.swing
  • Priority: P2
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 11
  • Submitted: 2018-04-24
  • Updated: 2018-06-13
  • Resolved: 2018-06-13
Related Reports
CSR :  
Description
Summary
------------

Provide a new module, jdk.unsupported.desktop, which provides
API that wraps and exports otherwise inaccessible java.desktop internals,
The sole intended client of this is the unbundled OpenJFX, which needs
these APIs to support interoperation between Swing and JavaFX components
to replace previous use of internal APIs when it was part of Oracle JDK.


Problem
-----------

A JavaFX UI can embed Swing UI, and vice versa.
This embedding also includes support for Drag and Drop between the two API stacks.
There is no suitable public API in the java.desktop module to support all  the required
parts of this, and designing such API is non-trivial, or may be undesirable.
Previously, when JavaFX was part of the Oracle JDK, it was able to use
internal JDK APIs from the java.desktop module to support this requirement, with
java.desktop providing qualified exports to the appropriate javafx modules.
Now in JDK 11 that is no longer possible and requiring that JavaFX applications
use command line --add-export options to re-enable this support is not desirable.

Solution
-------------

Provide a new "jdk.unsupported.desktop" module that exports public API that is
intended to be used by the javafx.swing module.
The concept is the same as jdk.unsupported, in that it is documented as being unsupported
and may be removed, either partially or wholly, without notice in a later release,
possibly by being superseded by standard SE API, or just removed.
Further, since it is only intended to be used by javafx.swing, it need not be in the default module graph.
The module-info.java will look like this:

    module jdk.unsupported.desktop { 
        requires transitive java.desktop;

        exports jdk.swing.interop;
    } 

The java.desktop module exports the 6 packages needed for swing interop to jdk.unsupported.desktop (and no other changes to java.desktop).

jdk.unsupported.desktop module will expose 6 public classes in jdk.swing.interop package to be used by javafx.swing.

    src/jdk.unsupported.desktop/share/classes/jdk/swing/interop/LightweightFrameWrapper.java
    src/jdk.unsupported.desktop/share/classes/jdk/swing/interop/LightweightContentWrapper.java
    src/jdk.unsupported.desktop/share/classes/jdk/swing/interop/SwingInterOpUtils.java
    src/jdk.unsupported.desktop/share/classes/jdk/swing/interop/DragSourceContextWrapper.java
    src/jdk.unsupported.desktop/share/classes/jdk/swing/interop/DropTargetContextWrapper.java.
    and
    src/jdk.unsupported.desktop/share/classes/jdk/swing/interop/DispatcherWrapper.java
    

Specification
-------------
    /**
      * This class wraps sun.swing.JLightweightFrame and implements
      * APIs to be used by FX swing interop to access and use JLightweightFrame APIs.
      */
    public class LightweightFrameWrapper  {
        public LightweightFrameWrapper() {}
        public void notifyDisplayChanged(final int scaleFactor) {}
        /**
          * {@code overrideNativeWindowHandle()} is package private but
          * part of the interface. It supports providing a foreign native window
          * handle (i.e. an FX window handle) to AWT, and as such is intended to
          * be called via JNI code, not by Java code, so it is not public
          */
        void overrideNativeWindowHandle(long handle, Runnable closeWindow) {}
        public void setHostBounds(int x, int y, int w, int h) {}
        public void dispose() {}
        public void addWindowFocusListener(WindowFocusListener listener) {}
        public void setVisible(boolean visible) {}
        public void setBounds(int x, int y, int w, int h) {}
        public void setContent(final LightweightContentProxy content) {}
        public void emulateActivation(boolean activate) {}
        public MouseEvent createMouseEvent(LightweightFrameWrapper lwFrame, int swingID, long swingWhen, 
                                                                     int swingModifiers, 
                                                                     int relX, int relY, int absX, int absY, 
                                                                     int clickCount, 
                                                                     boolean swingPopupTrigger, int swingButton) {}
        public MouseWheelEvent createMouseWheelEvent(LightweightFrameWrapper lwFrame, int swingModifiers, 
                                                                                          int x, int y, 
                                                                                          int wheelRotation) {}
        public KeyEvent createKeyEvent(LightweightFrameWrapper lwFrame, int swingID, long swingWhen,
                                                             int swingModifiers, 
                                                             int swingKeyCode, char swingChar) {}
        public AWTEvent createUngrabEvent(LightweightFrameWrapper lwFrame) {}
        public Component findComponentAt(LightweightFrameWrapper cont, int x, int y, boolean ignoreEnabled) {}
        public boolean isCompEqual(Component c, LightweightFrameWrapper lwFrame) {}        
    }

    /**
      * This class provides a wrapper over inner LightweightContentProxy class
      * which implements jdk internal sun.swing.LightweightContent interface 
      * and provides APIs to be used by FX swing interop to access and use LightweightContent APIs.
      */
    public abstract class LightweightContentWrapper {
        public LightweightContentWrapper() {}
        public abstract void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride);
        public abstract void imageBufferReset(int[] data, int x, int y, int width, int height,
                                     int linestride, double scaleX, double scaleY);
        public abstract JComponent getComponent();
        public abstract void paintLock();
        public abstract void paintUnlock();
        public abstract void imageReshaped(int x, int y, int width, int height);
        public abstract void imageUpdated(int dirtyX, int dirtyY,int dirtyWidth, int dirtyHeight);
        public abstract void focusGrabbed();
        public abstract void focusUngrabbed();
        public abstract void preferredSizeChanged(int width, int height);
        public abstract void maximumSizeChanged(int width, int height);
        public abstract void minimumSizeChanged(int width, int height);

        public abstract <T extends DragGestureRecognizer> T createDragGestureRecognizer(
                Class<T> abstractRecognizerClass,  DragSource ds, Component c, int srcActions,
                DragGestureListener dgl);
        public abstract DragSourceContextWrapper createDragSourceContext(DragGestureEvent dge)
                                            throws InvalidDnDOperationException;

        public abstract void addDropTarget(DropTarget dt);
        public abstract removeDropTarget(DropTarget dt);
    }

    /**
      * This class provides static utility methods to be used by FX swing interop 
      * to access and use jdk internal classes like SunToolkit, AppContext, 
      * and UngrabEvent.
      */
    public class SwingInterOpUtils {        
        public static void postEvent(Object target, java.awt.AWTEvent e) {}
        public static void grab(Toolkit toolkit, Window window) {}
        public static void ungrab(Toolkit toolkit, Window window) {}
        public static boolean isUngrabEvent(AWTEvent e) {}    
    }

      /**
        * This class provides a wrapper over inner DragSourceContextPeerProxy class
        * which extends jdk internal sun.awt.dnd.SunDragSourceContextPeer class
        * and provides APIs to be used by FX swing interop to access and use 
        * DragSourceContextPeer APIs.
        */
    public abstract class DragSourceContextWrapper {
        public DragSourceContextWrapper(DragGestureEvent e) {}
        public static int convertModifiersToDropAction(int modifiers,int supportedActions) {}
        protected abstract void setNativeCursor(Cursor c, int cType);
        protected abstract void startDrag(Transferable trans, long[] formats,Map<Long, DataFlavor> formatMap);
        public abstract void startSecondaryEventLoop();
        public abstract void quitSecondaryEventLoop();
        public void dragDropFinished(final boolean success,final int operations,final int x, final int y) {}
        public DragSourceContext getDragSourceContext() {}
    }

    /**
      * This class provides a wrapper over inner class DropTargetContextPeerProxy
      * which implements jdk internal java.awt.dnd.peer.DropTargetContextPeer interface
      * and provides APIs to be used by FX swing interop to access and use
      * DropTargetContextPeer APIs.
      */
    public abstract class DropTargetContextWrapper {
        public DropTargetContextWrapper() {}
        public void setDropTargetContext(DropTargetContext dtc,
                                                                             DropTargetContextWrapper dtcpw) {}
        public void reset(DropTargetContext dtc) {}
        public abstract void setTargetActions(int actions);
        public abstract int getTargetActions();
        public abstract DropTarget getDropTarget();
        public abstract DataFlavor[] getTransferDataFlavors();
        public abstract Transferable getTransferable() throws InvalidDnDOperationException;
        public abstract boolean isTransferableJVMLocal();
        public abstract void acceptDrag(int dragAction);
        public abstract void rejectDrag();
        public abstract void acceptDrop(int dropAction);
        public abstract void rejectDrop();
        public abstract void dropComplete(boolean success);
    }
   
    /**
      * This class provides a wrapper over inner class DispatcherProxy
      * which implements jdk internal sun.awt.FwDispatcher interface
      * and provides APIs to be used by FX swing interop to access and use
      * FwDispatcher APIs.
      */
    public abstract class DispatcherWrapper {
        public DispatcherWrapper() {}
        public abstract boolean isDispatchThread();
        public abstract void scheduleDispatch(Runnable r);
        public abstract SecondaryLoop createSecondaryLoop();
        public static void setFwDispatcher(EventQueue eventQueue, 
                                                               DispatcherWrapper dispatcher) {}
    }

Comments
Moving to Approved on the condition that "but part of the interface." is changed to "but part of the interface of this class." or similar edit.
13-06-2018

It is in a class .. not an interface. Were you confused by my words "part of the interface" ? That did not mean keyword interface .. it means "the public API defined by this class"
13-06-2018

Phil, a method in an interface without an explicit access modifier is public: https://docs.oracle.com/javase/specs/jls/se10/html/jls-9.html#jls-9.4 (As of 9, interfaces can also declare private methods in Java source, but that doesn't seem relevant here.) The above would be relevant, if the method in question were declared in an interface, but it is declared in a class. I read "interface" in the comment to mean it was a method in an interface.
13-06-2018

It is not public (in the sense of having the public access modifier) in this interface. How would you say this ?
13-06-2018

The comment /** * {@code overrideNativeWindowHandle()} is package private but * part of the interface. It supports providing a foreign native window * handle (i.e. an FX window handle) to AWT, and as such is intended to * be called via JNI code, not by Java code, so it is not public */ void overrideNativeWindowHandle(long handle, Runnable closeWindow) {} seems self-contradictory. Is it meant to be read as "While overrideNativeWindowHandle is package private in some-other-JDK-internal-package, it is a public method in this interface because..."
13-06-2018

1. The jdk.swing.interop.internal package is not exported, so is not public API. It is an implementation detail, as is the provider interface it implements, so it should not be part of the specification. 2. The public elements of the module-info.java (everything except the provider) should be part of the specification. 3. You need to remove "Peer" from the following: public abstract DragSourceContextPeerWrapper createDragSourceContextPeer(DragGestureEvent dge) public DragSourceContextPeerWrapper(DragGestureEvent e) {} public DropTargetContextPeerWrapper() {} public static void setDropTargetContextPeer(DropTargetContext dtc, DropTargetContextPeerWrapper dtcpw) {} You might want to regenerate this from the current set of class files to make sure there are no other method changes that aren't reflected here.
09-05-2018

Advancing this request to "Provisional" while the technical discussions continue.
06-05-2018