United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6392086 Copy to clipboard with html mime type fails with input stream
JDK-6392086 : Copy to clipboard with html mime type fails with input stream

Details
Type:
Bug
Submit Date:
2006-02-28
Status:
Closed
Updated Date:
2011-03-07
Project Name:
JDK
Resolved Date:
2011-03-07
Component:
client-libs
OS:
windows_xp
Sub-Component:
java.awt
CPU:
x86
Priority:
P4
Resolution:
Fixed
Affected Versions:
6
Fixed Versions:

Related Reports

Sub Tasks

Description
FULL PRODUCT VERSION :
java version "1.6.0-beta2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.6.0-beta2-b72)
Java HotSpot(TM) Client VM (build 1.6.0-beta2-b72, mixed mode, sharing)

also

java version "1.5.0_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
Probably all other windows versions, too.

A DESCRIPTION OF THE PROBLEM :
When using an input stream (the default class) as transfer data copying data of "text/html" mime types is not possible at all.

Java erroneously duplicates text in the native clipboard format:

Version:0.9
StartHTML:-1
EndHTML:-1
StartFragment:0000000111
EndFragment:0000000332
<!--StartFragment-->
Version:0.9
StartHTML:-1
EndHTML:-1
StartFragment:0000000111
EndFragment:0000000201
<!--StartFragment-->
The quick <font color='#78650d'>brown</font> <b>mouse</b> jumped over the lazy <b>cat</b>.<!--EndFragment-->
<!--EndFragment-->

This issue is described in more detail on
http://www.peterbuettner.de/develop/javasnippets/clipHtml/index.html

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the code below to fill the clipboard.
Try to paste in OpenOffice 2.0 Writer. Notice the error message "Requested clipboard format is not available".
Try to paste in MS Word 2003. The following text is inserted:
"Version:0.9 StartHTML:-1 EndHTML:-1 StartFragment:0000000111 EndFragment:0000000201 The quick brown mouse jumped over the lazy cat."

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
In both OpenOffice Writer and MS Word (and any other word processor capable of dealing with html type) the formatted text should be inserted.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;

public class HtmlStreamCopy {
    public static void main(String[] args) throws ClassNotFoundException {
        String htmlText = "The quick <font color='#78650d'>brown</font> <b>mouse</b> jumped over the lazy <b>cat</b>.";
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable htmlTransferable = new HtmlStreamCopy.HtmlInputStreamTransferable(htmlText);
        clipboard.setContents(htmlTransferable, null);
    }

    private static class HtmlInputStreamTransferable implements Transferable {
        private final DataFlavor _htmlDataFlavor;
        private final String _htmlText;

        public HtmlInputStreamTransferable(String htmlText) throws ClassNotFoundException {
            _htmlText = htmlText;
            _htmlDataFlavor = new DataFlavor("text/html");
        }

        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[]{_htmlDataFlavor};
        }

        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return "text/html".equals(flavor.getMimeType());

        }

        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException
        {
            InputStream stringStream = new ByteArrayInputStream(_htmlText.getBytes("utf-8"));
            return stringStream;
        }
    }
}

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

CUSTOMER SUBMITTED WORKAROUND :
Use a string instead of an input stream as transfer object.

                                    

Comments
EVALUATION

the problem are in wrong ???virtual??? recursion.
correction in WDataTransfer.java provides dirty solution for the problem
--------------------------------------------
public byte[] translateTransferable(Transferable contents,
                                        DataFlavor flavor,
                                        long format) throws IOException
    {
        if (format == CF_HTML) {
            return HTMLSupport.convertToHTMLFormat(
                    super.translateTransferable(contents, flavor, CF_TEXT)
            );
        } 
        return super.translateTransferable(contents, flavor, format);
    }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To extend HTML clipboard format compatibility I would like to implement following fix
in WDataTransfer.java (additional changes in HTMLDecodingInputStream class are required!):
--------------------------------------------
final class HTMLSupport {
    public static final String ENCODING = "UTF-8";

