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