1 /*
2  * Copyright (c) 2015, 2018, 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   @test
26   @key headful
27   @bug 6392086 8014725
28   @summary tests that HTMLs of all supported native HTML formats are transfered
29            properly
30   @run main/othervm HTMLTransferTest
31 */
32 
33 import java.awt.*;
34 import java.awt.datatransfer.*;
35 import java.io.*;
36 
37 public class HTMLTransferTest {
38     public static final int CODE_NOT_RETURNED = 100;
39     public static final int CODE_CONSUMER_TEST_FAILED = 101;
40     public static final int CODE_FAILURE = 102;
41     public static DataFlavor[] HTMLFlavors = null;
42     public static DataFlavor SyncFlavor = null;
43     static {
44         try{
45             HTMLFlavors = new DataFlavor[] {
46                 new DataFlavor("text/html; document=selection; Class=" + InputStream.class.getName() + "; charset=UTF-8"),
47                 new DataFlavor("text/html; document=selection; Class=" + String.class.getName() + "; charset=UTF-8")
48             };
49             SyncFlavor = new DataFlavor(
50                 "application/x-java-serialized-object; class="
51                 + SyncMessage.class.getName()
52                 + "; charset=UTF-8"
53             );
54         }catch(Exception e){
55             e.printStackTrace();
56         }
57     }
58 
59     private THTMLProducer imPr;
60     private int returnCode = CODE_NOT_RETURNED;
61 
main(final String[] args)62     public static void main(final String[] args) {
63         HTMLTransferTest app = new HTMLTransferTest();
64         app.init();
65         app.start();
66     }
67 
init()68     public void init() {
69         initImpl();
70 
71     } // init()
72 
initImpl()73     private void initImpl() {
74         imPr = new THTMLProducer();
75         imPr.begin();
76     }
77 
78 
start()79     public void start() {
80         try {
81             String stFormats = "";
82 
83             String iniMsg = "Testing formats from the list:\n";
84             for (int i = 0; i < HTMLTransferTest.HTMLFlavors.length; i++) {
85                 stFormats += "\"" + HTMLTransferTest.HTMLFlavors[i].getMimeType() + "\"\n";
86             }
87             System.out.println(iniMsg + stFormats);
88             System.err.println("===>" + iniMsg + stFormats);
89 
90             String javaPath = System.getProperty("java.home", "");
91             String cmd = javaPath + File.separator + "bin" + File.separator
92                 + "java -cp " + System.getProperty("test.classes", ".") +
93                 //+ "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 "
94                 " THTMLConsumer"
95                 //+ stFormats
96                 ;
97 
98             Process process = Runtime.getRuntime().exec(cmd);
99             ProcessResults pres = ProcessResults.doWaitFor(process);
100             returnCode = pres.exitValue;
101 
102             if (pres.stderr != null && pres.stderr.length() > 0) {
103                 System.err.println("========= Child VM System.err ========");
104                 System.err.print(pres.stderr);
105                 System.err.println("======================================");
106             }
107 
108             if (pres.stdout != null && pres.stdout.length() > 0) {
109                 System.err.println("========= Child VM System.out ========");
110                 System.err.print(pres.stdout);
111                 System.err.println("======================================");
112             }
113         } catch (Throwable e) {
114             e.printStackTrace();
115             //returnCode equals CODE_NOT_RETURNED
116         }
117 
118         switch (returnCode) {
119         case CODE_NOT_RETURNED:
120             System.err.println("Child VM: failed to start");
121             break;
122         case CODE_FAILURE:
123             System.err.println("Child VM: abnormal termination");
124             break;
125         case CODE_CONSUMER_TEST_FAILED:
126             throw new RuntimeException("test failed: HTMLs in some " +
127                 "native formats are not transferred properly: " +
128                 "see output of child VM");
129         default:
130             boolean failed = false;
131             String passedFormats = "";
132             String failedFormats = "";
133 
134             for (int i = 0; i < imPr.passedArray.length; i++) {
135                if (imPr.passedArray[i]) {
136                    passedFormats += HTMLTransferTest.HTMLFlavors[i].getMimeType() + " ";
137                } else {
138                    failed = true;
139                    failedFormats += HTMLTransferTest.HTMLFlavors[i].getMimeType() + " ";
140                }
141             }
142             if (failed) {
143                 throw new RuntimeException(
144                     "test failed: HTMLs in following "
145                     + "native formats are not transferred properly: "
146                     + failedFormats
147                 );
148             } else {
149                 System.err.println(
150                     "HTMLs in following native formats are "
151                     + "transferred properly: "
152                     + passedFormats
153                 );
154             }
155         }
156 
157     } // start()
158 
159 } // class HTMLTransferTest
160 
161 class SyncMessage implements Serializable {
162     String msg;
163 
SyncMessage(String sync)164     public SyncMessage(String sync) {
165         this.msg = sync;
166     }
167 
168     @Override
equals(Object obj)169     public boolean equals(Object obj) {
170         return this.msg.equals(((SyncMessage)obj).msg);
171     }
172 
173     @Override
toString()174     public String toString() {
175         return msg;
176     }
177 }
178 
179 class ProcessResults {
180     public int exitValue;
181     public String stdout;
182     public String stderr;
183 
ProcessResults()184     public ProcessResults() {
185         exitValue = -1;
186         stdout = "";
187         stderr = "";
188     }
189 
190     /**
191      * Method to perform a "wait" for a process and return its exit value.
192      * This is a workaround for <code>Process.waitFor()</code> never returning.
193      */
doWaitFor(Process p)194     public static ProcessResults doWaitFor(Process p) {
195         ProcessResults pres = new ProcessResults();
196 
197         InputStream in = null;
198         InputStream err = null;
199 
200         try {
201             in = p.getInputStream();
202             err = p.getErrorStream();
203 
204             boolean finished = false;
205 
206             while (!finished) {
207                 try {
208                     while (in.available() > 0) {
209                         pres.stdout += (char)in.read();
210                     }
211                     while (err.available() > 0) {
212                         pres.stderr += (char)err.read();
213                     }
214                     // Ask the process for its exitValue. If the process
215                     // is not finished, an IllegalThreadStateException
216                     // is thrown. If it is finished, we fall through and
217                     // the variable finished is set to true.
218                     pres.exitValue = p.exitValue();
219                     finished  = true;
220                 }
221                 catch (IllegalThreadStateException e) {
222                     // Process is not finished yet;
223                     // Sleep a little to save on CPU cycles
224                     Thread.currentThread().sleep(500);
225                 }
226             }
227             if (in != null) in.close();
228             if (err != null) err.close();
229         }
230         catch (Throwable e) {
231             System.err.println("doWaitFor(): unexpected exception");
232             e.printStackTrace();
233         }
234         return pres;
235     }
236 }
237 
238 
239 abstract class HTMLTransferer implements ClipboardOwner {
240 
241     static final SyncMessage S_PASSED = new SyncMessage("Y");
242     static final SyncMessage S_FAILED = new SyncMessage("N");
243     static final SyncMessage S_BEGIN = new SyncMessage("B");
244     static final SyncMessage S_BEGIN_ANSWER = new SyncMessage("BA");
245     static final SyncMessage S_END = new SyncMessage("E");
246 
247 
248 
249     Clipboard m_clipboard;
250 
HTMLTransferer()251     HTMLTransferer() {
252         m_clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
253     }
254 
255 
notifyTransferSuccess(boolean status)256     abstract void notifyTransferSuccess(boolean status);
257 
258 
createTRInstance(int i)259     static Object createTRInstance(int i) {
260         try{
261             String _htmlText =
262                 "The quick <font color='#78650d'>brown</font> <b>mouse</b> jumped over the lazy <b>cat</b>.";
263             switch(i){
264             case 0:
265                 return new ByteArrayInputStream(_htmlText.getBytes("utf-8"));
266             case 1:
267                 return _htmlText;
268             }
269         }catch(UnsupportedEncodingException e){ e.printStackTrace(); }
270         return null;
271     }
272 
getContent(InputStream is)273     static byte[] getContent(InputStream is)
274     {
275         ByteArrayOutputStream tmp = new ByteArrayOutputStream();
276         try{
277             int read;
278             while( -1 != (read = is.read()) ){
279                 tmp.write(read);
280             };
281         } catch( IOException e ) {
282             e.printStackTrace();
283         }
284         return tmp.toByteArray();
285     }
286 
Dump(byte[] b)287     static void Dump(byte[] b){
288         System.err.println( new String(b) );
289     };
290 
setClipboardContents( Transferable contents, ClipboardOwner owner )291     void setClipboardContents(
292         Transferable contents,
293         ClipboardOwner owner
294     ) {
295         synchronized (m_clipboard) {
296             boolean set = false;
297             while (!set) {
298                 try {
299                     m_clipboard.setContents(contents, owner);
300                     set = true;
301                 } catch (IllegalStateException ise) {
302                     try {
303                         Thread.sleep(100);
304                     } catch(InterruptedException e) {
305                         e.printStackTrace();
306                     }
307                 }
308             }
309         }
310     }
311 
getClipboardContents(Object requestor)312     Transferable getClipboardContents(Object requestor)
313     {
314         synchronized (m_clipboard) {
315             while (true) {
316                 try {
317                     Transferable t = m_clipboard.getContents(requestor);
318                     return t;
319                 } catch (IllegalStateException ise) {
320                     try {
321                         Thread.sleep(100);
322                     } catch (InterruptedException e) {
323                         e.printStackTrace();
324                     }
325                 }
326             }
327         }
328     }
329 
330 }
331 
332 
333 class THTMLProducer extends HTMLTransferer {
334 
335     boolean[] passedArray;
336     int fi = 0; // next format index
337     private boolean isFirstCallOfLostOwnership = true;
338 
THTMLProducer()339     THTMLProducer() {
340         passedArray = new boolean[HTMLTransferTest.HTMLFlavors.length];
341     }
342 
begin()343     void begin() {
344         setClipboardContents(
345             new HTMLSelection(
346                 HTMLTransferTest.SyncFlavor,
347                 S_BEGIN
348             ),
349             this
350         );
351     }
352 
lostOwnership(Clipboard cb, Transferable contents)353     public void lostOwnership(Clipboard cb, Transferable contents) {
354         System.err.println("{PRODUCER: lost clipboard ownership");
355         Transferable t = getClipboardContents(null);
356         if (t.isDataFlavorSupported(HTMLTransferTest.SyncFlavor)) {
357             SyncMessage msg = null;
358             // for test going on if t.getTransferData() will throw an exception
359             if (isFirstCallOfLostOwnership) {
360                 isFirstCallOfLostOwnership = false;
361                 msg = S_BEGIN_ANSWER;
362             } else {
363                 msg = S_PASSED;
364             }
365             try {
366                 msg = (SyncMessage)t.getTransferData(HTMLTransferTest.SyncFlavor);
367                 System.err.println("++received message: " + msg);
368             } catch (Exception e) {
369                 System.err.println("Can't getTransferData-message: " + e);
370             }
371             if( msg.equals(S_PASSED) ){
372                 notifyTransferSuccess(true);
373             } else if( msg.equals(S_FAILED) ){
374                 notifyTransferSuccess(false);
375             } else if (!msg.equals(S_BEGIN_ANSWER)) {
376                 throw new RuntimeException("wrong message in " +
377                     "THTMLProducer.lostOwnership(): " + msg +
378                     "  (possibly due to bug 4683804)");
379             }
380         } else {
381             throw new RuntimeException(
382                 "DataFlavor.stringFlavor is not "
383                 + "suppurted by transferable in "
384                 + "THTMLProducer.lostOwnership()"
385             );
386         }
387 
388         if (fi < HTMLTransferTest.HTMLFlavors.length) {
389             System.err.println(
390                 "testing native HTML format \""
391                 + HTMLTransferTest.HTMLFlavors[fi].getMimeType()
392                 + "\"..."
393             );
394             //leaveFormat( HTMLTransferTest.HTMLFlavors[fi].getMimeType() );
395             setClipboardContents(
396                 new HTMLSelection(
397                     HTMLTransferTest.HTMLFlavors[fi],
398                     HTMLTransferer.createTRInstance(fi)
399                 ),
400                 this
401             );
402         } else {
403             setClipboardContents(
404                 new HTMLSelection(
405                     HTMLTransferTest.SyncFlavor,
406                     S_END
407                 ),
408                 null
409             );
410         }
411         System.err.println("}PRODUCER: lost clipboard ownership");
412     }
413 
414 
notifyTransferSuccess(boolean status)415     void notifyTransferSuccess(boolean status) {
416         passedArray[fi] = status;
417         fi++;
418     }
419 
420 }
421 
422 
423 class THTMLConsumer extends HTMLTransferer
424 {
425     private static final Object LOCK = new Object();
426     private static boolean failed;
427     int fi = 0; // next format index
428 
lostOwnership(Clipboard cb, Transferable contents)429     public void lostOwnership(Clipboard cb, Transferable contents) {
430         System.err.println("{CONSUMER: lost clipboard ownership");
431         Transferable t = getClipboardContents(null);
432         boolean bContinue = true;
433         if(t.isDataFlavorSupported(HTMLTransferTest.SyncFlavor)) {
434             try {
435                 SyncMessage msg = (SyncMessage)t.getTransferData(HTMLTransferTest.SyncFlavor);
436                 System.err.println("received message: " + msg);
437                 if(msg.equals(S_END)){
438                     synchronized (LOCK) {
439                         LOCK.notifyAll();
440                     }
441                     bContinue = false;
442                 }
443             } catch (Exception e) {
444                 System.err.println("Can't getTransferData-message: " + e);
445             }
446         }
447         if(bContinue){
448             // all HTML formats have been processed
449             System.err.println( "============================================================");
450             System.err.println( "Put as " + HTMLTransferTest.HTMLFlavors[fi].getMimeType() );
451             boolean bSuccess = false;
452             for(int i = 0; i < HTMLTransferTest.HTMLFlavors.length; ++i) {
453                 System.err.println( "----------------------------------------------------------");
454                 if( t.isDataFlavorSupported(HTMLTransferTest.HTMLFlavors[i]) ){
455                     Object im = null; //? HTML;
456                     try {
457                        im = t.getTransferData(HTMLTransferTest.HTMLFlavors[i]);
458                        if (im == null) {
459                            System.err.println("getTransferData returned null");
460                        } else {
461                             System.err.println( "Extract as " + HTMLTransferTest.HTMLFlavors[i].getMimeType() );
462                             String stIn = "(unknown)", stOut = "(unknown)";
463                             switch( i ){
464                             case 0:
465                                 stIn = new String( getContent( (InputStream)HTMLTransferer.createTRInstance(i) ) );
466                                 stOut = new String( getContent((InputStream)im) );
467                                 bSuccess = stIn.equals(stOut);
468                                 break;
469                             case 1:
470                                 stIn = (String)HTMLTransferer.createTRInstance(i);
471                                 stOut = (String)im;
472                                 int head = stOut.indexOf("<HTML><BODY>");
473                                 if (head >= 0) {
474                                     stOut = stOut.substring(head + 12, stOut.length() - 14);
475                                 }
476                                 bSuccess = stIn.equals(stOut);
477                                 break;
478                             default:
479                                 bSuccess = HTMLTransferer.createTRInstance(i).equals(im);
480                                 break;
481                             };
482                             System.err.println("in :" + stIn);
483                             System.err.println("out:" + stOut);
484                        };
485                     } catch (Exception e) {
486                         System.err.println("Can't getTransferData: " + e);
487                     }
488                     if(!bSuccess)
489                         System.err.println("transferred DATA is different from initial DATA\n");
490                 } else {
491                     System.err.println("Flavor is not supported by transferable:\n");
492                     DataFlavor[] dfs = t.getTransferDataFlavors();
493                     int ii;
494                     for(ii = 0; ii < dfs.length; ++ii)
495                         System.err.println("Supported:" + dfs[ii] + "\n");
496                     dfs = HTMLTransferTest.HTMLFlavors;
497                     for(ii = 0; ii < dfs.length; ++ii)
498                         System.err.println("Accepted:" + dfs[ii] + "\n" );
499                 }
500             }
501             System.err.println( "----------------------------------------------------------");
502             notifyTransferSuccess(bSuccess);
503             System.err.println( "============================================================");
504             ++fi;
505         }
506         System.err.println("}CONSUMER: lost clipboard ownership");
507     }
508 
509 
notifyTransferSuccess(boolean status)510     void notifyTransferSuccess(boolean status) {
511         System.err.println(
512             "format "
513             + (status
514                 ? "passed"
515                 : "failed"
516             )
517             + "!!!"
518         );
519         setClipboardContents(
520             new HTMLSelection(
521                 HTMLTransferTest.SyncFlavor,
522                 status
523                     ? S_PASSED
524                     : S_FAILED
525             ),
526             this
527         );
528     }
529 
530 
main(String[] args)531     public static void main(String[] args) {
532         try {
533             System.err.println("{CONSUMER: start");
534             THTMLConsumer ic = new THTMLConsumer();
535             ic.setClipboardContents(
536                 new HTMLSelection(
537                     HTMLTransferTest.SyncFlavor,
538                     S_BEGIN_ANSWER
539                 ),
540                 ic
541             );
542             synchronized (LOCK) {
543                 LOCK.wait();
544             }
545             System.err.println("}CONSUMER: start");
546         } catch (Throwable e) {
547             e.printStackTrace();
548             System.exit(HTMLTransferTest.CODE_FAILURE);
549         }
550     }
551 
552 }
553 
554 
555 /**
556  * A <code>Transferable</code> which implements the capability required
557  * to transfer an <code>HTML</code>.
558  *
559  * This <code>Transferable</code> properly supports
560  * <code>HTMLTransferTest.HTMLFlavors</code>.
561  * and all equivalent flavors.
562  * No other <code>DataFlavor</code>s are supported.
563  *
564  * @see java.awt.datatransfer.HTMLTransferTest.HTMLFlavors
565  */
566 class HTMLSelection implements Transferable {
567     private DataFlavor m_flavor;
568     private Object m_data;
569 
570     /**
571      * Creates a <code>Transferable</code> capable of transferring
572      * the specified <code>String</code>.
573      */
HTMLSelection( DataFlavor flavor, Object data )574     public HTMLSelection(
575         DataFlavor flavor,
576         Object data
577     ){
578         m_flavor = flavor;
579         m_data = data;
580     }
581 
582     /**
583      * Returns an array of flavors in which this <code>Transferable</code>
584      * can provide the data. <code>DataFlavor.stringFlavor</code>
585      * is properly supported.
586      * Support for <code>DataFlavor.plainTextFlavor</code> is
587      * <b>deprecated</b>.
588      *
589      * @return an array of length one, whose element is <code>DataFlavor.
590      *         HTMLTransferTest.HTMLFlavors</code>
591      */
getTransferDataFlavors()592     public DataFlavor[] getTransferDataFlavors() {
593         // returning flavors itself would allow client code to modify
594         // our internal behavior
595         return new DataFlavor[]{ m_flavor } ;
596     }
597 
598     /**
599      * Returns whether the requested flavor is supported by this
600      * <code>Transferable</code>.
601      *
602      * @param flavor the requested flavor for the data
603      * @return true if <code>flavor</code> is equal to
604      *   <code>HTMLTransferTest.HTMLFlavors</code>;
605      *   false if <code>flavor</code>
606      *   is not one of the above flavors
607      * @throws NullPointerException if flavor is <code>null</code>
608      */
isDataFlavorSupported(DataFlavor flavor)609     public boolean isDataFlavorSupported(DataFlavor flavor) {
610         System.err.println("Have:" + flavor + " Can:" + m_flavor);
611         if(flavor.equals(m_flavor))
612             return true;
613         return false;
614     }
615 
616     /**
617      * Returns the <code>Transferable</code>'s data in the requested
618      * <code>DataFlavor</code> if possible. If the desired flavor is
619      * <code>HTMLTransferTest.HTMLFlavors</code>, or an equivalent flavor,
620      * the <code>HTML</code> representing the selection is
621      * returned.
622      *
623      * @param flavor the requested flavor for the data
624      * @return the data in the requested flavor, as outlined above
625      * @throws UnsupportedFlavorException if the requested data flavor is
626      *         not equivalent to <code>HTMLTransferTest.HTMLFlavors</code>
627      * @throws IOException if an IOException occurs while retrieving the data.
628      *         By default, <code>HTMLSelection</code> never throws
629      *         this exception, but a subclass may.
630      * @throws NullPointerException if flavor is <code>null</code>
631      */
getTransferData(DataFlavor flavor)632     public Object getTransferData(DataFlavor flavor)
633         throws UnsupportedFlavorException, IOException
634     {
635         if (flavor.equals(m_flavor)) {
636             return (Object)m_data;
637         } else {
638             throw new UnsupportedFlavorException(flavor);
639         }
640     }
641 
642 } // class HTMLSelection
643