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