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

[macosx] SystemFlavorMap.addFlavorForUnencodedNative is ineffective on MacOS

XMLWordPrintable

    • x86
    • os_x

      FULL PRODUCT VERSION :
      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)


      ADDITIONAL OS VERSION INFORMATION :
      MacOS X Version 10.9.5 (Darwin Kernel Version 13.4.0)

      A DESCRIPTION OF THE PROBLEM :
      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.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      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).

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The example code should run without error (printing 208, 207, the first two bytes of Excel's BIFF8 clipboard format).
      ACTUAL -
      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.)

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      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)

      REPRODUCIBILITY :
      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.
            System.out.println(is.read());
            System.out.println(is.read());
          }
        }

        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 ----------

      CUSTOMER SUBMITTED WORKAROUND :
      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.

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: