8014725: closed/java/awt/Clipboard/HTMLTransferTest/HTMLTransferTest.html failed intermittently
authorssadetsky
Wed, 16 Sep 2015 10:22:42 +0300
changeset 32861 e87c3d1cf3cf
parent 32860 87b4813ebbac
child 32862 0ac04606fbad
8014725: closed/java/awt/Clipboard/HTMLTransferTest/HTMLTransferTest.html failed intermittently Reviewed-by: alexsch, azvegint
jdk/src/java.desktop/share/classes/sun/awt/datatransfer/DataTransferer.java
jdk/test/java/awt/Clipboard/HTMLTransferTest/HTMLTransferTest.html
jdk/test/java/awt/Clipboard/HTMLTransferTest/HTMLTransferTest.java
--- a/jdk/src/java.desktop/share/classes/sun/awt/datatransfer/DataTransferer.java	Wed Sep 16 10:16:16 2015 +0300
+++ b/jdk/src/java.desktop/share/classes/sun/awt/datatransfer/DataTransferer.java	Wed Sep 16 10:22:42 2015 +0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -938,15 +938,9 @@
 
             try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                 try (InputStream is = (InputStream)obj) {
-                    boolean eof = false;
-                    int avail = is.available();
-                    byte[] tmp = new byte[avail > 8192 ? avail : 8192];
-                    do {
-                        int aValue;
-                        if (!(eof = (aValue = is.read(tmp, 0, tmp.length)) == -1)) {
-                            bos.write(tmp, 0, aValue);
-                        }
-                    } while (!eof);
+                    is.mark(Integer.MAX_VALUE);
+                    is.transferTo(bos);
+                    is.reset();
                 }
 
                 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Clipboard/HTMLTransferTest/HTMLTransferTest.html	Wed Sep 16 10:22:42 2015 +0300
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
+ DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+ This code is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License version 2 only, as
+ published by the Free Software Foundation.
+
+ This code is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ version 2 for more details (a copy is included in the LICENSE file that
+ accompanied this code).
+
+ You should have received a copy of the GNU General Public License version
+ 2 along with this work; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ or visit www.oracle.com if you need additional information or have any
+ questions.
+-->
+
+
+<html>
+<!--
+  @test
+  @bug 6392086 8014725
+  @summary Tests basic DnD functionality in an applet
+  @author Alexey Utkin, Semyon Sadetsky area=dnd
+  @run applet HTMLTransferTest.html
+-->
+<head>
+<title>  </title>
+</head>
+<body>
+
+<h1>HTMLTransferTest<br>Bug ID: 6392086</h1>
+
+<p> This is an AUTOMATIC test, simply wait for completion </p>
+
+<APPLET CODE="HTMLTransferTest.class" WIDTH=200 HEIGHT=200></APPLET>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Clipboard/HTMLTransferTest/HTMLTransferTest.java	Wed Sep 16 10:22:42 2015 +0300
@@ -0,0 +1,767 @@
+/*
+ * Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+  @bug 6392086 8014725
+  @summary Tests basic DnD functionality in an applet
+  @author Alexey Utkin, Semyon Sadetsky
+  @run applet HTMLTransferTest.html
+*/
+
+/**
+ * HTMLTransferTest.java
+ *
+ * summary: tests that HTMLs of all supported native HTML formats
+ *          are transfered properly
+ */
+
+import java.applet.Applet;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.io.*;
+
+
+public class HTMLTransferTest extends Applet {
+    public static final int CODE_NOT_RETURNED = 100;
+    public static final int CODE_CONSUMER_TEST_FAILED = 101;
+    public static final int CODE_FAILURE = 102;
+    public static DataFlavor[] HTMLFlavors = null;
+    public static DataFlavor SyncFlavor = null;
+    static {
+        try{
+            HTMLFlavors = new DataFlavor[] {
+                new DataFlavor("text/html; document=selection; Class=" + InputStream.class.getName() + "; charset=UTF-8"),
+                new DataFlavor("text/html; document=selection; Class=" + String.class.getName() + "; charset=UTF-8")
+            };
+            SyncFlavor = new DataFlavor(
+                "application/x-java-serialized-object; class="
+                + SyncMessage.class.getName()
+                + "; charset=UTF-8"
+            );
+        }catch(Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    private THTMLProducer imPr;
+    private int returnCode = CODE_NOT_RETURNED;
+
+    public void init() {
+        initImpl();
+
+        String[] instructions =
+        {
+            "This is an AUTOMATIC test",
+            "simply wait until it is done"
+        };
+        Sysout.createDialog( );
+        Sysout.printInstructions( instructions );
+
+    } // init()
+
+    private void initImpl() {
+        imPr = new THTMLProducer();
+        imPr.begin();
+    }
+
+
+    public void start() {
+        try {
+            String stFormats = "";
+
+            String iniMsg = "Testing formats from the list:\n";
+            for (int i = 0; i < HTMLTransferTest.HTMLFlavors.length; i++) {
+                stFormats += "\"" + HTMLTransferTest.HTMLFlavors[i].getMimeType() + "\"\n";
+            }
+            Sysout.println(iniMsg + stFormats);
+            System.err.println("===>" + iniMsg + stFormats);
+
+            String javaPath = System.getProperty("java.home", "");
+            String cmd = javaPath + File.separator + "bin" + File.separator
+                + "java -cp " + System.getProperty("test.classes", ".") +
+                //+ "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 "
+                " THTMLConsumer"
+                //+ stFormats
+                ;
+
+            Process process = Runtime.getRuntime().exec(cmd);
+            ProcessResults pres = ProcessResults.doWaitFor(process);
+            returnCode = pres.exitValue;
+
+            if (pres.stderr != null && pres.stderr.length() > 0) {
+                System.err.println("========= Child VM System.err ========");
+                System.err.print(pres.stderr);
+                System.err.println("======================================");
+            }
+
+            if (pres.stdout != null && pres.stdout.length() > 0) {
+                System.err.println("========= Child VM System.out ========");
+                System.err.print(pres.stdout);
+                System.err.println("======================================");
+            }
+        } catch (Throwable e) {
+            e.printStackTrace();
+            //returnCode equals CODE_NOT_RETURNED
+        }
+
+        switch (returnCode) {
+        case CODE_NOT_RETURNED:
+            System.err.println("Child VM: failed to start");
+            break;
+        case CODE_FAILURE:
+            System.err.println("Child VM: abnormal termination");
+            break;
+        case CODE_CONSUMER_TEST_FAILED:
+            throw new RuntimeException("test failed: HTMLs in some " +
+                "native formats are not transferred properly: " +
+                "see output of child VM");
+        default:
+            boolean failed = false;
+            String passedFormats = "";
+            String failedFormats = "";
+
+            for (int i = 0; i < imPr.passedArray.length; i++) {
+               if (imPr.passedArray[i]) {
+                   passedFormats += HTMLTransferTest.HTMLFlavors[i].getMimeType() + " ";
+               } else {
+                   failed = true;
+                   failedFormats += HTMLTransferTest.HTMLFlavors[i].getMimeType() + " ";
+               }
+            }
+            if (failed) {
+                throw new RuntimeException(
+                    "test failed: HTMLs in following "
+                    + "native formats are not transferred properly: "
+                    + failedFormats
+                );
+            } else {
+                System.err.println(
+                    "HTMLs in following native formats are "
+                    + "transferred properly: "
+                    + passedFormats
+                );
+            }
+        }
+
+    } // start()
+
+} // class HTMLTransferTest
+
+class SyncMessage implements Serializable {
+    String msg;
+
+    public SyncMessage(String sync) {
+        this.msg = sync;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return this.msg.equals(((SyncMessage)obj).msg);
+    }
+
+    @Override
+    public String toString() {
+        return msg;
+    }
+}
+
+class ProcessResults {
+    public int exitValue;
+    public String stdout;
+    public String stderr;
+
+    public ProcessResults() {
+        exitValue = -1;
+        stdout = "";
+        stderr = "";
+    }
+
+    /**
+     * Method to perform a "wait" for a process and return its exit value.
+     * This is a workaround for <code>Process.waitFor()</code> never returning.
+     */
+    public static ProcessResults doWaitFor(Process p) {
+        ProcessResults pres = new ProcessResults();
+
+        InputStream in = null;
+        InputStream err = null;
+
+        try {
+            in = p.getInputStream();
+            err = p.getErrorStream();
+
+            boolean finished = false;
+
+            while (!finished) {
+                try {
+                    while (in.available() > 0) {
+                        pres.stdout += (char)in.read();
+                    }
+                    while (err.available() > 0) {
+                        pres.stderr += (char)err.read();
+                    }
+                    // Ask the process for its exitValue. If the process
+                    // is not finished, an IllegalThreadStateException
+                    // is thrown. If it is finished, we fall through and
+                    // the variable finished is set to true.
+                    pres.exitValue = p.exitValue();
+                    finished  = true;
+                }
+                catch (IllegalThreadStateException e) {
+                    // Process is not finished yet;
+                    // Sleep a little to save on CPU cycles
+                    Thread.currentThread().sleep(500);
+                }
+            }
+            if (in != null) in.close();
+            if (err != null) err.close();
+        }
+        catch (Throwable e) {
+            System.err.println("doWaitFor(): unexpected exception");
+            e.printStackTrace();
+        }
+        return pres;
+    }
+}
+
+
+abstract class HTMLTransferer implements ClipboardOwner {
+
+    static final SyncMessage S_PASSED = new SyncMessage("Y");
+    static final SyncMessage S_FAILED = new SyncMessage("N");
+    static final SyncMessage S_BEGIN = new SyncMessage("B");
+    static final SyncMessage S_BEGIN_ANSWER = new SyncMessage("BA");
+    static final SyncMessage S_END = new SyncMessage("E");
+
+
+
+    Clipboard m_clipboard;
+
+    HTMLTransferer() {
+        m_clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+    }
+
+
+    abstract void notifyTransferSuccess(boolean status);
+
+
+    static Object createTRInstance(int i) {
+        try{
+            String _htmlText =
+                "The quick <font color='#78650d'>brown</font> <b>mouse</b> jumped over the lazy <b>cat</b>.";
+            switch(i){
+            case 0:
+                return new ByteArrayInputStream(_htmlText.getBytes("utf-8"));
+            case 1:
+                return _htmlText;
+            }
+        }catch(UnsupportedEncodingException e){ e.printStackTrace(); }
+        return null;
+    }
+
+    static byte[] getContent(InputStream is)
+    {
+        ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+        try{
+            int read;
+            while( -1 != (read = is.read()) ){
+                tmp.write(read);
+            };
+        } catch( IOException e ) {
+            e.printStackTrace();
+        }
+        return tmp.toByteArray();
+    }
+
+    static void Dump(byte[] b){
+        System.err.println( new String(b) );
+    };
+
+    void setClipboardContents(
+        Transferable contents,
+        ClipboardOwner owner
+    ) {
+        synchronized (m_clipboard) {
+            boolean set = false;
+            while (!set) {
+                try {
+                    m_clipboard.setContents(contents, owner);
+                    set = true;
+                } catch (IllegalStateException ise) {
+                    try {
+                        Thread.sleep(100);
+                    } catch(InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    Transferable getClipboardContents(Object requestor)
+    {
+        synchronized (m_clipboard) {
+            while (true) {
+                try {
+                    Transferable t = m_clipboard.getContents(requestor);
+                    return t;
+                } catch (IllegalStateException ise) {
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+}
+
+
+class THTMLProducer extends HTMLTransferer {
+
+    boolean[] passedArray;
+    int fi = 0; // next format index
+    private boolean isFirstCallOfLostOwnership = true;
+
+    THTMLProducer() {
+        passedArray = new boolean[HTMLTransferTest.HTMLFlavors.length];
+    }
+
+    void begin() {
+        setClipboardContents(
+            new HTMLSelection(
+                HTMLTransferTest.SyncFlavor,
+                S_BEGIN
+            ),
+            this
+        );
+    }
+
+    public void lostOwnership(Clipboard cb, Transferable contents) {
+        System.err.println("{PRODUCER: lost clipboard ownership");
+        Transferable t = getClipboardContents(null);
+        if (t.isDataFlavorSupported(HTMLTransferTest.SyncFlavor)) {
+            SyncMessage msg = null;
+            // for test going on if t.getTransferData() will throw an exception
+            if (isFirstCallOfLostOwnership) {
+                isFirstCallOfLostOwnership = false;
+                msg = S_BEGIN_ANSWER;
+            } else {
+                msg = S_PASSED;
+            }
+            try {
+                msg = (SyncMessage)t.getTransferData(HTMLTransferTest.SyncFlavor);
+                System.err.println("++received message: " + msg);
+            } catch (Exception e) {
+                System.err.println("Can't getTransferData-message: " + e);
+            }
+            if( msg.equals(S_PASSED) ){
+                notifyTransferSuccess(true);
+            } else if( msg.equals(S_FAILED) ){
+                notifyTransferSuccess(false);
+            } else if (!msg.equals(S_BEGIN_ANSWER)) {
+                throw new RuntimeException("wrong message in " +
+                    "THTMLProducer.lostOwnership(): " + msg +
+                    "  (possibly due to bug 4683804)");
+            }
+        } else {
+            throw new RuntimeException(
+                "DataFlavor.stringFlavor is not "
+                + "suppurted by transferable in "
+                + "THTMLProducer.lostOwnership()"
+            );
+        }
+
+        if (fi < HTMLTransferTest.HTMLFlavors.length) {
+            System.err.println(
+                "testing native HTML format \""
+                + HTMLTransferTest.HTMLFlavors[fi].getMimeType()
+                + "\"..."
+            );
+            //leaveFormat( HTMLTransferTest.HTMLFlavors[fi].getMimeType() );
+            setClipboardContents(
+                new HTMLSelection(
+                    HTMLTransferTest.HTMLFlavors[fi],
+                    HTMLTransferer.createTRInstance(fi)
+                ),
+                this
+            );
+        } else {
+            setClipboardContents(
+                new HTMLSelection(
+                    HTMLTransferTest.SyncFlavor,
+                    S_END
+                ),
+                null
+            );
+        }
+        System.err.println("}PRODUCER: lost clipboard ownership");
+    }
+
+
+    void notifyTransferSuccess(boolean status) {
+        passedArray[fi] = status;
+        fi++;
+    }
+
+}
+
+
+class THTMLConsumer extends HTMLTransferer
+{
+    private static final Object LOCK = new Object();
+    private static boolean failed;
+    int fi = 0; // next format index
+
+    public void lostOwnership(Clipboard cb, Transferable contents) {
+        System.err.println("{CONSUMER: lost clipboard ownership");
+        Transferable t = getClipboardContents(null);
+        boolean bContinue = true;
+        if(t.isDataFlavorSupported(HTMLTransferTest.SyncFlavor)) {
+            try {
+                SyncMessage msg = (SyncMessage)t.getTransferData(HTMLTransferTest.SyncFlavor);
+                System.err.println("received message: " + msg);
+                if(msg.equals(S_END)){
+                    synchronized (LOCK) {
+                        LOCK.notifyAll();
+                    }
+                    bContinue = false;
+                }
+            } catch (Exception e) {
+                System.err.println("Can't getTransferData-message: " + e);
+            }
+        }
+        if(bContinue){
+            // all HTML formats have been processed
+            System.err.println( "============================================================");
+            System.err.println( "Put as " + HTMLTransferTest.HTMLFlavors[fi].getMimeType() );
+            boolean bSuccess = false;
+            for(int i = 0; i < HTMLTransferTest.HTMLFlavors.length; ++i) {
+                System.err.println( "----------------------------------------------------------");
+                if( t.isDataFlavorSupported(HTMLTransferTest.HTMLFlavors[i]) ){
+                    Object im = null; //? HTML;
+                    try {
+                       im = t.getTransferData(HTMLTransferTest.HTMLFlavors[i]);
+                       if (im == null) {
+                           System.err.println("getTransferData returned null");
+                       } else {
+                            System.err.println( "Extract as " + HTMLTransferTest.HTMLFlavors[i].getMimeType() );
+                            String stIn = "(unknown)", stOut = "(unknown)";
+                            switch( i ){
+                            case 0:
+                                stIn = new String( getContent( (InputStream)HTMLTransferer.createTRInstance(i) ) );
+                                stOut = new String( getContent((InputStream)im) );
+                                bSuccess = stIn.equals(stOut);
+                                break;
+                            case 1:
+                                stIn = (String)HTMLTransferer.createTRInstance(i);
+                                stOut = (String)im;
+                                int head = stOut.indexOf("<HTML><BODY>");
+                                if (head >= 0) {
+                                    stOut = stOut.substring(head + 12, stOut.length() - 14);
+                                }
+                                bSuccess = stIn.equals(stOut);
+                                break;
+                            default:
+                                bSuccess = HTMLTransferer.createTRInstance(i).equals(im);
+                                break;
+                            };
+                            System.err.println("in :" + stIn);
+                            System.err.println("out:" + stOut);
+                       };
+                    } catch (Exception e) {
+                        System.err.println("Can't getTransferData: " + e);
+                    }
+                    if(!bSuccess)
+                        System.err.println("transferred DATA is different from initial DATA\n");
+                } else {
+                    System.err.println("Flavor is not supported by transferable:\n");
+                    DataFlavor[] dfs = t.getTransferDataFlavors();
+                    int ii;
+                    for(ii = 0; ii < dfs.length; ++ii)
+                        System.err.println("Supported:" + dfs[ii] + "\n");
+                    dfs = HTMLTransferTest.HTMLFlavors;
+                    for(ii = 0; ii < dfs.length; ++ii)
+                        System.err.println("Accepted:" + dfs[ii] + "\n" );
+                }
+            }
+            System.err.println( "----------------------------------------------------------");
+            notifyTransferSuccess(bSuccess);
+            System.err.println( "============================================================");
+            ++fi;
+        }
+        System.err.println("}CONSUMER: lost clipboard ownership");
+    }
+
+
+    void notifyTransferSuccess(boolean status) {
+        System.err.println(
+            "format "
+            + (status
+                ? "passed"
+                : "failed"
+            )
+            + "!!!"
+        );
+        setClipboardContents(
+            new HTMLSelection(
+                HTMLTransferTest.SyncFlavor,
+                status
+                    ? S_PASSED
+                    : S_FAILED
+            ),
+            this
+        );
+    }
+
+
+    public static void main(String[] args) {
+        try {
+            System.err.println("{CONSUMER: start");
+            THTMLConsumer ic = new THTMLConsumer();
+            ic.setClipboardContents(
+                new HTMLSelection(
+                    HTMLTransferTest.SyncFlavor,
+                    S_BEGIN_ANSWER
+                ),
+                ic
+            );
+            synchronized (LOCK) {
+                LOCK.wait();
+            }
+            System.err.println("}CONSUMER: start");
+        } catch (Throwable e) {
+            e.printStackTrace();
+            System.exit(HTMLTransferTest.CODE_FAILURE);
+        }
+    }
+
+}
+
+
+/**
+ * A <code>Transferable</code> which implements the capability required
+ * to transfer an <code>HTML</code>.
+ *
+ * This <code>Transferable</code> properly supports
+ * <code>HTMLTransferTest.HTMLFlavors</code>.
+ * and all equivalent flavors.
+ * No other <code>DataFlavor</code>s are supported.
+ *
+ * @see java.awt.datatransfer.HTMLTransferTest.HTMLFlavors
+ */
+class HTMLSelection implements Transferable {
+    private DataFlavor m_flavor;
+    private Object m_data;
+
+    /**
+     * Creates a <code>Transferable</code> capable of transferring
+     * the specified <code>String</code>.
+     */
+    public HTMLSelection(
+        DataFlavor flavor,
+        Object data
+    ){
+        m_flavor = flavor;
+        m_data = data;
+    }
+
+    /**
+     * Returns an array of flavors in which this <code>Transferable</code>
+     * can provide the data. <code>DataFlavor.stringFlavor</code>
+     * is properly supported.
+     * Support for <code>DataFlavor.plainTextFlavor</code> is
+     * <b>deprecated</b>.
+     *
+     * @return an array of length one, whose element is <code>DataFlavor.
+     *         HTMLTransferTest.HTMLFlavors</code>
+     */
+    public DataFlavor[] getTransferDataFlavors() {
+        // returning flavors itself would allow client code to modify
+        // our internal behavior
+        return new DataFlavor[]{ m_flavor } ;
+    }
+
+    /**
+     * Returns whether the requested flavor is supported by this
+     * <code>Transferable</code>.
+     *
+     * @param flavor the requested flavor for the data
+     * @return true if <code>flavor</code> is equal to
+     *   <code>HTMLTransferTest.HTMLFlavors</code>;
+     *   false if <code>flavor</code>
+     *   is not one of the above flavors
+     * @throws NullPointerException if flavor is <code>null</code>
+     */
+    public boolean isDataFlavorSupported(DataFlavor flavor) {
+        System.err.println("Have:" + flavor + " Can:" + m_flavor);
+        if(flavor.equals(m_flavor))
+            return true;
+        return false;
+    }
+
+    /**
+     * Returns the <code>Transferable</code>'s data in the requested
+     * <code>DataFlavor</code> if possible. If the desired flavor is
+     * <code>HTMLTransferTest.HTMLFlavors</code>, or an equivalent flavor,
+     * the <code>HTML</code> representing the selection is
+     * returned.
+     *
+     * @param flavor the requested flavor for the data
+     * @return the data in the requested flavor, as outlined above
+     * @throws UnsupportedFlavorException if the requested data flavor is
+     *         not equivalent to <code>HTMLTransferTest.HTMLFlavors</code>
+     * @throws IOException if an IOException occurs while retrieving the data.
+     *         By default, <code>HTMLSelection</code> never throws
+     *         this exception, but a subclass may.
+     * @throws NullPointerException if flavor is <code>null</code>
+     */
+    public Object getTransferData(DataFlavor flavor)
+        throws UnsupportedFlavorException, IOException
+    {
+        if (flavor.equals(m_flavor)) {
+            return (Object)m_data;
+        } else {
+            throw new UnsupportedFlavorException(flavor);
+        }
+    }
+
+} // class HTMLSelection
+
+
+/****************************************************
+ Standard Test Machinery
+ DO NOT modify anything below -- it's a standard
+  chunk of code whose purpose is to make user
+  interaction uniform, and thereby make it simpler
+  to read and understand someone else's test.
+ ****************************************************/
+class Sysout
+ {
+   private static TestDialog dialog;
+
+   public static void createDialogWithInstructions( String[] instructions )
+    {
+      dialog = new TestDialog( new Frame(), "Instructions" );
+      dialog.printInstructions( instructions );
+      dialog.show();
+      println( "Any messages for the tester will display here." );
+    }
+
+   public static void createDialog( )
+    {
+      dialog = new TestDialog( new Frame(), "Instructions" );
+      String[] defInstr = { "Instructions will appear here. ", "" } ;
+      dialog.printInstructions( defInstr );
+      dialog.show();
+      println( "Any messages for the tester will display here." );
+    }
+
+
+   public static void printInstructions( String[] instructions )
+    {
+      dialog.printInstructions( instructions );
+    }
+
+
+   public static void println( String messageIn )
+    {
+      dialog.displayMessage( messageIn );
+    }
+
+ }// Sysout  class
+
+class TestDialog extends Dialog
+ {
+
+   TextArea instructionsText;
+   TextArea messageText;
+   int maxStringLength = 80;
+
+   //DO NOT call this directly, go through Sysout
+   public TestDialog( Frame frame, String name )
+    {
+      super( frame, name );
+      int scrollBoth = TextArea.SCROLLBARS_BOTH;
+      instructionsText = new TextArea( "", 15, maxStringLength, scrollBoth );
+      add( "North", instructionsText );
+
+      messageText = new TextArea( "", 5, maxStringLength, scrollBoth );
+      add("South", messageText);
+
+      pack();
+
+      show();
+    }// TestDialog()
+
+   //DO NOT call this directly, go through Sysout
+   public void printInstructions( String[] instructions )
+    {
+      //Clear out any current instructions
+      instructionsText.setText( "" );
+
+      //Go down array of instruction strings
+
+      String printStr, remainingStr;
+      for( int i=0; i < instructions.length; i++ )
+       {
+         //chop up each into pieces maxSringLength long
+         remainingStr = instructions[ i ];
+         while( remainingStr.length() > 0 )
+          {
+            //if longer than max then chop off first max chars to print
+            if( remainingStr.length() >= maxStringLength )
+             {
+               //Try to chop on a word boundary
+               int posOfSpace = remainingStr.
+                  lastIndexOf(' ', maxStringLength - 1);
+
+               if( posOfSpace <= 0 ) posOfSpace = maxStringLength - 1;
+
+               printStr = remainingStr.substring( 0, posOfSpace + 1 );
+               remainingStr = remainingStr.substring( posOfSpace + 1 );
+             }
+            //else just print
+            else
+             {
+               printStr = remainingStr;
+               remainingStr = "";
+             }
+
+            instructionsText.append( printStr + "\n" );
+
+          }// while
+
+       }// for
+
+    }//printInstructions()
+
+   //DO NOT call this directly, go through Sysout
+   public void displayMessage( String messageIn )
+    {
+      messageText.append( messageIn + "\n" );
+    }
+
+ }// TestDialog  class