1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25  @bug 6392086 8014725
26  @summary Tests basic DnD functionality in an applet
27  @author Alexey Utkin, Semyon Sadetsky
28  @run applet HTMLTransferTest.html
29*/
30
31/**
32 * HTMLTransferTest.java
33 *
34 * summary: tests that HTMLs of all supported native HTML formats
35 *          are transfered properly
36 */
37
38import java.applet.Applet;
39import java.awt.*;
40import java.awt.datatransfer.*;
41import java.io.*;
42
43
44public class HTMLTransferTest extends Applet {
45    public static final int CODE_NOT_RETURNED = 100;
46    public static final int CODE_CONSUMER_TEST_FAILED = 101;
47    public static final int CODE_FAILURE = 102;
48    public static DataFlavor[] HTMLFlavors = null;
49    public static DataFlavor SyncFlavor = null;
50    static {
51        try{
52            HTMLFlavors = new DataFlavor[] {
53                new DataFlavor("text/html; document=selection; Class=" + InputStream.class.getName() + "; charset=UTF-8"),
54                new DataFlavor("text/html; document=selection; Class=" + String.class.getName() + "; charset=UTF-8")
55            };
56            SyncFlavor = new DataFlavor(
57                "application/x-java-serialized-object; class="
58                + SyncMessage.class.getName()
59                + "; charset=UTF-8"
60            );
61        }catch(Exception e){
62            e.printStackTrace();
63        }
64    }
65
66    private THTMLProducer imPr;
67    private int returnCode = CODE_NOT_RETURNED;
68
69    public void init() {
70        initImpl();
71
72        String[] instructions =
73        {
74            "This is an AUTOMATIC test",
75            "simply wait until it is done"
76        };
77        Sysout.createDialog( );
78        Sysout.printInstructions( instructions );
79
80    } // init()
81
82    private void initImpl() {
83        imPr = new THTMLProducer();
84        imPr.begin();
85    }
86
87
88    public void start() {
89        try {
90            String stFormats = "";
91
92            String iniMsg = "Testing formats from the list:\n";
93            for (int i = 0; i < HTMLTransferTest.HTMLFlavors.length; i++) {
94                stFormats += "\"" + HTMLTransferTest.HTMLFlavors[i].getMimeType() + "\"\n";
95            }
96            Sysout.println(iniMsg + stFormats);
97            System.err.println("===>" + iniMsg + stFormats);
98
99            String javaPath = System.getProperty("java.home", "");
100            String cmd = javaPath + File.separator + "bin" + File.separator
101                + "java -cp " + System.getProperty("test.classes", ".") +
102                //+ "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 "
103                " THTMLConsumer"
104                //+ stFormats
105                ;
106
107            Process process = Runtime.getRuntime().exec(cmd);
108            ProcessResults pres = ProcessResults.doWaitFor(process);
109            returnCode = pres.exitValue;
110
111            if (pres.stderr != null && pres.stderr.length() > 0) {
112                System.err.println("========= Child VM System.err ========");
113                System.err.print(pres.stderr);
114                System.err.println("======================================");
115            }
116
117            if (pres.stdout != null && pres.stdout.length() > 0) {
118                System.err.println("========= Child VM System.out ========");
119                System.err.print(pres.stdout);
120                System.err.println("======================================");
121            }
122        } catch (Throwable e) {
123            e.printStackTrace();
124            //returnCode equals CODE_NOT_RETURNED
125        }
126
127        switch (returnCode) {
128        case CODE_NOT_RETURNED:
129            System.err.println("Child VM: failed to start");
130            break;
131        case CODE_FAILURE:
132            System.err.println("Child VM: abnormal termination");
133            break;
134        case CODE_CONSUMER_TEST_FAILED:
135            throw new RuntimeException("test failed: HTMLs in some " +
136                "native formats are not transferred properly: " +
137                "see output of child VM");
138        default:
139            boolean failed = false;
140            String passedFormats = "";
141            String failedFormats = "";
142
143            for (int i = 0; i < imPr.passedArray.length; i++) {
144               if (imPr.passedArray[i]) {
145                   passedFormats += HTMLTransferTest.HTMLFlavors[i].getMimeType() + " ";
146               } else {
147                   failed = true;
148                   failedFormats += HTMLTransferTest.HTMLFlavors[i].getMimeType() + " ";
149               }
150            }
151            if (failed) {
152                throw new RuntimeException(
153                    "test failed: HTMLs in following "
154                    + "native formats are not transferred properly: "
155                    + failedFormats
156                );
157            } else {
158                System.err.println(
159                    "HTMLs in following native formats are "
160                    + "transferred properly: "
161                    + passedFormats
162                );
163            }
164        }
165
166    } // start()
167
168} // class HTMLTransferTest
169
170class SyncMessage implements Serializable {
171    String msg;
172
173    public SyncMessage(String sync) {
174        this.msg = sync;
175    }
176
177    @Override
178    public boolean equals(Object obj) {
179        return this.msg.equals(((SyncMessage)obj).msg);
180    }
181
182    @Override
183    public String toString() {
184        return msg;
185    }
186}
187
188class ProcessResults {
189    public int exitValue;
190    public String stdout;
191    public String stderr;
192
193    public ProcessResults() {
194        exitValue = -1;
195        stdout = "";
196        stderr = "";
197    }
198
199    /**
200     * Method to perform a "wait" for a process and return its exit value.
201     * This is a workaround for <code>Process.waitFor()</code> never returning.
202     */
203    public static ProcessResults doWaitFor(Process p) {
204        ProcessResults pres = new ProcessResults();
205
206        InputStream in = null;
207        InputStream err = null;
208
209        try {
210            in = p.getInputStream();
211            err = p.getErrorStream();
212
213            boolean finished = false;
214
215            while (!finished) {
216                try {
217                    while (in.available() > 0) {
218                        pres.stdout += (char)in.read();
219                    }
220                    while (err.available() > 0) {
221                        pres.stderr += (char)err.read();
222                    }
223                    // Ask the process for its exitValue. If the process
224                    // is not finished, an IllegalThreadStateException
225                    // is thrown. If it is finished, we fall through and
226                    // the variable finished is set to true.
227                    pres.exitValue = p.exitValue();
228                    finished  = true;
229                }
230                catch (IllegalThreadStateException e) {
231                    // Process is not finished yet;
232                    // Sleep a little to save on CPU cycles
233                    Thread.currentThread().sleep(500);
234                }
235            }
236            if (in != null) in.close();
237            if (err != null) err.close();
238        }
239        catch (Throwable e) {
240            System.err.println("doWaitFor(): unexpected exception");
241            e.printStackTrace();
242        }
243        return pres;
244    }
245}
246
247
248abstract class HTMLTransferer implements ClipboardOwner {
249
250    static final SyncMessage S_PASSED = new SyncMessage("Y");
251    static final SyncMessage S_FAILED = new SyncMessage("N");
252    static final SyncMessage S_BEGIN = new SyncMessage("B");
253    static final SyncMessage S_BEGIN_ANSWER = new SyncMessage("BA");
254    static final SyncMessage S_END = new SyncMessage("E");
255
256
257
258    Clipboard m_clipboard;
259
260    HTMLTransferer() {
261        m_clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
262    }
263
264
265    abstract void notifyTransferSuccess(boolean status);
266
267
268    static Object createTRInstance(int i) {
269        try{
270            String _htmlText =
271                "The quick <font color='#78650d'>brown</font> <b>mouse</b> jumped over the lazy <b>cat</b>.";
272            switch(i){
273            case 0:
274                return new ByteArrayInputStream(_htmlText.getBytes("utf-8"));
275            case 1:
276                return _htmlText;
277            }
278        }catch(UnsupportedEncodingException e){ e.printStackTrace(); }
279        return null;
280    }
281
282    static byte[] getContent(InputStream is)
283    {
284        ByteArrayOutputStream tmp = new ByteArrayOutputStream();
285        try{
286            int read;
287            while( -1 != (read = is.read()) ){
288                tmp.write(read);
289            };
290        } catch( IOException e ) {
291            e.printStackTrace();
292        }
293        return tmp.toByteArray();
294    }
295
296    static void Dump(byte[] b){
297        System.err.println( new String(b) );
298    };
299
300    void setClipboardContents(
301        Transferable contents,
302        ClipboardOwner owner
303    ) {
304        synchronized (m_clipboard) {
305            boolean set = false;
306            while (!set) {
307                try {
308                    m_clipboard.setContents(contents, owner);
309                    set = true;
310                } catch (IllegalStateException ise) {
311                    try {
312                        Thread.sleep(100);
313                    } catch(InterruptedException e) {
314                        e.printStackTrace();
315                    }
316                }
317            }
318        }
319    }
320
321    Transferable getClipboardContents(Object requestor)
322    {
323        synchronized (m_clipboard) {
324            while (true) {
325                try {
326                    Transferable t = m_clipboard.getContents(requestor);
327                    return t;
328                } catch (IllegalStateException ise) {
329                    try {
330                        Thread.sleep(100);
331                    } catch (InterruptedException e) {
332                        e.printStackTrace();
333                    }
334                }
335            }
336        }
337    }
338
339}
340
341
342class THTMLProducer extends HTMLTransferer {
343
344    boolean[] passedArray;
345    int fi = 0; // next format index
346    private boolean isFirstCallOfLostOwnership = true;
347
348    THTMLProducer() {
349        passedArray = new boolean[HTMLTransferTest.HTMLFlavors.length];
350    }
351
352    void begin() {
353        setClipboardContents(
354            new HTMLSelection(
355                HTMLTransferTest.SyncFlavor,
356                S_BEGIN
357            ),
358            this
359        );
360    }
361
362    public void lostOwnership(Clipboard cb, Transferable contents) {
363        System.err.println("{PRODUCER: lost clipboard ownership");
364        Transferable t = getClipboardContents(null);
365        if (t.isDataFlavorSupported(HTMLTransferTest.SyncFlavor)) {
366            SyncMessage msg = null;
367            // for test going on if t.getTransferData() will throw an exception
368            if (isFirstCallOfLostOwnership) {
369                isFirstCallOfLostOwnership = false;
370                msg = S_BEGIN_ANSWER;
371            } else {
372                msg = S_PASSED;
373            }
374            try {
375                msg = (SyncMessage)t.getTransferData(HTMLTransferTest.SyncFlavor);
376                System.err.println("++received message: " + msg);
377            } catch (Exception e) {
378                System.err.println("Can't getTransferData-message: " + e);
379            }
380            if( msg.equals(S_PASSED) ){
381                notifyTransferSuccess(true);
382            } else if( msg.equals(S_FAILED) ){
383                notifyTransferSuccess(false);
384            } else if (!msg.equals(S_BEGIN_ANSWER)) {
385                throw new RuntimeException("wrong message in " +
386                    "THTMLProducer.lostOwnership(): " + msg +
387                    "  (possibly due to bug 4683804)");
388            }
389        } else {
390            throw new RuntimeException(
391                "DataFlavor.stringFlavor is not "
392                + "suppurted by transferable in "
393                + "THTMLProducer.lostOwnership()"
394            );
395        }
396
397        if (fi < HTMLTransferTest.HTMLFlavors.length) {
398            System.err.println(
399                "testing native HTML format \""
400                + HTMLTransferTest.HTMLFlavors[fi].getMimeType()
401                + "\"..."
402            );
403            //leaveFormat( HTMLTransferTest.HTMLFlavors[fi].getMimeType() );
404            setClipboardContents(
405                new HTMLSelection(
406                    HTMLTransferTest.HTMLFlavors[fi],
407                    HTMLTransferer.createTRInstance(fi)
408                ),
409                this
410            );
411        } else {
412            setClipboardContents(
413                new HTMLSelection(
414                    HTMLTransferTest.SyncFlavor,
415                    S_END
416                ),
417                null
418            );
419        }
420        System.err.println("}PRODUCER: lost clipboard ownership");
421    }
422
423
424    void notifyTransferSuccess(boolean status) {
425        passedArray[fi] = status;
426        fi++;
427    }
428
429}
430
431
432class THTMLConsumer extends HTMLTransferer
433{
434    private static final Object LOCK = new Object();
435    private static boolean failed;
436    int fi = 0; // next format index
437
438    public void lostOwnership(Clipboard cb, Transferable contents) {
439        System.err.println("{CONSUMER: lost clipboard ownership");
440        Transferable t = getClipboardContents(null);
441        boolean bContinue = true;
442        if(t.isDataFlavorSupported(HTMLTransferTest.SyncFlavor)) {
443            try {
444                SyncMessage msg = (SyncMessage)t.getTransferData(HTMLTransferTest.SyncFlavor);
445                System.err.println("received message: " + msg);
446                if(msg.equals(S_END)){
447                    synchronized (LOCK) {
448                        LOCK.notifyAll();
449                    }
450                    bContinue = false;
451                }
452            } catch (Exception e) {
453                System.err.println("Can't getTransferData-message: " + e);
454            }
455        }
456        if(bContinue){
457            // all HTML formats have been processed
458            System.err.println( "============================================================");
459            System.err.println( "Put as " + HTMLTransferTest.HTMLFlavors[fi].getMimeType() );
460            boolean bSuccess = false;
461            for(int i = 0; i < HTMLTransferTest.HTMLFlavors.length; ++i) {
462                System.err.println( "----------------------------------------------------------");
463                if( t.isDataFlavorSupported(HTMLTransferTest.HTMLFlavors[i]) ){
464                    Object im = null; //? HTML;
465                    try {
466                       im = t.getTransferData(HTMLTransferTest.HTMLFlavors[i]);
467                       if (im == null) {
468                           System.err.println("getTransferData returned null");
469                       } else {
470                            System.err.println( "Extract as " + HTMLTransferTest.HTMLFlavors[i].getMimeType() );
471                            String stIn = "(unknown)", stOut = "(unknown)";
472                            switch( i ){
473                            case 0:
474                                stIn = new String( getContent( (InputStream)HTMLTransferer.createTRInstance(i) ) );
475                                stOut = new String( getContent((InputStream)im) );
476                                bSuccess = stIn.equals(stOut);
477                                break;
478                            case 1:
479                                stIn = (String)HTMLTransferer.createTRInstance(i);
480                                stOut = (String)im;
481                                int head = stOut.indexOf("<HTML><BODY>");
482                                if (head >= 0) {
483                                    stOut = stOut.substring(head + 12, stOut.length() - 14);
484                                }
485                                bSuccess = stIn.equals(stOut);
486                                break;
487                            default:
488                                bSuccess = HTMLTransferer.createTRInstance(i).equals(im);
489                                break;
490                            };
491                            System.err.println("in :" + stIn);
492                            System.err.println("out:" + stOut);
493                       };
494                    } catch (Exception e) {
495                        System.err.println("Can't getTransferData: " + e);
496                    }
497                    if(!bSuccess)
498                        System.err.println("transferred DATA is different from initial DATA\n");
499                } else {
500                    System.err.println("Flavor is not supported by transferable:\n");
501                    DataFlavor[] dfs = t.getTransferDataFlavors();
502                    int ii;
503                    for(ii = 0; ii < dfs.length; ++ii)
504                        System.err.println("Supported:" + dfs[ii] + "\n");
505                    dfs = HTMLTransferTest.HTMLFlavors;
506                    for(ii = 0; ii < dfs.length; ++ii)
507                        System.err.println("Accepted:" + dfs[ii] + "\n" );
508                }
509            }
510            System.err.println( "----------------------------------------------------------");
511            notifyTransferSuccess(bSuccess);
512            System.err.println( "============================================================");
513            ++fi;
514        }
515        System.err.println("}CONSUMER: lost clipboard ownership");
516    }
517
518
519    void notifyTransferSuccess(boolean status) {
520        System.err.println(
521            "format "
522            + (status
523                ? "passed"
524                : "failed"
525            )
526            + "!!!"
527        );
528        setClipboardContents(
529            new HTMLSelection(
530                HTMLTransferTest.SyncFlavor,
531                status
532                    ? S_PASSED
533                    : S_FAILED
534            ),
535            this
536        );
537    }
538
539
540    public static void main(String[] args) {
541        try {
542            System.err.println("{CONSUMER: start");
543            THTMLConsumer ic = new THTMLConsumer();
544            ic.setClipboardContents(
545                new HTMLSelection(
546                    HTMLTransferTest.SyncFlavor,
547                    S_BEGIN_ANSWER
548                ),
549                ic
550            );
551            synchronized (LOCK) {
552                LOCK.wait();
553            }
554            System.err.println("}CONSUMER: start");
555        } catch (Throwable e) {
556            e.printStackTrace();
557            System.exit(HTMLTransferTest.CODE_FAILURE);
558        }
559    }
560
561}
562
563
564/**
565 * A <code>Transferable</code> which implements the capability required
566 * to transfer an <code>HTML</code>.
567 *
568 * This <code>Transferable</code> properly supports
569 * <code>HTMLTransferTest.HTMLFlavors</code>.
570 * and all equivalent flavors.
571 * No other <code>DataFlavor</code>s are supported.
572 *
573 * @see java.awt.datatransfer.HTMLTransferTest.HTMLFlavors
574 */
575class HTMLSelection implements Transferable {
576    private DataFlavor m_flavor;
577    private Object m_data;
578
579    /**
580     * Creates a <code>Transferable</code> capable of transferring
581     * the specified <code>String</code>.
582     */
583    public HTMLSelection(
584        DataFlavor flavor,
585        Object data
586    ){
587        m_flavor = flavor;
588        m_data = data;
589    }
590
591    /**
592     * Returns an array of flavors in which this <code>Transferable</code>
593     * can provide the data. <code>DataFlavor.stringFlavor</code>
594     * is properly supported.
595     * Support for <code>DataFlavor.plainTextFlavor</code> is
596     * <b>deprecated</b>.
597     *
598     * @return an array of length one, whose element is <code>DataFlavor.
599     *         HTMLTransferTest.HTMLFlavors</code>
600     */
601    public DataFlavor[] getTransferDataFlavors() {
602        // returning flavors itself would allow client code to modify
603        // our internal behavior
604        return new DataFlavor[]{ m_flavor } ;
605    }
606
607    /**
608     * Returns whether the requested flavor is supported by this
609     * <code>Transferable</code>.
610     *
611     * @param flavor the requested flavor for the data
612     * @return true if <code>flavor</code> is equal to
613     *   <code>HTMLTransferTest.HTMLFlavors</code>;
614     *   false if <code>flavor</code>
615     *   is not one of the above flavors
616     * @throws NullPointerException if flavor is <code>null</code>
617     */
618    public boolean isDataFlavorSupported(DataFlavor flavor) {
619        System.err.println("Have:" + flavor + " Can:" + m_flavor);
620        if(flavor.equals(m_flavor))
621            return true;
622        return false;
623    }
624
625    /**
626     * Returns the <code>Transferable</code>'s data in the requested
627     * <code>DataFlavor</code> if possible. If the desired flavor is
628     * <code>HTMLTransferTest.HTMLFlavors</code>, or an equivalent flavor,
629     * the <code>HTML</code> representing the selection is
630     * returned.
631     *
632     * @param flavor the requested flavor for the data
633     * @return the data in the requested flavor, as outlined above
634     * @throws UnsupportedFlavorException if the requested data flavor is
635     *         not equivalent to <code>HTMLTransferTest.HTMLFlavors</code>
636     * @throws IOException if an IOException occurs while retrieving the data.
637     *         By default, <code>HTMLSelection</code> never throws
638     *         this exception, but a subclass may.
639     * @throws NullPointerException if flavor is <code>null</code>
640     */
641    public Object getTransferData(DataFlavor flavor)
642        throws UnsupportedFlavorException, IOException
643    {
644        if (flavor.equals(m_flavor)) {
645            return (Object)m_data;
646        } else {
647            throw new UnsupportedFlavorException(flavor);
648        }
649    }
650
651} // class HTMLSelection
652
653
654/****************************************************
655 Standard Test Machinery
656 DO NOT modify anything below -- it's a standard
657  chunk of code whose purpose is to make user
658  interaction uniform, and thereby make it simpler
659  to read and understand someone else's test.
660 ****************************************************/
661class Sysout
662 {
663   private static TestDialog dialog;
664
665   public static void createDialogWithInstructions( String[] instructions )
666    {
667      dialog = new TestDialog( new Frame(), "Instructions" );
668      dialog.printInstructions( instructions );
669      dialog.show();
670      println( "Any messages for the tester will display here." );
671    }
672
673   public static void createDialog( )
674    {
675      dialog = new TestDialog( new Frame(), "Instructions" );
676      String[] defInstr = { "Instructions will appear here. ", "" } ;
677      dialog.printInstructions( defInstr );
678      dialog.show();
679      println( "Any messages for the tester will display here." );
680    }
681
682
683   public static void printInstructions( String[] instructions )
684    {
685      dialog.printInstructions( instructions );
686    }
687
688
689   public static void println( String messageIn )
690    {
691      dialog.displayMessage( messageIn );
692    }
693
694 }// Sysout  class
695
696class TestDialog extends Dialog
697 {
698
699   TextArea instructionsText;
700   TextArea messageText;
701   int maxStringLength = 80;
702
703   //DO NOT call this directly, go through Sysout
704   public TestDialog( Frame frame, String name )
705    {
706      super( frame, name );
707      int scrollBoth = TextArea.SCROLLBARS_BOTH;
708      instructionsText = new TextArea( "", 15, maxStringLength, scrollBoth );
709      add( "North", instructionsText );
710
711      messageText = new TextArea( "", 5, maxStringLength, scrollBoth );
712      add("South", messageText);
713
714      pack();
715
716      show();
717    }// TestDialog()
718
719   //DO NOT call this directly, go through Sysout
720   public void printInstructions( String[] instructions )
721    {
722      //Clear out any current instructions
723      instructionsText.setText( "" );
724
725      //Go down array of instruction strings
726
727      String printStr, remainingStr;
728      for( int i=0; i < instructions.length; i++ )
729       {
730         //chop up each into pieces maxSringLength long
731         remainingStr = instructions[ i ];
732         while( remainingStr.length() > 0 )
733          {
734            //if longer than max then chop off first max chars to print
735            if( remainingStr.length() >= maxStringLength )
736             {
737               //Try to chop on a word boundary
738               int posOfSpace = remainingStr.
739                  lastIndexOf(' ', maxStringLength - 1);
740
741               if( posOfSpace <= 0 ) posOfSpace = maxStringLength - 1;
742
743               printStr = remainingStr.substring( 0, posOfSpace + 1 );
744               remainingStr = remainingStr.substring( posOfSpace + 1 );
745             }
746            //else just print
747            else
748             {
749               printStr = remainingStr;
750               remainingStr = "";
751             }
752
753            instructionsText.append( printStr + "\n" );
754
755          }// while
756
757       }// for
758
759    }//printInstructions()
760
761   //DO NOT call this directly, go through Sysout
762   public void displayMessage( String messageIn )
763    {
764      messageText.append( messageIn + "\n" );
765    }
766
767 }// TestDialog  class
768