Runtime.java revision f33eae7e84eb6d3b0f4e86b59605bb3de73009f3
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17/* 18 * Copyright (C) 2008 The Android Open Source Project 19 * 20 * Licensed under the Apache License, Version 2.0 (the "License"); 21 * you may not use this file except in compliance with the License. 22 * You may obtain a copy of the License at 23 * 24 * http://www.apache.org/licenses/LICENSE-2.0 25 * 26 * Unless required by applicable law or agreed to in writing, software 27 * distributed under the License is distributed on an "AS IS" BASIS, 28 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 * See the License for the specific language governing permissions and 30 * limitations under the License. 31 */ 32 33package java.lang; 34 35import java.io.ByteArrayOutputStream; 36import java.io.File; 37import java.io.IOException; 38import java.io.InputStream; 39import java.io.OutputStream; 40import java.io.OutputStreamWriter; 41import java.io.PipedInputStream; 42import java.io.PipedOutputStream; 43import java.io.Reader; 44import java.io.InputStreamReader; 45import java.io.UnsupportedEncodingException; 46import java.io.Writer; 47 48import java.util.StringTokenizer; 49import java.util.List; 50import java.util.ArrayList; 51 52import dalvik.system.VMDebug; 53import dalvik.system.VMStack; 54 55/** 56 * Allows Java applications to interface with the environment in which they are 57 * running. Applications can not create an instance of this class, but they can 58 * get a singleton instance by invoking {@link #getRuntime()}. 59 * 60 * @see System 61 */ 62public class Runtime { 63 64 /** 65 * Holds the Singleton global instance of Runtime. 66 */ 67 private static final Runtime mRuntime = new Runtime(); 68 69 /** 70 * Holds the library paths, used for native library lookup. 71 */ 72 private final String[] mLibPaths; 73 74 /** 75 * Holds the list of threads to run when the VM terminates 76 */ 77 private List<Thread> shutdownHooks = new ArrayList<Thread>(); 78 79 /** 80 * Reflects whether finalization should be run for all objects 81 * when the VM terminates. 82 */ 83 private static boolean finalizeOnExit; 84 85 /** 86 * Reflects whether we are already shutting down the VM. 87 */ 88 private boolean shuttingDown; 89 90 /** 91 * Reflects whether we are tracing method calls. 92 */ 93 private boolean tracingMethods; 94 95 /** 96 * Prevent this class from being instantiated. 97 */ 98 private Runtime(){ 99 String pathList = System.getProperty("java.library.path", "."); 100 String pathSep = System.getProperty("path.separator", ":"); 101 String fileSep = System.getProperty("file.separator", "/"); 102 103 mLibPaths = pathList.split(pathSep); 104 105 int i; 106 107 if (false) 108 System.out.println("Runtime paths:"); 109 110 // Add a '/' to the end so we don't have to do the property lookup 111 // and concatenation later. 112 for (i = 0; i < mLibPaths.length; i++) { 113 if (!mLibPaths[i].endsWith(fileSep)) 114 mLibPaths[i] += fileSep; 115 if (false) 116 System.out.println(" " + mLibPaths[i]); 117 } 118 } 119 120 /** 121 * Executes the specified command and its arguments in a separate native 122 * process. The new process inherits the environment of the caller. Calling 123 * this method is equivalent to calling {@code exec(progArray, null, null)}. 124 * 125 * @param progArray 126 * the array containing the program to execute as well as any 127 * arguments to the program. 128 * @return the new {@code Process} object that represents the native 129 * process. 130 * @throws IOException 131 * if the requested program can not be executed. 132 * @throws SecurityException 133 * if the current {@code SecurityManager} disallows program 134 * execution. 135 * @see SecurityManager#checkExec 136 */ 137 public Process exec(String[] progArray) throws java.io.IOException { 138 return exec(progArray, null, null); 139 } 140 141 /** 142 * Executes the specified command and its arguments in a separate native 143 * process. The new process uses the environment provided in {@code envp}. 144 * Calling this method is equivalent to calling 145 * {@code exec(progArray, envp, null)}. 146 * 147 * @param progArray 148 * the array containing the program to execute as well as any 149 * arguments to the program. 150 * @param envp 151 * the array containing the environment to start the new process 152 * in. 153 * @return the new {@code Process} object that represents the native 154 * process. 155 * @throws IOException 156 * if the requested program can not be executed. 157 * @throws SecurityException 158 * if the current {@code SecurityManager} disallows program 159 * execution. 160 * @see SecurityManager#checkExec 161 */ 162 public Process exec(String[] progArray, String[] envp) throws java.io.IOException { 163 return exec(progArray, envp, null); 164 } 165 166 /** 167 * Executes the specified command and its arguments in a separate native 168 * process. The new process uses the environment provided in {@code envp} 169 * and the working directory specified by {@code directory}. 170 * 171 * @param progArray 172 * the array containing the program to execute as well as any 173 * arguments to the program. 174 * @param envp 175 * the array containing the environment to start the new process 176 * in. 177 * @param directory 178 * the directory in which to execute the program. If {@code null}, 179 * execute if in the same directory as the parent process. 180 * @return the new {@code Process} object that represents the native 181 * process. 182 * @throws IOException 183 * if the requested program can not be executed. 184 * @throws SecurityException 185 * if the current {@code SecurityManager} disallows program 186 * execution. 187 * @see SecurityManager#checkExec 188 */ 189 public Process exec(String[] progArray, String[] envp, File directory) throws IOException { 190 // BEGIN android-changed: push responsibility for argument checking into ProcessManager 191 return ProcessManager.getInstance().exec(progArray, envp, directory, false); 192 // END android-changed 193 } 194 195 /** 196 * Executes the specified program in a separate native process. The new 197 * process inherits the environment of the caller. Calling this method is 198 * equivalent to calling {@code exec(prog, null, null)}. 199 * 200 * @param prog 201 * the name of the program to execute. 202 * @return the new {@code Process} object that represents the native 203 * process. 204 * @throws IOException 205 * if the requested program can not be executed. 206 * @throws SecurityException 207 * if the current {@code SecurityManager} disallows program 208 * execution. 209 * @see SecurityManager#checkExec 210 */ 211 public Process exec(String prog) throws java.io.IOException { 212 return exec(prog, null, null); 213 } 214 215 /** 216 * Executes the specified program in a separate native process. The new 217 * process uses the environment provided in {@code envp}. Calling this 218 * method is equivalent to calling {@code exec(prog, envp, null)}. 219 * 220 * @param prog 221 * the name of the program to execute. 222 * @param envp 223 * the array containing the environment to start the new process 224 * in. 225 * @return the new {@code Process} object that represents the native 226 * process. 227 * @throws IOException 228 * if the requested program can not be executed. 229 * @throws SecurityException 230 * if the current {@code SecurityManager} disallows program 231 * execution. 232 * @see SecurityManager#checkExec 233 */ 234 public Process exec(String prog, String[] envp) throws java.io.IOException { 235 return exec(prog, envp, null); 236 } 237 238 /** 239 * Executes the specified program in a separate native process. The new 240 * process uses the environment provided in {@code envp} and the working 241 * directory specified by {@code directory}. 242 * 243 * @param prog 244 * the name of the program to execute. 245 * @param envp 246 * the array containing the environment to start the new process 247 * in. 248 * @param directory 249 * the directory in which to execute the program. If {@code null}, 250 * execute if in the same directory as the parent process. 251 * @return the new {@code Process} object that represents the native 252 * process. 253 * @throws IOException 254 * if the requested program can not be executed. 255 * @throws SecurityException 256 * if the current {@code SecurityManager} disallows program 257 * execution. 258 * @see SecurityManager#checkExec 259 */ 260 public Process exec(String prog, String[] envp, File directory) throws java.io.IOException { 261 // Sanity checks 262 if (prog == null) { 263 throw new NullPointerException(); 264 } else if (prog.length() == 0) { 265 throw new IllegalArgumentException(); 266 } 267 268 // Break down into tokens, as described in Java docs 269 StringTokenizer tokenizer = new StringTokenizer(prog); 270 int length = tokenizer.countTokens(); 271 String[] progArray = new String[length]; 272 for (int i = 0; i < length; i++) { 273 progArray[i] = tokenizer.nextToken(); 274 } 275 276 // Delegate 277 return exec(progArray, envp, directory); 278 } 279 280 /** 281 * Causes the virtual machine to stop running and the program to exit. If 282 * {@link #runFinalizersOnExit(boolean)} has been previously invoked with a 283 * {@code true} argument, then all objects will be properly 284 * garbage-collected and finalized first. 285 * 286 * @param code 287 * the return code. By convention, non-zero return codes indicate 288 * abnormal terminations. 289 * @throws SecurityException 290 * if the current {@code SecurityManager} does not allow the 291 * running thread to terminate the virtual machine. 292 * @see SecurityManager#checkExit 293 */ 294 public void exit(int code) { 295 // Security checks 296 SecurityManager smgr = System.getSecurityManager(); 297 if (smgr != null) { 298 smgr.checkExit(code); 299 } 300 301 // Make sure we don't try this several times 302 synchronized(this) { 303 if (!shuttingDown) { 304 shuttingDown = true; 305 306 Thread[] hooks; 307 synchronized (shutdownHooks) { 308 // create a copy of the hooks 309 hooks = new Thread[shutdownHooks.size()]; 310 shutdownHooks.toArray(hooks); 311 } 312 313 // Start all shutdown hooks concurrently 314 for (int i = 0; i < hooks.length; i++) { 315 hooks[i].start(); 316 } 317 318 // Wait for all shutdown hooks to finish 319 for (Thread hook : hooks) { 320 try { 321 hook.join(); 322 } catch (InterruptedException ex) { 323 // Ignore, since we are at VM shutdown. 324 } 325 } 326 327 // Ensure finalization on exit, if requested 328 if (finalizeOnExit) { 329 runFinalization(true); 330 } 331 332 // Get out of here finally... 333 nativeExit(code, true); 334 } 335 } 336 } 337 338 /** 339 * Returns the amount of free memory resources which are available to the 340 * running program. 341 * 342 * @return the approximate amount of free memory, measured in bytes. 343 */ 344 public native long freeMemory(); 345 346 /** 347 * Indicates to the virtual machine that it would be a good time to run the 348 * garbage collector. Note that this is a hint only. There is no guarantee 349 * that the garbage collector will actually be run. 350 */ 351 public native void gc(); 352 353 /** 354 * Returns the single {@code Runtime} instance. 355 * 356 * @return the {@code Runtime} object for the current application. 357 */ 358 public static Runtime getRuntime() { 359 return mRuntime; 360 } 361 362 /** 363 * Loads and links the dynamic library that is identified through the 364 * specified path. This method is similar to {@link #loadLibrary(String)}, 365 * but it accepts a full path specification whereas {@code loadLibrary} just 366 * accepts the name of the library to load. 367 * 368 * @param pathName 369 * the absolute (platform dependent) path to the library to load. 370 * @throws UnsatisfiedLinkError 371 * if the library can not be loaded. 372 * @throws SecurityException 373 * if the current {@code SecurityManager} does not allow to load 374 * the library. 375 * @see SecurityManager#checkLink 376 */ 377 public void load(String pathName) { 378 // Security checks 379 SecurityManager smgr = System.getSecurityManager(); 380 if (smgr != null) { 381 smgr.checkLink(pathName); 382 } 383 384 load(pathName, VMStack.getCallingClassLoader()); 385 } 386 387 /* 388 * Loads and links a library without security checks. 389 */ 390 void load(String filename, ClassLoader loader) { 391 if (filename == null) { 392 throw new NullPointerException("library path was null."); 393 } 394 if (!nativeLoad(filename, loader)) { 395 throw new UnsatisfiedLinkError( 396 "Library " + filename + " not found"); 397 } 398 } 399 400 /** 401 * Loads and links the library with the specified name. The mapping of the 402 * specified library name to the full path for loading the library is 403 * implementation-dependent. 404 * 405 * @param libName 406 * the name of the library to load. 407 * @throws UnsatisfiedLinkError 408 * if the library can not be loaded. 409 * @throws SecurityException 410 * if the current {@code SecurityManager} does not allow to load 411 * the library. 412 * @see SecurityManager#checkLink 413 */ 414 public void loadLibrary(String libName) { 415 // Security checks 416 SecurityManager smgr = System.getSecurityManager(); 417 if (smgr != null) { 418 smgr.checkLink(libName); 419 } 420 421 loadLibrary(libName, VMStack.getCallingClassLoader()); 422 } 423 424 /* 425 * Loads and links a library without security checks. 426 */ 427 void loadLibrary(String libname, ClassLoader loader) { 428 String filename; 429 int i; 430 431 if (loader != null) { 432 filename = loader.findLibrary(libname); 433 if (filename != null && nativeLoad(filename, loader)) 434 return; 435 // else fall through to exception 436 } else { 437 filename = System.mapLibraryName(libname); 438 for (i = 0; i < mLibPaths.length; i++) { 439 if (false) 440 System.out.println("Trying " + mLibPaths[i] + filename); 441 if (nativeLoad(mLibPaths[i] + filename, loader)) 442 return; 443 } 444 } 445 446 throw new UnsatisfiedLinkError("Library " + libname + " not found"); 447 } 448 449 private static native void nativeExit(int code, boolean isExit); 450 451 private static native boolean nativeLoad(String filename, 452 ClassLoader loader); 453 454 /** 455 * Requests proper finalization for all Objects on the heap. 456 * 457 * @param forced Decides whether the VM really needs to do this (true) 458 * or if this is just a suggestion that can safely be ignored 459 * (false). 460 */ 461 private native void runFinalization(boolean forced); 462 463 /** 464 * Provides a hint to the virtual machine that it would be useful to attempt 465 * to perform any outstanding object finalizations. 466 * 467 */ 468 public void runFinalization() { 469 runFinalization(false); 470 } 471 472 /** 473 * Sets the flag that indicates whether all objects are finalized when the 474 * virtual machine is about to exit. Note that all finalization which occurs 475 * when the system is exiting is performed after all running threads have 476 * been terminated. 477 * 478 * @param run 479 * {@code true} to enable finalization on exit, {@code false} to 480 * disable it. 481 * @deprecated This method is unsafe. 482 */ 483 @Deprecated 484 public static void runFinalizersOnExit(boolean run) { 485 SecurityManager smgr = System.getSecurityManager(); 486 if (smgr != null) { 487 smgr.checkExit(0); 488 } 489 finalizeOnExit = run; 490 } 491 492 /** 493 * Returns the total amount of memory which is available to the running 494 * program. 495 * 496 * @return the total amount of memory, measured in bytes. 497 */ 498 public native long totalMemory(); 499 500 /** 501 * Switches the output of debug information for instructions on or off. 502 * For the Android 1.0 reference implementation, this method does nothing. 503 * 504 * @param enable 505 * {@code true} to switch tracing on, {@code false} to switch it 506 * off. 507 */ 508 public void traceInstructions(boolean enable) { 509 // TODO(Google) Provide some implementation for this. 510 return; 511 } 512 513 /** 514 * Switches the output of debug information for methods on or off. 515 * 516 * @param enable 517 * {@code true} to switch tracing on, {@code false} to switch it 518 * off. 519 */ 520 public void traceMethodCalls(boolean enable) { 521 if (enable != tracingMethods) { 522 if (enable) { 523 VMDebug.startMethodTracing(); 524 } else { 525 VMDebug.stopMethodTracing(); 526 } 527 tracingMethods = enable; 528 } 529 } 530 531 /** 532 * Returns the localized version of the specified input stream. The input 533 * stream that is returned automatically converts all characters from the 534 * local character set to Unicode after reading them from the underlying 535 * stream. 536 * 537 * @param stream 538 * the input stream to localize. 539 * @return the localized input stream. 540 * @deprecated Use {@link InputStreamReader}. 541 */ 542 @Deprecated 543 public InputStream getLocalizedInputStream(InputStream stream) { 544 if (System.getProperty("file.encoding", "UTF-8").equals("UTF-8")) { 545 return stream; 546 } 547 return new ReaderInputStream(stream); 548 } 549 550 /** 551 * Returns the localized version of the specified output stream. The output 552 * stream that is returned automatically converts all characters from 553 * Unicode to the local character set before writing them to the underlying 554 * stream. 555 * 556 * @param stream 557 * the output stream to localize. 558 * @return the localized output stream. 559 * @deprecated Use {@link OutputStreamWriter}. 560 */ 561 @Deprecated 562 public OutputStream getLocalizedOutputStream(OutputStream stream) { 563 if (System.getProperty("file.encoding", "UTF-8").equals("UTF-8")) { 564 return stream; 565 } 566 return new WriterOutputStream(stream ); 567 } 568 569 /** 570 * Registers a virtual-machine shutdown hook. A shutdown hook is a 571 * {@code Thread} that is ready to run, but has not yet been started. All 572 * registered shutdown hooks will be executed once the virtual machine shuts 573 * down properly. A proper shutdown happens when either the 574 * {@link #exit(int)} method is called or the surrounding system decides to 575 * terminate the application, for example in response to a {@code CTRL-C} or 576 * a system-wide shutdown. A termination of the virtual machine due to the 577 * {@link #halt(int)} method, an {@link Error} or a {@code SIGKILL}, in 578 * contrast, is not considered a proper shutdown. In these cases the 579 * shutdown hooks will not be run. 580 * <p> 581 * Shutdown hooks are run concurrently and in an unspecified order. Hooks 582 * failing due to an unhandled exception are not a problem, but the stack 583 * trace might be printed to the console. Once initiated, the whole shutdown 584 * process can only be terminated by calling {@code halt()}. 585 * <p> 586 * If {@link #runFinalizersOnExit(boolean)} has been called with a {@code 587 * true} argument, garbage collection and finalization will take place after 588 * all hooks are either finished or have failed. Then the virtual machine 589 * terminates. 590 * <p> 591 * It is recommended that shutdown hooks do not do any time-consuming 592 * activities, in order to not hold up the shutdown process longer than 593 * necessary. 594 * 595 * @param hook 596 * the shutdown hook to register. 597 * @throws IllegalArgumentException 598 * if the hook has already been started or if it has already 599 * been registered. 600 * @throws IllegalStateException 601 * if the virtual machine is already shutting down. 602 * @throws SecurityException 603 * if a SecurityManager is registered and the calling code 604 * doesn't have the RuntimePermission("shutdownHooks"). 605 */ 606 public void addShutdownHook(Thread hook) { 607 // Sanity checks 608 if (hook == null) { 609 throw new NullPointerException("Hook may not be null."); 610 } 611 612 if (shuttingDown) { 613 throw new IllegalStateException("VM already shutting down"); 614 } 615 616 if (hook.hasBeenStarted) { 617 throw new IllegalArgumentException("Hook has already been started"); 618 } 619 620 SecurityManager sm = System.getSecurityManager(); 621 if (sm != null) { 622 sm.checkPermission(new RuntimePermission("shutdownHooks")); 623 } 624 625 synchronized (shutdownHooks) { 626 if (shutdownHooks.contains(hook)) { 627 throw new IllegalArgumentException("Hook already registered."); 628 } 629 630 shutdownHooks.add(hook); 631 } 632 } 633 634 /** 635 * Unregisters a previously registered virtual machine shutdown hook. 636 * 637 * @param hook 638 * the shutdown hook to remove. 639 * @return {@code true} if the hook has been removed successfully; {@code 640 * false} otherwise. 641 * @throws IllegalStateException 642 * if the virtual machine is already shutting down. 643 * @throws SecurityException 644 * if a SecurityManager is registered and the calling code 645 * doesn't have the RuntimePermission("shutdownHooks"). 646 */ 647 public boolean removeShutdownHook(Thread hook) { 648 // Sanity checks 649 if (hook == null) { 650 throw new NullPointerException("Hook may not be null."); 651 } 652 653 if (shuttingDown) { 654 throw new IllegalStateException("VM already shutting down"); 655 } 656 657 SecurityManager sm = System.getSecurityManager(); 658 if (sm != null) { 659 sm.checkPermission(new RuntimePermission("shutdownHooks")); 660 } 661 662 synchronized (shutdownHooks) { 663 return shutdownHooks.remove(hook); 664 } 665 } 666 667 /** 668 * Causes the virtual machine to stop running, and the program to exit. 669 * Neither shutdown hooks nor finalizers are run before. 670 * 671 * @param code 672 * the return code. By convention, non-zero return codes indicate 673 * abnormal terminations. 674 * @throws SecurityException 675 * if the current {@code SecurityManager} does not allow the 676 * running thread to terminate the virtual machine. 677 * @see SecurityManager#checkExit 678 * @see #addShutdownHook(Thread) 679 * @see #removeShutdownHook(Thread) 680 * @see #runFinalizersOnExit(boolean) 681 */ 682 public void halt(int code) { 683 // Security checks 684 SecurityManager smgr = System.getSecurityManager(); 685 if (smgr != null) { 686 smgr.checkExit(code); 687 } 688 689 // Get out of here... 690 nativeExit(code, false); 691 } 692 693 /** 694 * Returns the number of processors available to the virtual machine. The 695 * Android reference implementation (currently) always returns 1. 696 * 697 * @return the number of available processors, at least 1. 698 */ 699 public int availableProcessors() { 700 return 1; 701 } 702 703 /** 704 * Returns the maximum amount of memory that may be used by the virtual 705 * machine, or {@code Long.MAX_VALUE} if there is no such limit. 706 * 707 * @return the maximum amount of memory that the virtual machine will try to 708 * allocate, measured in bytes. 709 */ 710 public native long maxMemory(); 711 712} 713 714/* 715 * Internal helper class for creating a localized InputStream. A reader 716 * wrapped in an InputStream. 717 */ 718class ReaderInputStream extends InputStream { 719 720 private Reader reader; 721 722 private Writer writer; 723 724 ByteArrayOutputStream out = new ByteArrayOutputStream(256); 725 726 private byte[] bytes; 727 728 private int nextByte; 729 730 private int numBytes; 731 732 String encoding = System.getProperty("file.encoding", "UTF-8"); 733 734 public ReaderInputStream(InputStream stream) { 735 try { 736 reader = new InputStreamReader(stream, "UTF-8"); 737 writer = new OutputStreamWriter(out, encoding); 738 } catch (UnsupportedEncodingException e) { 739 // Should never happen, since UTF-8 and platform encoding must be 740 // supported. 741 throw new RuntimeException(e); 742 } 743 } 744 745 @Override 746 public int read() throws IOException { 747 if (nextByte >= numBytes) { 748 readBuffer(); 749 } 750 751 return (numBytes < 0) ? -1 : bytes[nextByte++]; 752 } 753 754 private void readBuffer() throws IOException { 755 char[] chars = new char[128]; 756 int read = reader.read(chars); 757 if (read < 0) { 758 numBytes = read; 759 return; 760 } 761 762 writer.write(chars, 0, read); 763 writer.flush(); 764 bytes = out.toByteArray(); 765 numBytes = bytes.length; 766 nextByte = 0; 767 } 768 769} 770 771/* 772 * Internal helper class for creating a localized OutputStream. A writer 773 * wrapped in an OutputStream. Bytes are written to characters in big-endian 774 * fashion. 775 */ 776class WriterOutputStream extends OutputStream { 777 778 private Reader reader; 779 780 private Writer writer; 781 782 private PipedOutputStream out; 783 784 private PipedInputStream pipe; 785 786 private int numBytes; 787 788 private String enc = System.getProperty("file.encoding", "UTF-8"); 789 790 public WriterOutputStream(OutputStream stream) { 791 try { 792 // sink 793 this.writer = new OutputStreamWriter(stream, enc); 794 795 // transcriber 796 out = new PipedOutputStream(); 797 pipe = new PipedInputStream(out); 798 this.reader = new InputStreamReader(pipe, "UTF-8"); 799 800 } catch (UnsupportedEncodingException e) { 801 // Should never happen, since platform encoding must be supported. 802 throw new RuntimeException(e); 803 } catch (IOException e) { 804 throw new RuntimeException(e); 805 } 806 } 807 808 @Override 809 public void write(int b) throws IOException { 810 out.write(b); 811 if( ++numBytes > 256) { 812 flush(); 813 numBytes = 0; 814 } 815 } 816 817 @Override 818 public void flush() throws IOException { 819 out.flush(); 820 char[] chars = new char[128]; 821 if (pipe.available() > 0) { 822 int read = reader.read(chars); 823 if (read > 0) { 824 writer.write(chars, 0, read); 825 } 826 } 827 writer.flush(); 828 } 829 830 @Override 831 public void close() throws IOException { 832 out.close(); 833 flush(); 834 writer.close(); 835 } 836} 837