    public static final String VERSION = "Version:";
    public static final String START_HTML = "StartHTML:";
    public static final String END_HTML = "EndHTML:";
    public static final String START_FRAGMENT = "StartFragment:";
    public static final String END_FRAGMENT = "EndFragment:";
    public static final String HTML_PREFIX = "<HTML><BODY>";
    public static final String HTML_SUFFIX = "</BODY></HTML>";
    public static final String START_FRAGMENT_CMT = "<!--StartFragment-->";
    public static final String END_FRAGMENT_CMT = "<!--EndFragment-->";
    public static final String SOURCE_URL = "SourceURL:";
    public static final String DEF_SOURCE_URL = "about:blank";

    public static final String EOLN = "\r\n";

    private static final String VERSION_NUM = "1.0";
    private static final int PADDED_WIDTH = 10;

    private static String toPaddedString(int n, int width) {
        String string = "" + n;
        int len = string.length();
        if (n >= 0 && len < width) {
            char[] array = new char[width - len];
            Arrays.fill(array, '0');
            StringBuffer buffer = new StringBuffer();
            buffer.append(array);
            buffer.append(string);
            string = buffer.toString();
        }
        return string;
    }
    
    public static byte[] convertToHTMLFormat(byte[] bytes) {
        // Calculate section offsets
        String stBaseUrl = DEF_SOURCE_URL;
        int nStartHTML = 
            VERSION.length() + VERSION_NUM.length() + EOLN.length() 
            + START_HTML.length() + PADDED_WIDTH + EOLN.length() 
            + END_HTML.length() + PADDED_WIDTH + EOLN.length() 
            + START_FRAGMENT.length() + PADDED_WIDTH + EOLN.length() 
            + END_FRAGMENT.length() + PADDED_WIDTH + EOLN.length() 
            + SOURCE_URL.length() + stBaseUrl.length() + EOLN.length() 
            ;
        int nStartFragment = nStartHTML + HTML_PREFIX.length();
        int nEndFragment = nStartFragment
            + START_FRAGMENT_CMT.length()
            + bytes.length - 1
            + END_FRAGMENT_CMT.length()            
            ;
        int nEndHTML = nEndFragment + HTML_SUFFIX.length();

        StringBuffer header = new StringBuffer(
            nStartFragment 
            + START_FRAGMENT_CMT.length()
        );
        //header
        header.append(VERSION); 
            header.append(VERSION_NUM); header.append(EOLN);
        header.append(START_HTML); 
            header.append(toPaddedString(nStartHTML, PADDED_WIDTH)); header.append(EOLN);
        header.append(END_HTML); 
            header.append(toPaddedString(nEndHTML, PADDED_WIDTH)); header.append(EOLN);
        header.append(START_FRAGMENT); 
            header.append(toPaddedString(nStartFragment, PADDED_WIDTH)); header.append(EOLN);
        header.append(END_FRAGMENT);
            header.append(toPaddedString(nEndFragment, PADDED_WIDTH)); header.append(EOLN);
        header.append(SOURCE_URL);
            header.append(stBaseUrl); header.append(EOLN);
        //HTML
        header.append(HTML_PREFIX);
        header.append(START_FRAGMENT_CMT);

        byte[] headerBytes = null, trailerBytes = null;

        try {
            headerBytes = new String(header).getBytes(ENCODING);
            trailerBytes = (END_FRAGMENT_CMT + HTML_SUFFIX).getBytes(ENCODING);
        } catch (UnsupportedEncodingException cannotHappen) {
        }

        byte[] retval = new byte[headerBytes.length + bytes.length +
                                 trailerBytes.length];

        System.arraycopy(headerBytes, 0, retval, 0, headerBytes.length);
        System.arraycopy(bytes, 0, retval, headerBytes.length,
                       bytes.length - 1);
        System.arraycopy(trailerBytes, 0, retval,
                         headerBytes.length + bytes.length - 1,
                         trailerBytes.length);
        retval[retval.length-1] = 0;

        return retval;
    }
}
.....
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                     
2006-10-02
EVALUATION

The main problem is in recursion call of public virtual function 
    public byte[] translateTransferable(Transferable contents,
                                        DataFlavor flavor,
                                        long format) throws IOException  
