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 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 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 170 class 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 188 class 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 248 abstract 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 342 class 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 432 class 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 */ 575 class 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 ****************************************************/ 661 class 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 696 class 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