001    
002    /*
003     * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004     *
005     * $Id: NullModemInputStream.java 150 2011-10-21 03:17:30Z archie.cobbs $
006     */
007    
008    package org.dellroad.stuff.io;
009    
010    import java.io.FilterInputStream;
011    import java.io.IOException;
012    import java.io.OutputStream;
013    import java.io.PipedInputStream;
014    import java.io.PipedOutputStream;
015    
016    /**
017     * Presents an {@link java.io.InputStream InputStream} interface given a {@link WriteCallback} that can write to an
018     * {@link OutputStream}. A separate thread is created to perform the actual writing.
019     *
020     * @since 1.0.74
021     */
022    public class NullModemInputStream extends FilterInputStream {
023    
024        private final PipedOutputStream output;
025    
026        /**
027         * Constructor.
028         *
029         * @param writer    {@link OutputStream} writer callback
030         * @param name      name for this instance; used to create the name of the background thread
031         */
032        public NullModemInputStream(final WriteCallback writer, String name) {
033            super(new PipedInputStream());
034    
035            // Sanity check
036            if (writer == null)
037                throw new IllegalArgumentException("null writer");
038    
039            // Create other end of pipe
040            try {
041                this.output = new PipedOutputStream(this.getPipedInputStream());
042            } catch (IOException e) {
043                throw new RuntimeException("unexpected exception", e);
044            }
045    
046            // Launch writer thread
047            Thread thread = new WriterThread(writer, this.output, name);
048            thread.setDaemon(true);
049            thread.start();
050        }
051    
052        /**
053         * Get the wrapped stream cast as a {@link PipedInputStream}.
054         */
055        protected PipedInputStream getPipedInputStream() {
056            return (PipedInputStream)this.in;
057        }
058    
059        /**
060         * Ensure input stream is closed when this instance is no longer referenced.
061         *
062         * <p>
063         * This ensures the writer thread wakes up (and exits, avoiding a memory leak) when an instance of this class
064         * is created but never read from.
065         */
066        @Override
067        protected void finalize() throws Throwable {
068            try {
069                try {
070                    this.getPipedInputStream().close();
071                } catch (IOException e) {
072                    // ignore
073                }
074            } finally {
075                super.finalize();
076            }
077        }
078    
079        /**
080         * Callback interface used by {@link NullModemInputStream}.
081         */
082        public interface WriteCallback {
083    
084            /**
085             * Write the output to the given output stream.
086             *
087             * <p>
088             * This method will be invoked (once) asynchronously in a dedicated writer thread.
089             * </p>
090             *
091             * @param output output that sends data to the corresponding {@link NullModemInputStream}
092             * @throws IOException if an I/O error occurs
093             */
094            void writeTo(OutputStream output) throws IOException;
095        }
096    
097        /**
098         * Writer thread. This is designed to not hold a reference to the {@link NullModemInputStream}.
099         */
100        private static class WriterThread extends Thread {
101    
102            private final WriteCallback writer;
103            private final PipedOutputStream output;
104    
105            WriterThread(WriteCallback writer, PipedOutputStream output, String name) {
106                super(name);
107                this.writer = writer;
108                this.output = output;
109            }
110    
111            @Override
112            public void run() {
113                try {
114                    this.writer.writeTo(this.output);
115                } catch (IOException e) {
116                    // ignore - reader will get another IOException because pipe is about to be broken
117                } finally {
118                    try {
119                        this.output.close();
120                    } catch (IOException e) {
121                        // ignore
122                    }
123                }
124            }
125        }
126    }
127