in 
  src\share\classes\sun\awt\datatransfer\DataTransferer.java 
that is reimplemeted in 
  src\windows\classes\sun\awt\windows\WDataTransferer.java
That leads to doubled HTML format-head concatination with clipboard data stream.

This practice does not correspond to good software design and have to be rewritten. But "two-bytes fix" is availuable for service packs (look to the "Suggested fix"). 
Correct fix includes translateBytesOrStream and translateTransferable functions rewriting to avoid the recursion.
A number of tests have to be implemented to test backward compartibility as well as interaction with native applications in clipboard and DnD operations.
HTML text transport have to be upgraded, but this is a subject for another task.
                                     
2006-12-21
SUGGESTED FIX

There is "fast and dirty" fix for Java Service pack,
but mail stream solution have to be more accurate.
 
*** %root%/src/windows/classes/sun/awt/windows/WDataTransferer.java	
*** 158,172 ****

      public byte[] translateTransferable(Transferable contents,
                                          DataFlavor flavor,
                                          long format) throws IOException
      {
!         byte[] bytes = super.translateTransferable(contents, flavor, format);

          if (format == CF_HTML) {
!             bytes = HTMLSupport.convertToHTMLFormat(bytes);
!         }

          return bytes;
      }

      protected Object translateBytesOrStream(InputStream str, byte[] bytes,
--- 158,175 ----

      public byte[] translateTransferable(Transferable contents,
                                          DataFlavor flavor,
                                          long format) throws IOException
      {
!         byte[] bytes = null;

          if (format == CF_HTML) {
!             bytes = HTMLSupport.convertToHTMLFormat(
!                     super.translateTransferable(contents, flavor, CF_TEXT)
!             );
!         } else
!             bytes = super.translateTransferable(contents, flavor, format);

          return bytes;
      }

      protected Object translateBytesOrStream(InputStream str, byte[] bytes,
                                     
2006-12-21
EVALUATION

The recursive action looks like that:
WDataTransfer::translateTransferable(...) { +HTML header for CF_HTML native format }
--(super with HTMLFlavor)-->DataTransfer::translateTransferable()
--(if  flavor.isRepresentationClassInputStream() call translateTransferable again with
plainTextFlavor)-->WDataTransfer::translateTransferable(...){ +second HTML header for CF_HTML native format - that is a problem!}
--(super with plainTextFlavor)-->DataTransfer::translateTransferable()

Using of recursion has no reason at that place because  it needs just for  particular  InputStream to byte[] conversion. Now it is realized as separate translateTransferableString function.

The same problem was in the translateBytesOrStream function that is using at clipboard data extraction. It does not produce an error yet, but gets lack in performance and creates a big risk for any platform-dependent modification.

HTML format is the only format with custom platform action at translateTransferable stage:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   public byte[] translateTransferable(Transferable contents,
                                       DataFlavor flavor,
                                       long format) throws IOException
   {
       byte[] bytes = super.translateTransferable(contents, flavor, format);

       if (format == CF_HTML) {
           bytes = HTMLSupport.convertToHTMLFormat(bytes);
       }
       return bytes;
   }

The clipboard data exchange in JAVA is different for in-process and out-process communications. We most of clipboard support subroutines executing entirely just in case of out-process interaction. That is why HTMLTransferTest.java need to run child process. System.err was chosen by historical reason: ImageTransferTest is a direct child of  ImageTransferTest.

DnDHTMLToOutlookTest is valid for X too. Word and Outlook mentioned as optional program. Any HTML document editor are acceptable at any platform. But MS Outlook and Word for Window platform would provide more extended coverage for test in future, when HTML clipboard format ver 1.0 will accepted for review.

The tests were designed with aim to cover backward compatibility and compatibility with native applications while changing HTML clipboard format.
                                     
2006-12-25
SUGGESTED FIX

The approved webrev revision was copied to
NFS: /net/jano.sfbay/export/disk29/jcg/1.7.0-dolphin/awt/6392086
URL: http://javaweb.sfbay/jcg/1.7.0-dolphin/awt/6392086/
                                     
2007-01-16



Hardware and Software, Engineered to Work Together