/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.stetho.inspector.network;
import com.facebook.stetho.common.ExceptionUtil;
import com.facebook.stetho.common.Util;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.zip.GZIPInputStream;
/**
* An {@link OutputStream} filter which decompresses gzip data before it is written to the
* specified destination output stream. This is functionally equivalent to
* {@link java.util.zip.InflaterOutputStream} but provides gzip header awareness. The
* implementation however is very different to avoid actually interpreting the gzip header.
*/
class GunzippingOutputStream extends FilterOutputStream {
private final Future<Void> mCopyFuture;
private static final ExecutorService sExecutor = Executors.newCachedThreadPool();
public static GunzippingOutputStream create(OutputStream finalOut) throws IOException {
PipedInputStream pipeIn = new PipedInputStream();
PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);
Future<Void> copyFuture = sExecutor.submit(
new GunzippingCallable(pipeIn, finalOut));
return new GunzippingOutputStream(pipeOut, copyFuture);
}
private GunzippingOutputStream(OutputStream out, Future<Void> copyFuture) throws IOException {
super(out);
mCopyFuture = copyFuture;
}
@Override
public void close() throws IOException {
boolean success = false;
try {
super.close();
success = true;
} finally {
try {
getAndRethrow(mCopyFuture);
} catch (IOException e) {
if (success) {
throw e;
}
}
}
}
private static <T> T getAndRethrow(Future<T> future) throws IOException {
while (true) {
try {
return future.get();
} catch (InterruptedException e) {
// Continue...
} catch (ExecutionException e) {
Throwable cause = e.getCause();
ExceptionUtil.propagateIfInstanceOf(cause, IOException.class);
ExceptionUtil.propagate(cause);
}
}
}
private static class GunzippingCallable implements Callable<Void> {
private final InputStream mIn;
private final OutputStream mOut;
public GunzippingCallable(InputStream in, OutputStream out) {
mIn = in;
mOut = out;
}
@Override
public Void call() throws IOException {
GZIPInputStream in = new GZIPInputStream(mIn);
try {
Util.copy(in, mOut, new byte[1024]);
} finally {
in.close();
mOut.close();
}
return null;
}
}
}