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 
  62     public static void main(final String[] args) {
  63         HTMLTransferTest app = new HTMLTransferTest();
  64         app.init();
  65         app.start();
  66     }
  67 
  68     public void init() {
  69         initImpl();
  70 
  71     } // init()
  72 
  73     private void initImpl() {
  74         imPr = new THTMLProducer();
  75         imPr.begin();
  76     }
  77 
  78 
  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 
 164     public SyncMessage(String sync) {
 165         this.msg = sync;
 166     }
 167 
 168     @Override
 169     public boolean equals(Object obj) {
 170         return this.msg.equals(((SyncMessage)obj).msg);
 171     }
 172 
 173     @Override
 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 
 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      */
 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 
 251     HTMLTransferer() {
 252         m_clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
 253     }
 254 
 255 
 256     abstract void notifyTransferSuccess(boolean status);
 257 
 258 
 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 
 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 
 287     static void Dump(byte[] b){
 288         System.err.println( new String(b) );
 289     };
 290 
 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 
 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 
 339     THTMLProducer() {
 340         passedArray = new boolean[HTMLTransferTest.HTMLFlavors.length];
 341     }
 342 
 343     void begin() {
 344         setClipboardContents(
 345             new HTMLSelection(
 346                 HTMLTransferTest.SyncFlavor,
 347                 S_BEGIN
 348             ),
 349             this
 350         );
 351     }
 352 
 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 
 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 
 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 
 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 
 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      */
 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      */
 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      */
 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      */
 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