1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.util;
17 
18 import com.android.tradefed.result.InputStreamSource;
19 
20 import com.google.common.io.ByteStreams;
21 
22 import java.io.BufferedInputStream;
23 import java.io.BufferedReader;
24 import java.io.ByteArrayOutputStream;
25 import java.io.Closeable;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.OutputStream;
30 import java.io.PrintStream;
31 import java.io.Reader;
32 import java.io.Writer;
33 import java.security.DigestInputStream;
34 import java.security.MessageDigest;
35 import java.security.NoSuchAlgorithmException;
36 import java.util.Objects;
37 import java.util.zip.GZIPOutputStream;
38 import java.util.zip.ZipOutputStream;
39 
40 /**
41  * Utility class for managing input streams.
42  */
43 public class StreamUtil {
44 
45     // 16K buffer size
46     private static final int BUF_SIZE = 16 * 1024;
47 
StreamUtil()48     private StreamUtil() {
49     }
50 
51     /**
52      * Retrieves a {@link String} from an {@link InputStreamSource}.
53      *
54      * @param source the {@link InputStreamSource}
55      * @return a {@link String} containing the stream contents
56      * @throws IOException if failure occurred reading the stream
57      */
getStringFromSource(InputStreamSource source)58     public static String getStringFromSource(InputStreamSource source) throws IOException {
59         final InputStream stream = source.createInputStream();
60         final String contents;
61         try {
62             contents = getStringFromStream(stream);
63         } finally {
64             close(stream);
65         }
66         return contents;
67     }
68 
69     /**
70      * Count number of lines in an {@link InputStreamSource}
71      * @param source the {@link InputStreamSource}
72      * @return number of lines
73      * @throws IOException if failure occurred reading the stream
74      */
countLinesFromSource(InputStreamSource source)75     public static int countLinesFromSource(InputStreamSource source) throws IOException {
76         int lineCount = 0;
77         try (BufferedReader br =
78                 new BufferedReader(new InputStreamReader(source.createInputStream()))) {
79             while (br.readLine() != null) {
80                 lineCount++;
81             }
82         }
83         return lineCount;
84     }
85 
86     /**
87      * Retrieves a {@link ByteArrayList} from an {@link InputStreamSource}.
88      *
89      * @param source the {@link InputStreamSource}
90      * @return a {@link ByteArrayList} containing the stream contents
91      * @throws IOException if failure occurred reading the stream
92      */
getByteArrayListFromSource(InputStreamSource source)93     public static ByteArrayList getByteArrayListFromSource(InputStreamSource source)
94             throws IOException {
95         final InputStream stream = source.createInputStream();
96         final ByteArrayList contents;
97         try {
98             contents = getByteArrayListFromStream(stream);
99         } finally {
100             close(stream);
101         }
102         return contents;
103     }
104 
105     /**
106      * Retrieves a {@link String} from a character stream.
107      *
108      * @param stream the {@link InputStream}
109      * @return a {@link String} containing the stream contents
110      * @throws IOException if failure occurred reading the stream
111      */
getStringFromStream(InputStream stream)112     public static String getStringFromStream(InputStream stream) throws IOException {
113         int irChar = -1;
114         StringBuilder builder = new StringBuilder();
115         try (Reader ir = new BufferedReader(new InputStreamReader(stream))) {
116             while ((irChar = ir.read()) != -1) {
117                 builder.append((char) irChar);
118             }
119         }
120         return builder.toString();
121     }
122 
123     /**
124      * Retrieves a {@link ByteArrayList} from a byte stream.
125      *
126      * @param stream the {@link InputStream}
127      * @return a {@link ByteArrayList} containing the stream contents
128      * @throws IOException if failure occurred reading the stream
129      */
getByteArrayListFromStream(InputStream stream)130     public static ByteArrayList getByteArrayListFromStream(InputStream stream) throws IOException {
131         InputStream is = new BufferedInputStream(stream);
132         int inputByte = -1;
133         ByteArrayList list = new ByteArrayList();
134         while ((inputByte = is.read()) != -1) {
135             list.add((byte)inputByte);
136         }
137         list.trimToSize();
138         return list;
139     }
140 
141     /**
142      * Return a BuffferedReader to read the contents from the given InputstreamSource.
143      *
144      * @param stream the {@link InputStreamSource}
145      * @return a BuffferedReader
146      */
getBufferedReaderFromStreamSrc(InputStreamSource stream)147     public static BufferedReader getBufferedReaderFromStreamSrc(InputStreamSource stream) {
148         return new BufferedReader(new InputStreamReader(stream.createInputStream()));
149     }
150 
151     /**
152      * Copies contents of origStream to destStream.
153      * <p/>
154      * Recommended to provide a buffered stream for input and output
155      *
156      * @param inStream the {@link InputStream}
157      * @param outStream the {@link OutputStream}
158      * @throws IOException
159      */
copyStreams(InputStream inStream, OutputStream outStream)160     public static void copyStreams(InputStream inStream, OutputStream outStream)
161             throws IOException {
162         byte[] buf = new byte[BUF_SIZE];
163         int size = -1;
164         while ((size = inStream.read(buf)) != -1) {
165             outStream.write(buf, 0, size);
166         }
167     }
168 
169     /**
170      * Copies contents of inStream to writer.
171      * <p/>
172      * Recommended to provide a buffered stream for input and output
173      *
174      * @param inStream the {@link InputStream}
175      * @param writer the {@link Writer} destination
176      * @throws IOException
177      */
copyStreamToWriter(InputStream inStream, Writer writer)178     public static void copyStreamToWriter(InputStream inStream, Writer writer) throws IOException {
179         byte[] buf = new byte[BUF_SIZE];
180         int size = -1;
181         while ((size = inStream.read(buf)) != -1) {
182             writer.write(new String(buf, 0, size));
183         }
184     }
185 
186     /**
187      * Gets the stack trace as a {@link String}.
188      *
189      * @param throwable the {@link Throwable} to convert.
190      * @return a {@link String} stack trace
191      */
getStackTrace(Throwable throwable)192     public static String getStackTrace(Throwable throwable) {
193         // dump the print stream results to the ByteArrayOutputStream, so contents can be evaluated
194         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
195         PrintStream bytePrintStream = new PrintStream(outputStream);
196         throwable.printStackTrace(bytePrintStream);
197         return outputStream.toString();
198     }
199 
200     /**
201      * @deprecated use {@link #close(Closeable)} instead.
202      */
203     @Deprecated
closeStream(OutputStream out)204     public static void closeStream(OutputStream out) {
205         close(out);
206     }
207 
208     /**
209      * @deprecated use {@link #close(Closeable)} instead.
210      */
211     @Deprecated
closeStream(InputStream in)212     public static void closeStream(InputStream in) {
213         close(in);
214     }
215 
216     /**
217      * Attempts to flush the given output stream, and then closes it.
218      *
219      * @param outStream the {@link OutputStream}. No action taken if outStream is null.
220      */
flushAndCloseStream(OutputStream outStream)221     public static void flushAndCloseStream(OutputStream outStream) {
222         if (outStream != null) {
223             try {
224                 outStream.flush();
225             } catch (IOException e) {
226                 // ignore
227             }
228             try {
229                 outStream.close();
230             } catch (IOException e) {
231                 // ignore
232             }
233         }
234     }
235 
236     /**
237      * Closes given zip output stream.
238      *
239      * @param outStream the {@link ZipOutputStream}. No action taken if outStream is null.
240      */
closeZipStream(ZipOutputStream outStream)241     public static void closeZipStream(ZipOutputStream outStream) {
242         if (outStream != null) {
243             try {
244                 outStream.closeEntry();
245                 outStream.close();
246             } catch (IOException e) {
247                 // ignore
248             }
249         }
250     }
251 
252     /**
253      * Closes given gzip output stream.
254      *
255      * @param outStream the {@link ZipOutputStream}. No action taken if outStream is null.
256      */
closeGZipStream(GZIPOutputStream outStream)257     public static void closeGZipStream(GZIPOutputStream outStream) {
258         if (outStream != null) {
259             try {
260                 outStream.finish();
261                 outStream.close();
262             } catch (IOException e) {
263                 // ignore
264             }
265         }
266     }
267 
268     /**
269      * Closes the given {@link Closeable}.
270      *
271      * @param closeable the {@link Closeable}. No action taken if <code>null</code>.
272      */
close(Closeable closeable)273     public static void close(Closeable closeable) {
274         if (closeable != null) {
275             try {
276                 closeable.close();
277             } catch (IOException e) {
278                 // ignore
279             }
280         }
281     }
282 
283     /**
284      * Cancels the given {@link InputStreamSource} if non-null.
285      */
cancel(InputStreamSource outputSource)286     public static void cancel(InputStreamSource outputSource) {
287         if (outputSource != null) {
288             outputSource.close();
289         }
290     }
291 
292     /**
293      * Create a {@link OutputStream} that discards all writes.
294      */
nullOutputStream()295     public static OutputStream nullOutputStream() {
296         return ByteStreams.nullOutputStream();
297     }
298 
299     /**
300      * Helper method to calculate md5 for a inputStream. The inputStream will be consumed and
301      * closed.
302      *
303      * @param inputSource used to create inputStream
304      * @return md5 of the stream
305      * @throws IOException
306      */
calculateMd5(InputStream inputSource)307     public static String calculateMd5(InputStream inputSource) throws IOException {
308         MessageDigest md = null;
309         try {
310             md = MessageDigest.getInstance("md5");
311         } catch (NoSuchAlgorithmException e) {
312             // This should not happen
313             throw new RuntimeException(e);
314         }
315         InputStream input = new BufferedInputStream(new DigestInputStream(inputSource, md));
316         byte[] buf = new byte[BUF_SIZE];
317         while (input.read(buf) != -1) {
318             // Read through the stream to update digest.
319         }
320         input.close();
321         String md5 = bytesToHexString(md.digest());
322         return md5;
323     }
324 
325     private static final char[] HEX_CHARS = {
326         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
327     };
328 
329     /**
330      * Converts a byte array into a String of hexadecimal characters.
331      *
332      * @param bytes an array of bytes
333      * @return hex string representation of bytes array
334      */
bytesToHexString(byte[] bytes)335     private static String bytesToHexString(byte[] bytes) {
336         Objects.requireNonNull(bytes);
337         StringBuilder sb = new StringBuilder(2 * bytes.length);
338         for (int i = 0; i < bytes.length; i++) {
339             int b = 0x0f & (bytes[i] >> 4);
340             sb.append(HEX_CHARS[b]);
341             b = 0x0f & bytes[i];
342             sb.append(HEX_CHARS[b]);
343         }
344         return sb.toString();
345     }
346 }
347