001package com.randomnoun.common;
002
003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
005 */
006
007import java.io.*;
008
009
010/**
011 * Utility class to copy streams synchronously and asynchronously.
012 *
013 * 
014 * @author knoxg
015 */
016public class StreamUtil {
017    
018
019    /**
020     * Creates a new StreamUtils object.
021     */
022    public StreamUtil() {
023    }
024
025    /** Copies the data from an inputStream to an outputstream (used to mimic
026     *  pipes).
027     *
028     *  @param input The stream to retrieve information from
029     *  @param output The stream to send data to
030     *  @param bufSize buffer size in bytes
031     *  
032     * @throws IOException
033     */
034    public static void copyStream(InputStream input, OutputStream output, int bufSize)
035        throws IOException {
036        int bytesRead;
037        byte[] buffer = new byte[bufSize];
038
039        while ((bytesRead = input.read(buffer)) != -1) {
040            output.write(buffer, 0, bytesRead);
041            output.flush();
042        }
043    }
044
045    /** Copies a stream and return number of bytes copied
046     *
047     *  @param input The stream to retrieve information from
048     *  @param output The stream to send data to
049     *  @param bufSize buffer size in bytes
050     *  
051     * @throws IOException
052     */
053    public static long copyStreamCount(InputStream input, OutputStream output, int bufSize)
054        throws IOException 
055    {
056        long totalBytes = 0;
057        int bytesRead;
058        byte[] buffer = new byte[bufSize];
059
060        while ((bytesRead = input.read(buffer)) != -1) {
061            output.write(buffer, 0, bytesRead);
062            output.flush();
063            totalBytes += bytesRead;
064        }
065        return totalBytes;
066    }
067
068    
069    /** Copies the data from an inputStream to an outputstream 
070    *
071    *  @param input The stream to retrieve information from
072    *  @param output The stream to send data to
073    *  
074    * @throws IOException
075    */
076    public static void copyStream(InputStream input, OutputStream output)
077                throws IOException 
078    {
079        copyStream(input, output, 4096);
080        }    
081
082    /** Reads all available data from an InputStream, and returns it in a
083     *  single byte array.
084     *
085     * @param input The stream to receive information from
086     * @return A byte array containing the contents of the stream
087     * @throws IOException
088     */
089    public static byte[] getByteArray(InputStream input)
090        throws IOException {
091        ByteArrayOutputStream baos = new ByteArrayOutputStream();
092
093        copyStream(input, baos, 1024);
094
095        return baos.toByteArray();
096    }
097
098    /** Returns a thread that, when started, will pipe all data from one
099     *  inputstream to an outputstream. The thread will complete when the
100     *  inputStream returns EOF.
101     *
102     *  @param input The stream to retrieve information from
103     *  @param output The stream to send data to
104     *  @param bufSize buffer size in bytes
105     */
106    public static Thread copyThread(InputStream input, OutputStream output, int bufSize) {
107        // not terribly sure why variables accessed from within anonymous classes
108        // need to be final, but hey. Hopefully this is just final within the
109        // scope of this method call. Which would make sense.
110        final InputStream f_input = input;
111        final OutputStream f_output = output;
112        final int f_bufSize = bufSize;
113
114        return new Thread() {
115            public void run() {
116                try {
117                    copyStream(f_input, f_output, f_bufSize);
118                } catch (IOException e) {
119                    // not much we can do about this, unfortunately.
120                    // could wrap in a runtime exception, I guess.
121                        // yeah, let's do that.
122                    throw new RuntimeException(e);
123                }
124            }
125        };
126    }
127
128    /** Scans this input stream until the text in 'searchText' is found. Returns
129     * the number of bytes skipped if the search text was found, or -1 if the text
130     * was not found.
131     * 
132     * @param input The input stream to scan
133     * @param searchText The text we are searching for
134     * 
135     * @throws IOException if an IO Exception occurs reading the stream
136     */    
137    public static int indexOf(InputStream input, String searchText) throws IOException {
138        int bytesRead = 0;
139        int matched = 0;
140        int ch;
141        byte[] compare = searchText.getBytes();
142        int    compareSize = compare.length;
143        while (true) {
144            ch = input.read();
145            if (ch==-1) { return -1; }
146            bytesRead++;
147            if (ch==compare[matched]) {
148                matched++;
149                if (matched==compareSize) {
150                    return bytesRead;
151                }
152            } else {
153                matched = 0;
154            }
155        }
156    }
157
158    /** Reads this input stream until the text in 'searchText' is found. Returns
159     * a String containing the data read, including the searchText. Returns null if the text
160     * was not found.
161     * 
162     * @param input The input stream to scan
163     * @param searchText The text we are searching for
164     * 
165     * @throws IOException if an IO Exception occurs reading the stream
166     */    
167    public static String readUntil(InputStream input, String searchText) throws IOException {
168        //int bytesRead = 0;
169        int matched = 0;
170        int ch;
171        byte[] compare = searchText.getBytes();
172        int    compareSize = compare.length;
173        ByteArrayOutputStream baos = new ByteArrayOutputStream();
174        while (true) {
175            ch = input.read();
176            if (ch==-1) { return null; }
177            //bytesRead++;
178            baos.write(ch);
179            if (ch==compare[matched]) {
180                matched++;
181                if (matched==compareSize) {
182                    return baos.toString();
183                }
184            } else {
185                matched = 0;
186            }
187        }
188    }
189    
190    /** Returns a thread that, when started, will pipe all data from one
191     *  inputstream to an outputstream. The thread will complete when the
192     *  inputStream returns EOF. The output stream will also be closed.
193     *
194     *  @param input The stream to retrieve information from
195     *  @param output The stream to send data to
196     *  @param bufSize buffer size in bytes
197     */
198    public static Thread copyAndCloseThread(InputStream input, OutputStream output, int bufSize) {
199        // not terribly sure why variables accessed from within anonymous classes
200        // need to be final, but hey. Hopefully this is just final within the
201        // scope of this method call. Which would make sense.
202        final InputStream f_input = input;
203        final OutputStream f_output = output;
204        final int f_bufSize = bufSize;
205
206        return new Thread() {
207            public void run() {
208                try {
209                    copyStream(f_input, f_output, f_bufSize);
210                    f_output.close();
211                } catch (IOException e) {
212                    // not much we can do about this, unfortunately.
213                    // could wrap in a runtime exception, I guess.
214                        // yeah, let's do that.
215                    throw new RuntimeException(e);
216                }
217            }
218        };
219    }
220    
221    
222    
223}