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