1 /************************************************************************* 2 * 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * Copyright 2000, 2010 Oracle and/or its affiliates. 6 * 7 * OpenOffice.org - a multi-platform office productivity suite 8 * 9 * This file is part of OpenOffice.org. 10 * 11 * OpenOffice.org is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Lesser General Public License version 3 13 * only, as published by the Free Software Foundation. 14 * 15 * OpenOffice.org is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU Lesser General Public License version 3 for more details 19 * (a copy is included in the LICENSE file that accompanied this code). 20 * 21 * You should have received a copy of the GNU Lesser General Public License 22 * version 3 along with OpenOffice.org. If not, see 23 * <http://www.openoffice.org/license.html> 24 * for a copy of the LGPLv3 License. 25 * 26 ************************************************************************/ 27 28 package com.sun.star.comp.beans; 29 30 import java.awt.Container; 31 import java.io.File; 32 import java.util.Iterator; 33 import java.util.List; 34 import java.util.Vector; 35 36 import com.sun.star.lang.XMultiComponentFactory; 37 import com.sun.star.lang.XComponent; 38 import com.sun.star.lang.XEventListener; 39 import com.sun.star.connection.XConnection; 40 import com.sun.star.connection.XConnector; 41 import com.sun.star.bridge.XBridge; 42 import com.sun.star.bridge.XBridgeFactory; 43 import com.sun.star.beans.XPropertySet; 44 import com.sun.star.uno.XComponentContext; 45 import com.sun.star.uno.UnoRuntime; 46 import com.sun.star.uno.Exception; 47 import com.sun.star.lib.uno.helper.UnoUrl; 48 import com.sun.star.lib.util.NativeLibraryLoader; 49 50 /** 51 * This class reprecents a connection to the local office application. 52 * 53 * @since OOo 2.0.0 54 */ 55 public class LocalOfficeConnection 56 implements OfficeConnection 57 { 58 public static final String OFFICE_APP_NAME = "soffice"; 59 public static final String OFFICE_LIB_NAME = "officebean"; 60 public static final String OFFICE_ID_SUFFIX = "_Office"; 61 62 private static String mProgramPath; 63 64 private Process mProcess; 65 private ContainerFactory mContainerFactory; 66 private XComponentContext mContext; 67 private XBridge mBridge; 68 69 private String mURL; 70 private String mConnType; 71 private String mPipe; 72 private String mPort; 73 private String mProtocol; 74 private String mInitialObject; 75 76 private List mComponents = new Vector(); 77 78 private static long m_nBridgeCounter = 0; 79 //------------------------------------------------------------------------- 80 static 81 { 82 // preload shared libraries whichs import lips are linked to officebean 83 if ( System.getProperty( "os.name" ).startsWith( "Windows" ) ) 84 { 85 try 86 { 87 NativeLibraryLoader.loadLibrary(LocalOfficeConnection.class.getClassLoader(), "msvcr70"); 88 } 89 catch (Throwable e) 90 { 91 // loading twice would fail 92 System.err.println( "cannot find msvcr70" ); 93 } 94 95 try 96 { 97 NativeLibraryLoader.loadLibrary(LocalOfficeConnection.class.getClassLoader(), "msvcr71"); 98 } 99 catch (Throwable e) 100 { 101 // loading twice would fail 102 System.err.println( "cannot find msvcr71" ); 103 } 104 105 try 106 { 107 NativeLibraryLoader.loadLibrary(LocalOfficeConnection.class.getClassLoader(), "uwinapi"); 108 } 109 catch (Throwable e) 110 { 111 // loading twice would fail 112 System.err.println( "cannot find uwinapi" ); 113 } 114 115 try 116 { 117 NativeLibraryLoader.loadLibrary(LocalOfficeConnection.class.getClassLoader(), "jawt"); 118 } 119 catch (Throwable e) 120 { 121 // loading twice would fail 122 System.err.println( "cannot find jawt" ); 123 } 124 } 125 126 // load shared library for JNI code 127 NativeLibraryLoader.loadLibrary( LocalOfficeConnection.class.getClassLoader(), "officebean" ); 128 } 129 130 //------------------------------------------------------------------------- 131 // debugging method 132 private void dbgPrint( String aMessage ) 133 { 134 System.err.println( aMessage ); 135 } 136 137 /** 138 * Constructor. 139 * Sets up paths to the office application and native libraries if 140 * values are available in <code>OFFICE_PROP_FILE</code> in the user 141 * home directory.<br /> 142 * "com.sun.star.beans.path" - the office application directory;<br/> 143 * "com.sun.star.beans.libpath" - native libraries directory. 144 */ 145 public LocalOfficeConnection() 146 { 147 // init member vars 148 try 149 { 150 setUnoUrl( "uno:pipe,name=" + getPipeName() + ";urp;StarOffice.ServiceManager" ); 151 } 152 catch ( java.net.MalformedURLException e ) 153 {} 154 } 155 156 /** 157 * protected Constructor 158 * Initialise a LocalOfficeConnection with an already running office. 159 * This C'Tor is only used in complex tests at the moment. 160 * @param xContext 161 */ 162 protected LocalOfficeConnection(com.sun.star.uno.XComponentContext xContext) 163 { 164 this.mContext = xContext; 165 } 166 167 /** 168 * Sets a connection URL. 169 * This implementation accepts a UNO URL with following format:<br /> 170 * <pre> 171 * url := uno:localoffice[,<params>];urp;StarOffice.ServiceManager 172 * params := <path>[,<pipe>] 173 * path := path=<pathv> 174 * pipe := pipe=<pipev> 175 * pathv := platform_specific_path_to_the_local_office_distribution 176 * pipev := local_office_connection_pipe_name 177 * </pre> 178 * 179 * @param url This is UNO URL which discribes the type of a connection. 180 */ 181 public void setUnoUrl(String url) 182 throws java.net.MalformedURLException 183 { 184 mURL = null; 185 186 String prefix = "uno:localoffice"; 187 if ( url.startsWith(prefix) ) 188 parseUnoUrlWithOfficePath( url, prefix ); 189 else 190 { 191 try 192 { 193 UnoUrl aURL = UnoUrl.parseUnoUrl( url ); 194 mProgramPath = null; 195 mConnType = aURL.getConnection(); 196 mPipe = (String) aURL.getConnectionParameters().get( "pipe" ); 197 mPort = (String) aURL.getConnectionParameters().get( "port" ); 198 mProtocol = aURL.getProtocol(); 199 mInitialObject = aURL.getRootOid(); 200 } 201 catch ( com.sun.star.lang.IllegalArgumentException eIll ) 202 { 203 throw new java.net.MalformedURLException( 204 "Invalid UNO connection URL."); 205 } 206 } 207 mURL = url; 208 } 209 210 /** 211 * Sets an AWT container catory. 212 * 213 * @param containerFactory This is a application provided AWT container 214 * factory. 215 */ 216 public void setContainerFactory(ContainerFactory containerFactory) 217 { 218 mContainerFactory = containerFactory; 219 } 220 221 /** 222 * Retrives the UNO component context. 223 * Establishes a connection if necessary and initialises the 224 * UNO service manager if it has not already been initialised. 225 * This method can return <code>null</code> if it fails to connect 226 * to the office application. 227 * 228 * @return The office UNO component context. 229 */ 230 synchronized public XComponentContext getComponentContext() 231 { 232 if ( mContext == null ) 233 mContext = connect(); 234 return mContext; 235 } 236 237 /** 238 * Creates an office window. 239 * The window is either a sub-class of java.awt.Canvas (local) or 240 * java.awt.Container (RVP). 241 * 242 * @param container This is an AWT container. 243 * @return The office window instance. 244 */ 245 public OfficeWindow createOfficeWindow(Container container) 246 { 247 return new LocalOfficeWindow(this); 248 } 249 250 /** 251 * Closes the connection. 252 */ 253 public void dispose() 254 { 255 Iterator itr = mComponents.iterator(); 256 while (itr.hasNext() == true) { 257 // ignore runtime exceptions in dispose 258 try { ((XEventListener)itr.next()).disposing(null); } 259 catch ( RuntimeException aExc ) {} 260 } 261 mComponents.clear(); 262 263 //Terminate the bridge. It turned out that this is necessary for the bean 264 //to work properly when displayed in an applet within Internet Explorer. 265 //When navigating off the page which is showing the applet and then going 266 //back to it, then the Java remote bridge is damaged. That is the Java threads 267 //do not work properly anymore. Therefore when Applet.stop is called the connection 268 //to the office including the bridge needs to be terminated. 269 if (mBridge != null) 270 { 271 XComponent comp = (XComponent)UnoRuntime.queryInterface( 272 XComponent.class, mBridge); 273 if (comp != null) 274 comp.dispose(); 275 else 276 System.err.println("LocalOfficeConnection: could not dispose bridge!"); 277 278 mBridge = null; 279 } 280 281 mContainerFactory = null; 282 mContext = null; 283 } 284 285 /** 286 * Adds an event listener to the object. 287 * 288 * @param listener is a listener object. 289 */ 290 public void addEventListener(XEventListener listener) 291 { 292 mComponents.add(listener); 293 } 294 295 /** 296 * Removes an event listener from the listener list. 297 * 298 * @param listener is a listener object. 299 */ 300 public void removeEventListener(XEventListener listener) 301 { 302 mComponents.remove(listener); 303 } 304 305 /** 306 * Establishes the connection to the office. 307 */ 308 private XComponentContext connect() 309 { 310 try 311 { 312 // create default local component context 313 XComponentContext xLocalContext = 314 com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null); 315 316 // initial serviceManager 317 XMultiComponentFactory xLocalServiceManager = xLocalContext.getServiceManager(); 318 319 // try to connect to soffice 320 Object aInitialObject = null; 321 try 322 { 323 aInitialObject = resolve(xLocalContext, mURL); 324 } 325 catch( com.sun.star.connection.NoConnectException e ) 326 { 327 // launch soffice 328 OfficeService aSOffice = new OfficeService(); 329 aSOffice.startupService(); 330 331 // wait until soffice is started 332 long nMaxMillis = System.currentTimeMillis() + 1000*aSOffice.getStartupTime(); 333 while ( aInitialObject == null ) 334 { 335 try 336 { 337 // try to connect to soffice 338 Thread.currentThread().sleep( 500 ); 339 aInitialObject = resolve(xLocalContext, mURL); 340 } 341 catch( com.sun.star.connection.NoConnectException aEx ) 342 { 343 // soffice did not start in time 344 if ( System.currentTimeMillis() > nMaxMillis ) 345 throw aEx; 346 347 } 348 } 349 } 350 finally 351 { 352 } 353 354 // XComponentContext 355 if( null != aInitialObject ) 356 { 357 XPropertySet xPropertySet = (XPropertySet) 358 UnoRuntime.queryInterface( XPropertySet.class, aInitialObject); 359 Object xContext = xPropertySet.getPropertyValue("DefaultContext"); 360 XComponentContext xComponentContext = (XComponentContext) UnoRuntime.queryInterface( 361 XComponentContext.class, xContext); 362 return xComponentContext; 363 } 364 } 365 catch( com.sun.star.connection.NoConnectException e ) 366 { 367 System.out.println( "Couldn't connect to remote server" ); 368 System.out.println( e.getMessage() ); 369 } 370 catch( com.sun.star.connection.ConnectionSetupException e ) 371 { 372 System.out.println( "Couldn't access necessary local resource to establish the interprocess connection" ); 373 System.out.println( e.getMessage() ); 374 } 375 catch( com.sun.star.lang.IllegalArgumentException e ) 376 { 377 System.out.println( "uno-url is syntactical illegal ( " + mURL + " )" ); 378 System.out.println( e.getMessage() ); 379 } 380 catch( com.sun.star.uno.RuntimeException e ) 381 { 382 System.out.println( "--- RuntimeException:" ); 383 System.out.println( e.getMessage() ); 384 e.printStackTrace(); 385 System.out.println( "--- end." ); 386 throw e; 387 } 388 catch( java.lang.Exception e ) 389 { 390 System.out.println( "java.lang.Exception: " ); 391 System.out.println( e ); 392 e.printStackTrace(); 393 System.out.println( "--- end." ); 394 throw new com.sun.star.uno.RuntimeException( e.toString() ); 395 } 396 397 return null; 398 } 399 400 401 //The function is copied and adapted from the UrlResolver.resolve. 402 //We cannot use the URLResolver because we need access to the bridge which has 403 //to be disposed when Applet.stop is called. 404 private Object resolve(XComponentContext xLocalContext, String dcp) 405 throws com.sun.star.connection.NoConnectException, 406 com.sun.star.connection.ConnectionSetupException, 407 com.sun.star.lang.IllegalArgumentException 408 { 409 String conDcp = null; 410 String protDcp = null; 411 String rootOid = null; 412 413 if(dcp.indexOf(';') == -1) {// use old style 414 conDcp = dcp; 415 protDcp = "iiop"; 416 rootOid = "classic_uno"; 417 } 418 else { // new style 419 int index = dcp.indexOf(':'); 420 String url = dcp.substring(0, index).trim(); 421 dcp = dcp.substring(index + 1).trim(); 422 423 index = dcp.indexOf(';'); 424 conDcp = dcp.substring(0, index).trim(); 425 dcp = dcp.substring(index + 1).trim(); 426 427 index = dcp.indexOf(';'); 428 protDcp = dcp.substring(0, index).trim(); 429 dcp = dcp.substring(index + 1).trim(); 430 431 rootOid = dcp.trim().trim(); 432 } 433 434 Object rootObject = null; 435 XBridgeFactory xBridgeFactory= null; 436 437 XMultiComponentFactory xLocalServiceManager = xLocalContext.getServiceManager(); 438 try { 439 xBridgeFactory = (XBridgeFactory)UnoRuntime.queryInterface( 440 XBridgeFactory.class, 441 xLocalServiceManager.createInstanceWithContext( 442 "com.sun.star.bridge.BridgeFactory", xLocalContext)); 443 } catch (com.sun.star.uno.Exception e) { 444 throw new com.sun.star.uno.RuntimeException(e.getMessage()); 445 } 446 synchronized(this) { 447 if(mBridge == null) { 448 Object connector= null; 449 try { 450 connector = xLocalServiceManager.createInstanceWithContext( 451 "com.sun.star.connection.Connector", xLocalContext); 452 } catch (com.sun.star.uno.Exception e) { 453 throw new com.sun.star.uno.RuntimeException(e.getMessage()); 454 } 455 XConnector connector_xConnector = (XConnector)UnoRuntime.queryInterface(XConnector.class, connector); 456 // connect to the server 457 XConnection xConnection = connector_xConnector.connect(conDcp); 458 // create the bridge name. This should not be necessary if we pass an 459 //empty string as bridge name into createBridge. Then we should always get 460 //a new bridge. This does not work because of (i51323). Therefore we 461 //create unique bridge names for the current process. 462 String sBridgeName = "OOoBean_private_bridge_" + String.valueOf(m_nBridgeCounter++); 463 try { 464 mBridge = xBridgeFactory.createBridge(sBridgeName, protDcp, xConnection, null); 465 } catch (com.sun.star.bridge.BridgeExistsException e) { 466 throw new com.sun.star.uno.RuntimeException(e.getMessage()); 467 } 468 } 469 rootObject = mBridge.getInstance(rootOid); 470 return rootObject; 471 } 472 } 473 474 475 /** 476 * Retrives a path to the office program folder. 477 * 478 * @return The path to the office program folder. 479 */ 480 static private String getProgramPath() 481 { 482 if (mProgramPath == null) 483 { 484 // determine name of executable soffice 485 String aExec = OFFICE_APP_NAME; // default for UNIX 486 String aOS = System.getProperty("os.name"); 487 488 // running on Windows? 489 if (aOS.startsWith("Windows")) 490 aExec = OFFICE_APP_NAME + ".exe"; 491 492 // add other non-UNIX operating systems here 493 // ... 494 495 // find soffice executable relative to this class's class loader: 496 File path = NativeLibraryLoader.getResource( 497 LocalOfficeConnection.class.getClassLoader(), aExec); 498 if (path != null) 499 mProgramPath = path.getParent(); 500 501 // default is "" 502 if ( mProgramPath == null ) 503 mProgramPath = ""; 504 } 505 return mProgramPath; 506 } 507 508 /** 509 * Parses a connection URL. 510 * This method accepts a UNO URL with following format:<br /> 511 * <pre> 512 * url := uno:localoffice[,<params>];urp;StarOffice.NamingService 513 * params := <path>[,<pipe>] 514 * path := path=<pathv> 515 * pipe := pipe=<pipev> 516 * pathv := platform_specific_path_to_the_local_office_distribution 517 * pipev := local_office_connection_pipe_name 518 * </pre> 519 * 520 * <h4>Examples</h4> 521 * <ul> 522 * <li>"uno:localoffice,pipe=xyz_Office,path=/opt/openoffice11/program;urp;StarOffice.ServiceManager"; 523 * <li>"uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager"; 524 * </ul> 525 * 526 * @param url This is UNO URL which describes the type of a connection. 527 * @exception java.net.MalformedURLException when inappropreate URL was 528 * provided. 529 */ 530 private void parseUnoUrlWithOfficePath(String url, String prefix) 531 throws java.net.MalformedURLException 532 { 533 // Extruct parameters. 534 int idx = url.indexOf(";urp;StarOffice.NamingService"); 535 if (idx < 0) 536 throw new java.net.MalformedURLException( 537 "Invalid UNO connection URL."); 538 String params = url.substring(prefix.length(), idx + 1); 539 540 // Parse parameters. 541 String name = null; 542 String path = null; 543 String pipe = null; 544 char ch; 545 int state = 0; 546 StringBuffer buffer = new StringBuffer(); 547 for(idx = 0; idx < params.length(); idx += 1) { 548 ch = params.charAt(idx); 549 switch (state) { 550 case 0: // initial state 551 switch(ch) { 552 case ',': 553 buffer.delete(0, buffer.length()); 554 state = 1; 555 break; 556 557 case ';': 558 state = 7; 559 break; 560 561 default: 562 buffer.delete(0, buffer.length()); 563 buffer.append(ch); 564 state = 1; 565 break; 566 } 567 break; 568 569 case 1: // parameter name 570 switch(ch) { 571 case ' ': 572 case '=': 573 name = buffer.toString(); 574 state = (ch == ' ')? 2: 3; 575 break; 576 577 case ',': 578 case ';': 579 state = -6; // error: invalid name 580 break; 581 582 default: 583 buffer.append(ch); 584 break; 585 } 586 break; 587 588 case 2: // equal between the name and the value 589 switch(ch) { 590 case '=': 591 state = 3; 592 break; 593 594 case ' ': 595 break; 596 597 default: 598 state = -1; // error: missing '=' 599 break; 600 } 601 break; 602 603 case 3: // value leading spaces 604 switch(ch) { 605 case ' ': 606 break; 607 608 default: 609 buffer.delete(0, buffer.length()); 610 buffer.append(ch); 611 state = 4; 612 break; 613 } 614 break; 615 616 case 4: // value 617 switch(ch) { 618 case ' ': 619 case ',': 620 case ';': 621 idx -= 1; // put back the last read character 622 state = 5; 623 if (name.equals("path")) { 624 if (path == null) 625 path = buffer.toString(); 626 else 627 state = -3; // error: more then one 'path' 628 } else if (name.equals("pipe")) { 629 if (pipe == null) 630 pipe = buffer.toString(); 631 else 632 state = -4; // error: more then one 'pipe' 633 } else 634 state = -2; // error: unknown parameter 635 buffer.delete(0, buffer.length()); 636 break; 637 638 default: 639 buffer.append(ch); 640 break; 641 } 642 break; 643 644 case 5: // a delimeter after the value 645 switch(ch) { 646 case ' ': 647 break; 648 649 case ',': 650 state = 6; 651 break; 652 653 case ';': 654 state = 7; 655 break; 656 657 default: 658 state = -5; // error: ' ' inside the value 659 break; 660 } 661 break; 662 663 case 6: // leading spaces before next parameter name 664 switch(ch) { 665 case ' ': 666 break; 667 668 default: 669 buffer.delete(0, buffer.length()); 670 buffer.append(ch); 671 state = 1; 672 break; 673 } 674 break; 675 676 default: 677 throw new java.net.MalformedURLException( 678 "Invalid UNO connection URL."); 679 } 680 } 681 if (state != 7) 682 throw new java.net.MalformedURLException( 683 "Invalid UNO connection URL."); 684 685 // Set up the connection parameters. 686 if (path != null) 687 mProgramPath = path; 688 if (pipe != null) 689 mPipe = pipe; 690 } 691 692 /* replaces each substring aSearch in aString by aReplace. 693 694 StringBuffer.replaceAll() is not avaialable in Java 1.3.x. 695 */ 696 private static String replaceAll(String aString, String aSearch, String aReplace ) 697 { 698 StringBuffer aBuffer = new StringBuffer(aString); 699 700 int nPos = aString.length(); 701 int nOfs = aSearch.length(); 702 703 while ( ( nPos = aString.lastIndexOf( aSearch, nPos - 1 ) ) > -1 ) 704 aBuffer.replace( nPos, nPos+nOfs, aReplace ); 705 706 return aBuffer.toString(); 707 } 708 709 710 /** creates a unique pipe name. 711 */ 712 static String getPipeName() 713 { 714 // turn user name into a URL and file system safe name (% chars will not work) 715 String aPipeName = System.getProperty("user.name") + OFFICE_ID_SUFFIX; 716 aPipeName = replaceAll( aPipeName, "_", "%B7" ); 717 return replaceAll( replaceAll( java.net.URLEncoder.encode(aPipeName), "+", "%20" ), "%", "_" ); 718 } 719 720 /** 721 * @para This is an implementation of the native office service. 722 */ 723 private class OfficeService 724 implements NativeService 725 { 726 /** 727 * Retrive the office service identifier. 728 * 729 * @return The identifier of the office service. 730 */ 731 public String getIdentifier() 732 { 733 if ( mPipe == null) 734 return getPipeName(); 735 else 736 return mPipe; 737 } 738 739 /** 740 * Starts the office process. 741 */ 742 public void startupService() 743 throws java.io.IOException 744 { 745 int nSizeCmdArray = 4; 746 String sOption = null; 747 //examine if user specified command-line options in system properties. 748 //We may offer later a more sophisticated way of providing options if 749 //the need arises. Currently this is intended to ease the pain during 750 //development with pre-release builds of OOo where one wants to start 751 //OOo with the -norestore options. The value of the property is simple 752 //passed on to the Runtime.exec call. 753 try { 754 sOption = System.getProperty("com.sun.star.officebean.Options"); 755 if (sOption != null) 756 nSizeCmdArray ++; 757 } catch (java.lang.SecurityException e) 758 { 759 e.printStackTrace(); 760 } 761 // create call with arguments 762 String[] cmdArray = new String[nSizeCmdArray]; 763 764 // read UNO_PATH environment variable to get path to soffice binary 765 String unoPath = System.getenv("UNO_PATH"); 766 if (unoPath == null) 767 throw new java.io.IOException( "UNO_PATH environment variable is not set (required system path to the office program directory)" ); 768 769 // cmdArray[0] = (new File(getProgramPath(), OFFICE_APP_NAME)).getPath(); 770 cmdArray[0] = (new File(unoPath, OFFICE_APP_NAME)).getPath(); 771 cmdArray[1] = "-nologo"; 772 cmdArray[2] = "-nodefault"; 773 if ( mConnType.equals( "pipe" ) ) 774 cmdArray[3] = "-accept=pipe,name=" + getIdentifier() + ";" + 775 mProtocol + ";" + mInitialObject; 776 else if ( mConnType.equals( "socket" ) ) 777 cmdArray[3] = "-accept=socket,port=" + mPort + ";urp"; 778 else 779 throw new java.io.IOException( "not connection specified" ); 780 781 if (sOption != null) 782 cmdArray[4] = sOption; 783 784 // start process 785 mProcess = Runtime.getRuntime().exec(cmdArray); 786 if ( mProcess == null ) 787 throw new RuntimeException( "cannot start soffice: " + cmdArray ); 788 new StreamProcessor(mProcess.getInputStream(), System.out); 789 new StreamProcessor(mProcess.getErrorStream(), System.err); 790 } 791 792 /** 793 * Retrives the ammount of time to wait for the startup. 794 * 795 * @return The ammount of time to wait in seconds(?). 796 */ 797 public int getStartupTime() 798 { 799 return 60; 800 } 801 } 802 803 804 805 class StreamProcessor extends Thread 806 { 807 java.io.InputStream m_in; 808 java.io.PrintStream m_print; 809 810 public StreamProcessor(final java.io.InputStream in, final java.io.PrintStream out) 811 { 812 m_in = in; 813 m_print = out; 814 start(); 815 } 816 817 public void run() { 818 java.io.BufferedReader r = new java.io.BufferedReader( 819 new java.io.InputStreamReader(m_in) ); 820 try { 821 for ( ; ; ) { 822 String s = r.readLine(); 823 if ( s == null ) { 824 break; 825 } 826 m_print.println(s); 827 } 828 } catch ( java.io.IOException e ) { 829 e.printStackTrace( System.err ); 830 } 831 } 832 } 833 834 } 835