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