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