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