blob: 1e9235d2206380f506b2ef0b3d42dfcb178a7cc4 [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.libraries.mobiledatadownload.file.common.internal;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;
/**
* An {@code InputStream} that wraps a byte array provided lazily. This can be helpful for adapting
* an all-at-once byte-processing API to a streaming paradigm. Sample usage:
*
* <pre>{@code
* InputStream source = ...;
* InputStream lazy =
* new LazyByteArrayInputStream(
* () -> {
* try {
* byte[] bytes = ByteStreams.toByteArray(source);
* return myByteFunction(bytes);
* } finally {
* source.close();
* }
* });
* // source stream is neither read nor processed until lazy.read()
* }</pre>
*/
public final class LazyByteArrayInputStream extends InputStream {
private final Callable<byte[]> byteProvider;
// Lazily initialized by init()
private volatile boolean isInitialized = false;
private InputStream delegate;
/**
* Constructs a new instance that will lazily call upon {@code byteProvider} once bytes are
* requested. The provider will be called at most once; if it throws exception, the {@code
* LazyByteArrayInputStream} will be left in an inoperable state.
*
* <p>The caller MUST read or close the stream in order to avoid a possible resource leak in the
* byte provider.
*/
public LazyByteArrayInputStream(Callable<byte[]> byteProvider) {
this.byteProvider = byteProvider;
}
@Override
public int available() throws IOException {
if (!isInitialized) {
return 0; // available() returns the number of bytes that can be read without blocking
}
return delegate.available();
}
@Override
public void close() throws IOException {
init(); // give the underlying byte source an opportunity to close any open resources
}
@Override
public int read() throws IOException {
init();
return delegate.read();
}
@Override
public int read(byte[] b) throws IOException {
init();
return delegate.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
init();
return delegate.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
init();
return delegate.skip(n);
}
@Override
public void mark(int readlimit) {
if (!isInitialized) {
return; // stream hasn't been read, thus its position is 0, thus nothing to mark
}
delegate.mark(readlimit);
}
@Override
public boolean markSupported() {
return true; // the markSupported method of ByteArrayInputStream always returns true
}
@Override
public void reset() throws IOException {
init();
delegate.reset();
}
private void init() throws IOException {
if (!isInitialized) {
isInitialized = true; // don't try again if provider fails
try {
delegate = new ByteArrayInputStream(byteProvider.call());
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
}
}
}