JDK-8136781 : [macosx] SystemFlavorMap.addFlavorForUnencodedNative is ineffective on MacOS
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 8u60,9
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: os_x
  • CPU: x86
  • Submitted: 2015-09-18
  • Updated: 2018-09-05
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Related Reports
Relates :  
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

MacOS X Version 10.9.5 (Darwin Kernel Version 13.4.0)

My Java application allows users to paste selections from Microsoft Excel. The data is retrieved using Excel's binary BIFF8 format rather than in plain text format in order to reliably detect date formatting, preserve numerical precision, and such. On Windows, getting to the relevant clipboard InputStream can be achieved by calling SystemFlavorMap.addUnencodedNativeForFlavor to map a new DataFlavor to a native data type identifier. On MacOS, however, there seems to be a bug that renders SystemFlavorMap.addUnencodedNativeForFlavor ineffective.

Looking at the JDK source code, the bug seems to be that sun.lwawt.macosx.CDataTransferer.registerFormatWithPasteboard is never called when SystemFlavorMap.addUnencodedNativeForFlavor/ addFlavorForUnencodedNative is used to register a new native clipboard data format. This leads Java_sun_lwawt_macosx_CClipboard_getClipboardFormats in macosx/native/sun/awt/CClipboard.m to never return formats of the new type, because indexForFormat (in CDataTransferer.m) will always return -1.

Open up Excel for Mac 2011. Enter some text into the spreadsheet cells, select the cells, and hit Command+C to copy the cell to the clipboard. Now run the attached sample code (being careful not to ruin the clipboard contents by copying something else in the meantime).

The example code should run without error (printing 208, 207, the first two bytes of Excel's BIFF8 clipboard format).
On MacOS, the following exception is thrown:

Exception in thread "main" java.awt.datatransfer.UnsupportedFlavorException: Excel BIFF8

(This exception is also thrown if nothing is copied from Excel when the example code is run, so take care to actually copy some data from Excel before running the example.)

Exception in thread "main" java.awt.datatransfer.UnsupportedFlavorException: Excel BIFF8
	at sun.awt.datatransfer.ClipboardTransferable.getTransferData(ClipboardTransferable.java:159)
	at FlavorMapBugExhibit.main(FlavorMapBugExhibit.java:24)

This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.SystemFlavorMap;
import java.awt.datatransfer.Transferable;
import java.io.InputStream;

public final class FlavorMapBugExhibit {
  public static final boolean ENABLE_WORKAROUND = false;

  private FlavorMapBugExhibit() { }

  /* To test, open Microsoft Excel for Mac 2011, select a few cells, and invoke "Copy" to copy the
  cells to the clipboard. Then run this class. When ENABLE_WORKAROUND is false, an
  UnsupportedFlavorException will be thrown. The bug does not exist on Windows (tested with
  Excel 2013). */
  public static void main(String args[]) throws Exception {
    DataFlavor dataFlavor = registerExcelDataFlavor();
    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    Transferable transferable = clipboard.getContents(null);

    try (InputStream is = (InputStream) transferable.getTransferData(dataFlavor)) {
      // Should print 208, 207.

  private static DataFlavor registerExcelDataFlavor() throws Exception {
    // See http://lists.apple.com/archives/java-dev/2010/Oct/msg00026.html .
    final boolean isMacOS = System.getProperty("os.name").contains("Mac OS X");
    final String nat = isMacOS
        // Tested with Excel for Mac 2011.
        ? "CorePasteboardFlavorType 0x454D4253"
        // Tested with Excel for Windows 2013.
        : "Biff8";
    DataFlavor result = new DataFlavor("application/vnd.ms-excel", "Excel BIFF8");
    SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap();
    map.addUnencodedNativeForFlavor(result, nat);
    map.addFlavorForUnencodedNative(nat, result);
    if (isMacOS && ENABLE_WORKAROUND) {
      // Workaround for the exhibited bug.
      /* Force calling of DataTransferer.getFormatForNativeAsLong, which is necessary for getting
      indexForFormat to return a valid index when CClipboard.getClipboardFormats in
      macosx/native/sun/awt/CClipboard.m calls it. */
      sun.awt.datatransfer.DataTransferer.getInstance().getFormatsForFlavor(result, map);
    return result;

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

Studying the AWT source code lead me to a workaround for the bug, which is to call sun.awt.datatransfer.DataTransferer.getInstance().getFormatsForFlavor(myNewFlavor, (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap()) once after first mapping the flavor using addUnencodedNativeForFlavor and addFlavorForUnencodedNative. This works because the call to getFormatsForFlavor forces DataTransferer.getFormatForNativeAsLong to be called with the new native data type identifier, which in turn causes registerFormatWithPasteboard to be called in CDataTransferer.m. I'm worried that the workaround will cease to work in the future, however, since it relies on an obscure side-effect of a method in a private API from the sun.awt package.

The workaround can be tested in by setting ENABLE_WORKAROUND = true in the provided example code.

This bug is "In Progress" status now, so re-targeting back to Fix Version 9. If you are not working on this bug fix anymore, please update the bug from "In Progress" to just "Open"

Read data from clipboard test. Native code that copies data to the clipboard on Windows: -------------------- void copyToClipboard(){ UINT clipboardFormat = RegisterClipboardFormat(L"JAVA_TEST_NAT"); int value = 93; int nValueLen = sizeof(value); HANDLE hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, nValueLen + 1); int *ptrData = (int*)GlobalLock(hData); memcpy(ptrData, &value, nValueLen + 1); GlobalUnlock(hData); if (OpenClipboard(NULL)) { EmptyClipboard(); hData = SetClipboardData(clipboardFormat, hData); CloseClipboard(); } } -------------------- Native code that copies data to the clipboard on Mac OS X: -------------------- - (void)copyToClipboard { NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; int i = 93; NSData *data = [NSData dataWithBytes: &i length: sizeof(i)]; [pasteboard clearContents]; [pasteboard setData:data forType:@"JAVA_TEST_NAT"]; } -------------------- Java code to read data from the clipboard: -------------------- import java.awt.*; import java.awt.datatransfer.*; import java.io.*; public class CopyFromClipboardTest { private static final String TEST_MIME_TYPE = "application/x-java-jvm-local-objectref;class=" + ClipboardInputStream.class.getName(); private static final String TEST_NAT = "JAVA_TEST_NAT"; private static final String COPIED_VALUE = "93"; public static void main(String[] args) throws Exception { final DataFlavor dataFlavor = new DataFlavor(TEST_MIME_TYPE); SystemFlavorMap systemFlavorMap = (SystemFlavorMap) SystemFlavorMap. getDefaultFlavorMap(); systemFlavorMap.addFlavorForUnencodedNative(TEST_NAT, dataFlavor); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Object clipboardData = clipboard.getData(dataFlavor); System.out.println("clipboard value: " + clipboardData); if (!COPIED_VALUE.equals(clipboardData.toString())) { throw new RuntimeException("Wrong copied value: " + clipboardData); } } public static class ClipboardInputStream extends InputStream { private byte value = -1; public ClipboardInputStream(InputStream inputStream) { try { byte[] bytes = inputStream.readAllBytes(); value = bytes[0]; } catch (Exception e) { throw new RuntimeException(e); } } @Override public int read() throws IOException { return -1; } @Override public String toString() { return String.format("%d", value); } } } --------------------

"jlong indexForFormat(NSString *format)" method from CDataTransferer.m class registers unknown formats that have only "JAVA_DATAFLAVOR:" prefix.