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