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.File;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.InputStreamReader;
31 import java.io.OutputStream;
32 import java.io.PrintStream;
33 import java.io.Reader;
34 import java.io.Writer;
35 import java.security.DigestInputStream;
36 import java.security.MessageDigest;
37 import java.security.NoSuchAlgorithmException;
38 import java.util.Base64;
39 import java.util.Objects;
40 import java.util.zip.CRC32;
41 import java.util.zip.GZIPOutputStream;
42 import java.util.zip.ZipOutputStream;
43 
44 /**
45  * Utility class for managing input streams.
46  */
47 public class StreamUtil {
48 
49     // 16K buffer size
50     private static final int BUF_SIZE = 16 * 1024;
51 
StreamUtil()52     private StreamUtil() {
53     }
54 
55     /**
56      * Retrieves a {@link String} from an {@link InputStreamSource}.
57      *
58      * @param source the {@link InputStreamSource}
59      * @return a {@link String} containing the stream contents
60      * @throws IOException if failure occurred reading the stream
61      */
getStringFromSource(InputStreamSource source)62     public static String getStringFromSource(InputStreamSource source) throws IOException {
63         final InputStream stream = source.createInputStream();
64         final String contents;
65         try {
66             contents = getStringFromStream(stream);
67         } finally {
68             close(stream);
69         }
70         return contents;
71     }
72 
73     /**
74      * Count number of lines in an {@link InputStreamSource}
75      * @param source the {@link InputStreamSource}
76      * @return number of lines
77      * @throws IOException if failure occurred reading the stream
78      */
countLinesFromSource(InputStreamSource source)79     public static int countLinesFromSource(InputStreamSource source) throws IOException {
80         int lineCount = 0;
81         try (BufferedReader br =
82                 new BufferedReader(new InputStreamReader(source.createInputStream()))) {
83             while (br.readLine() != null) {
84                 lineCount++;
85             }
86         }
87         return lineCount;
88     }
89 
90     /**
91      * Retrieves a {@link ByteArrayList} from an {@link InputStreamSource}.
92      *
93      * @param source the {@link InputStreamSource}
94      * @return a {@link ByteArrayList} containing the stream contents
95      * @throws IOException if failure occurred reading the stream
96      */
getByteArrayListFromSource(InputStreamSource source)97     public static ByteArrayList getByteArrayListFromSource(InputStreamSource source)
98             throws IOException {
99         final InputStream stream = source.createInputStream();
100         final ByteArrayList contents;
101         try {
102             contents = getByteArrayListFromStream(stream);
103         } finally {
104             close(stream);
105         }
106         return contents;
107     }
108 
109     /**
110      * Retrieves a {@link String} from a character stream.
111      *
112      * @param stream the {@link InputStream}
113      * @return a {@link String} containing the stream contents
114      * @throws IOException if failure occurred reading the stream
115      */
getStringFromStream(InputStream stream)116     public static String getStringFromStream(InputStream stream) throws IOException {
117         return getStringFromStream(stream, 0);
118     }
119 
120     /**
121      * Retrieves a {@link String} from a character stream.
122      *
123      * @param stream the {@link InputStream}
124      * @param length the size of the content to read, set to 0 to read all contents
125      * @return a {@link String} containing the stream contents
126      * @throws IOException if failure occurred reading the stream
127      */
getStringFromStream(InputStream stream, long length)128     public static String getStringFromStream(InputStream stream, long length) throws IOException {
129         int irChar = -1;
130         StringBuilder builder = new StringBuilder();
131         try (Reader ir = new BufferedReader(new InputStreamReader(stream))) {
132             long count = 0;
133             while ((irChar = ir.read()) != -1) {
134                 builder.append((char) irChar);
135                 count++;
136                 if (length > 0 && count >= length) {
137                     break;
138                 }
139             }
140         }
141         return builder.toString();
142     }
143 
144     /**
145      * Retrieves a {@link ByteArrayList} from a byte stream.
146      *
147      * @param stream the {@link InputStream}
148      * @return a {@link ByteArrayList} containing the stream contents
149      * @throws IOException if failure occurred reading the stream
150      */
getByteArrayListFromStream(InputStream stream)151     public static ByteArrayList getByteArrayListFromStream(InputStream stream) throws IOException {
152         InputStream is = new BufferedInputStream(stream);
153         int inputByte = -1;
154         ByteArrayList list = new ByteArrayList();
155         while ((inputByte = is.read()) != -1) {
156             list.add((byte)inputByte);
157         }
158         list.trimToSize();
159         return list;
160     }
161 
162     /**
163      * Return a BuffferedReader to read the contents from the given InputstreamSource.
164      *
165      * @param stream the {@link InputStreamSource}
166      * @return a BuffferedReader
167      */
getBufferedReaderFromStreamSrc(InputStreamSource stream)168     public static BufferedReader getBufferedReaderFromStreamSrc(InputStreamSource stream) {
169         return new BufferedReader(new InputStreamReader(stream.createInputStream()));
170     }
171 
172     /**
173      * Copies contents of origStream to destStream.
174      * <p/>
175      * Recommended to provide a buffered stream for input and output
176      *
177      * @param inStream the {@link InputStream}
178      * @param outStream the {@link OutputStream}
179      * @throws IOException
180      */
copyStreams(InputStream inStream, OutputStream outStream)181     public static void copyStreams(InputStream inStream, OutputStream outStream)
182             throws IOException {
183         copyStreams(inStream, outStream, 0);
184     }
185 
186     /**
187      * Copies contents of origStream to destStream.
188      *
189      * <p>Recommended to provide a buffered stream for input and output
190      *
191      * @param inStream the {@link InputStream}
192      * @param outStream the {@link OutputStream}
193      * @param offset the offset of when to start copying the data.
194      * @throws IOException
195      */
copyStreams(InputStream inStream, OutputStream outStream, int offset)196     public static void copyStreams(InputStream inStream, OutputStream outStream, int offset)
197             throws IOException {
198         // Set size to a negative value to copy all content starting at the given offset.
199         copyStreams(inStream, outStream, offset, -1);
200     }
201 
202     /**
203      * Copies contents of origStream to destStream starting at a given offset with a specific size.
204      *
205      * <p>Recommended to provide a buffered stream for input and output
206      *
207      * @param inStream the {@link InputStream}
208      * @param outStream the {@link OutputStream}
209      * @param offset the offset of when to start copying the data.
210      * @param size the number of bytes to copy. A negative value means to copy all content.
211      * @throws IOException
212      */
copyStreams( InputStream inStream, OutputStream outStream, long offset, long size)213     public static void copyStreams(
214             InputStream inStream, OutputStream outStream, long offset, long size)
215             throws IOException {
216         assert offset >= 0 : "offset must be greater or equal to zero.";
217         assert size != 0 : "size cannot be zero.";
218         inStream.skip(offset);
219         byte[] buf = new byte[BUF_SIZE];
220         long totalRetrievedSize = 0;
221         int retrievedSize = -1;
222         while ((retrievedSize = inStream.read(buf)) != -1) {
223             if (size > 0 && size < totalRetrievedSize + retrievedSize) {
224                 retrievedSize = (int) (size - totalRetrievedSize);
225             }
226             outStream.write(buf, 0, retrievedSize);
227             totalRetrievedSize += retrievedSize;
228             if (size == totalRetrievedSize) {
229                 break;
230             }
231         }
232         if (size > 0 && size > totalRetrievedSize) {
233             throw new IOException(
234                     String.format(
235                             "Failed to read %d bytes starting at offset %d, only %d bytes "
236                                     + "retrieved.",
237                             size, offset, totalRetrievedSize));
238         }
239     }
240 
241     /**
242      * Copies contents of inStream to writer.
243      * <p/>
244      * Recommended to provide a buffered stream for input and output
245      *
246      * @param inStream the {@link InputStream}
247      * @param writer the {@link Writer} destination
248      * @throws IOException
249      */
copyStreamToWriter(InputStream inStream, Writer writer)250     public static void copyStreamToWriter(InputStream inStream, Writer writer) throws IOException {
251         byte[] buf = new byte[BUF_SIZE];
252         int size = -1;
253         while ((size = inStream.read(buf)) != -1) {
254             writer.write(new String(buf, 0, size));
255         }
256     }
257 
258     /**
259      * Copies contents of file to outStream. It is recommended to provide a buffered stream.
260      *
261      * @param file the {@link File}
262      * @param outStream the {@link OutputStream}
263      * @throws IOException
264      */
copyFileToStream(File file, OutputStream outStream)265     public static void copyFileToStream(File file, OutputStream outStream) throws IOException {
266         InputStream inStream = null;
267         try {
268             inStream = new FileInputStream(file);
269             inStream = new BufferedInputStream(inStream);
270             StreamUtil.copyStreams(inStream, outStream);
271         } finally {
272             StreamUtil.close(inStream);
273         }
274     }
275 
276     /**
277      * Gets the stack trace as a {@link String}.
278      *
279      * @param throwable the {@link Throwable} to convert.
280      * @return a {@link String} stack trace
281      */
getStackTrace(Throwable throwable)282     public static String getStackTrace(Throwable throwable) {
283         // dump the print stream results to the ByteArrayOutputStream, so contents can be evaluated
284         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
285         PrintStream bytePrintStream = new PrintStream(outputStream);
286         throwable.printStackTrace(bytePrintStream);
287         return outputStream.toString();
288     }
289 
290     /**
291      * @deprecated use {@link #close(Closeable)} instead.
292      */
293     @Deprecated
closeStream(OutputStream out)294     public static void closeStream(OutputStream out) {
295         close(out);
296     }
297 
298     /**
299      * @deprecated use {@link #close(Closeable)} instead.
300      */
301     @Deprecated
closeStream(InputStream in)302     public static void closeStream(InputStream in) {
303         close(in);
304     }
305 
306     /**
307      * Attempts to flush the given output stream, and then closes it.
308      *
309      * @param outStream the {@link OutputStream}. No action taken if outStream is null.
310      */
flushAndCloseStream(OutputStream outStream)311     public static void flushAndCloseStream(OutputStream outStream) {
312         if (outStream != null) {
313             try {
314                 outStream.flush();
315             } catch (IOException e) {
316                 // ignore
317             }
318             try {
319                 outStream.close();
320             } catch (IOException e) {
321                 // ignore
322             }
323         }
324     }
325 
326     /**
327      * Closes given zip output stream.
328      *
329      * @param outStream the {@link ZipOutputStream}. No action taken if outStream is null.
330      */
closeZipStream(ZipOutputStream outStream)331     public static void closeZipStream(ZipOutputStream outStream) {
332         if (outStream != null) {
333             try {
334                 outStream.closeEntry();
335                 outStream.close();
336             } catch (IOException e) {
337                 // ignore
338             }
339         }
340     }
341 
342     /**
343      * Closes given gzip output stream.
344      *
345      * @param outStream the {@link ZipOutputStream}. No action taken if outStream is null.
346      */
closeGZipStream(GZIPOutputStream outStream)347     public static void closeGZipStream(GZIPOutputStream outStream) {
348         if (outStream != null) {
349             try {
350                 outStream.finish();
351                 outStream.close();
352             } catch (IOException e) {
353                 // ignore
354             }
355         }
356     }
357 
358     /**
359      * Closes the given {@link Closeable}.
360      *
361      * @param closeable the {@link Closeable}. No action taken if <code>null</code>.
362      */
close(Closeable closeable)363     public static void close(Closeable closeable) {
364         if (closeable != null) {
365             try {
366                 closeable.close();
367             } catch (IOException e) {
368                 // ignore
369             }
370         }
371     }
372 
373     /**
374      * Cancels the given {@link InputStreamSource} if non-null.
375      */
cancel(InputStreamSource outputSource)376     public static void cancel(InputStreamSource outputSource) {
377         if (outputSource != null) {
378             outputSource.close();
379         }
380     }
381 
382     /**
383      * Create a {@link OutputStream} that discards all writes.
384      */
nullOutputStream()385     public static OutputStream nullOutputStream() {
386         return ByteStreams.nullOutputStream();
387     }
388 
389     /**
390      * Helper method to calculate CRC-32 for an {@link InputStream}. The stream will be consumed and
391      * closed. It is recommended to provide a buffered stream.
392      *
393      * @param inStream the {@link InputStream}
394      * @return CRC-32 of the stream
395      * @throws IOException
396      */
calculateCrc32(InputStream inStream)397     public static long calculateCrc32(InputStream inStream) throws IOException {
398         CRC32 crc32 = new CRC32();
399         byte[] buf = new byte[BUF_SIZE];
400         int size = -1;
401         try {
402             while ((size = inStream.read(buf)) >= 0) {
403                 crc32.update(buf, 0, size);
404             }
405         } finally {
406             inStream.close();
407         }
408         return crc32.getValue();
409     }
410 
411     /**
412      * Helper method to calculate md5 for a inputStream. The inputStream will be consumed and
413      * closed.
414      *
415      * @param inputSource used to create inputStream
416      * @return md5 of the stream
417      * @throws IOException
418      */
calculateMd5(InputStream inputSource)419     public static String calculateMd5(InputStream inputSource) throws IOException {
420         return bytesToHexString(calculateMd5Digest(inputSource));
421     }
422 
423     /**
424      * Helper method to calculate base64 md5 for a inputStream. The inputStream will be consumed and
425      * closed.
426      *
427      * @param inputSource used to create inputStream
428      * @return base64 md5 of the stream
429      * @throws IOException
430      */
calculateBase64Md5(InputStream inputSource)431     public static String calculateBase64Md5(InputStream inputSource) throws IOException {
432         return Base64.getEncoder().encodeToString(calculateMd5Digest(inputSource));
433     }
434 
calculateMd5Digest(InputStream inputSource)435     private static byte[] calculateMd5Digest(InputStream inputSource) throws IOException {
436         MessageDigest md = null;
437         try {
438             md = MessageDigest.getInstance("md5");
439         } catch (NoSuchAlgorithmException e) {
440             // This should not happen
441             throw new RuntimeException(e);
442         }
443         InputStream input = new BufferedInputStream(new DigestInputStream(inputSource, md));
444         byte[] buf = new byte[BUF_SIZE];
445         while (input.read(buf) != -1) {
446             // Read through the stream to update digest.
447         }
448         input.close();
449         return md.digest();
450     }
451 
452     private static final char[] HEX_CHARS = {
453         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
454     };
455 
456     /**
457      * Converts a byte array into a String of hexadecimal characters.
458      *
459      * @param bytes an array of bytes
460      * @return hex string representation of bytes array
461      */
bytesToHexString(byte[] bytes)462     private static String bytesToHexString(byte[] bytes) {
463         Objects.requireNonNull(bytes);
464         StringBuilder sb = new StringBuilder(2 * bytes.length);
465         for (int i = 0; i < bytes.length; i++) {
466             int b = 0x0f & (bytes[i] >> 4);
467             sb.append(HEX_CHARS[b]);
468             b = 0x0f & bytes[i];
469             sb.append(HEX_CHARS[b]);
470         }
471         return sb.toString();
472     }
473 }
474