com.joyent.manta.util.ContinuingInputStream.java Source code

Java tutorial

Introduction

Here is the source code for com.joyent.manta.util.ContinuingInputStream.java

Source

/*
 * Copyright (c) 2018, Joyent, Inc. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package com.joyent.manta.util;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;

import static org.apache.commons.lang3.Validate.notNull;

/**
 * {@link InputStream} implementation that allows for the attaching and detaching of delegated (source) streams. This
 * allows classes which embed us to continue supplying data to their callers transparently regardless of whether the
 * data is coming from the original source {@link InputStream} or a continuation thereof.
 * <p>
 * Inpsired by {@link com.joyent.manta.client.multipart.MultipartOutputStream}.
 *
 * <strong>This class is not thread-safe.</strong>
 *
 * @author <a href="https://github.com/tjcelaya">Tomas Celaya</a>
 * @since 3.2.3
 */
public class ContinuingInputStream extends InputStream {

    private static final Logger LOG = LoggerFactory.getLogger(ContinuingInputStream.class);

    /**
     * EOF marker.
     */
    private static final int EOF = -1;

    /**
     * We don't want to keep trying to read from the backing stream (or accept new continuations) if we've actually seen
     * EOF.
     */
    private boolean eofSeen;

    /**
     * Whether the stream has been closed yet. We don't want to continue delivering reads or accept new continuations if
     * this stream has been closed.
     */
    private boolean closed;

    /**
     * Total number of bytes read so far.
     */
    private long bytesRead;

    /**
     * The delegate stream. We replace this when the user calls {@link #continueWith(InputStream)}. When this field is
     * null we are in a "dirty" state and will not handle read calls.
     */
    private InputStream wrapped;

    /**
     * Construct a new stream that reads from {@code initial} and can handle continuations and the lifecycle of both.
     *
     * @param initial the stream from which to start reading
     */
    public ContinuingInputStream(final InputStream initial) {
        this.eofSeen = false;
        this.closed = false;
        this.bytesRead = 0;
        this.wrapped = Objects.requireNonNull(initial);
    }

    protected InputStream getWrapped() {
        return this.wrapped;
    }

    /**
     * Attaches the next stream.
     *
     * @param next stream to switch to as a backing stream
     */
    public void continueWith(final InputStream next) {
        notNull(next, "InputStream must not be null");

        if (this.eofSeen) {
            throw new IllegalStateException("Already reached end of source stream, refusing to set new source");
        }

        if (this.closed) {
            throw new IllegalStateException("Stream is closed, refusing to set new source");
        }

        if (this.wrapped == next) {
            throw new IllegalArgumentException("Current and next stream are the same");
        }

        if (this.wrapped != null) {
            this.closeAndDiscardWrapped();
        }

        this.wrapped = next;
    }

    /**
     * Retrieve the total number of bytes read through this stream.
     *
     * @return total bytes read
     */
    public long getBytesRead() {
        return this.bytesRead;
    }

    @Override
    public int read() throws IOException {
        ensureReady();

        if (this.eofSeen) {
            return EOF;
        }

        final int b = this.wrapped.read();

        if (b != EOF) {
            this.bytesRead += b;
        } else {
            this.eofSeen = true;
        }

        return b;
    }

    @Override
    public int read(final byte[] b) throws IOException {
        ensureReady();

        if (this.eofSeen) {
            return EOF;
        }

        final int n = this.wrapped.read(b);

        if (n != EOF) {
            this.bytesRead += n;
        } else {
            this.eofSeen = true;
        }

        return n;
    }

    @Override
    public int read(final byte[] b, final int off, final int len) throws IOException {
        ensureReady();

        if (this.eofSeen) {
            return EOF;
        }

        final int n = this.wrapped.read(b, off, len);

        if (n != EOF) {
            this.bytesRead += n;
        } else {
            this.eofSeen = true;
        }

        return n;
    }

    @Override
    public long skip(final long n) throws IOException {
        ensureReady();

        final long s = this.wrapped.skip(n);

        if (0 < s) {
            this.bytesRead += s;
        }

        return s;
    }

    @Override
    public int available() throws IOException {
        ensureReady();

        return this.wrapped.available();
    }

    @Override
    public void mark(final int readlimit) {
        throw new UnsupportedOperationException("ContinuingInputStream does not support mark/reset");
    }

    @Override
    public void reset() throws IOException {
        throw new UnsupportedOperationException("ContinuingInputStream does not support mark/reset");
    }

    @Override
    public boolean markSupported() {
        return false;
    }

    @Override
    public void close() throws IOException {
        if (this.closed) {
            return;
        }

        this.closed = true;

        if (this.wrapped != null) {
            wrapped.close();
        }
    }

    @SuppressWarnings("checkstyle:JavadocMethod")
    private void ensureReady() {
        if (this.closed) {
            throw new IllegalStateException("Attempted to read from a closed InputStream");
        }

        if (this.wrapped == null) {
            throw new IllegalStateException("Read called before setting a continuation stream");
        }
    }

    @SuppressWarnings("checkstyle:JavadocMethod")
    private void closeAndDiscardWrapped() {
        IOUtils.closeQuietly(this.wrapped);
        this.wrapped = null;
    }

}