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