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