com.moilioncircle.redis.replicator.io.AsyncBufferedInputStream.java Source code

Java tutorial

Introduction

Here is the source code for com.moilioncircle.redis.replicator.io.AsyncBufferedInputStream.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.moilioncircle.redis.replicator.io;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Jingqi Xu
 * @author andyqzb
 * @since 2.1.0
 */
public final class AsyncBufferedInputStream extends InputStream implements Runnable {
    //
    private static final Log logger = LogFactory.getLog(AsyncBufferedInputStream.class);

    //
    private static final int DEFAULT_CAPACITY = 2 * 1024 * 1024;

    //
    private final Thread worker;
    private final InputStream is;
    private volatile IOException exception;
    private final ByteRingBuffer ringBuffer;
    private final ThreadFactory threadFactory;
    private final ReentrantLock lock = new ReentrantLock(false);
    private final AtomicBoolean closed = new AtomicBoolean(false);
    private final Condition bufferNotFull = this.lock.newCondition();
    private final Condition bufferNotEmpty = this.lock.newCondition();

    /*
     *
     */
    public AsyncBufferedInputStream(InputStream is) {
        this(is, DEFAULT_CAPACITY);
    }

    public AsyncBufferedInputStream(InputStream is, int size) {
        this(is, size, Executors.defaultThreadFactory());
    }

    public AsyncBufferedInputStream(InputStream is, int size, ThreadFactory tf) {
        //
        this.is = is;
        this.threadFactory = tf;
        this.ringBuffer = new ByteRingBuffer(size);

        //
        this.worker = this.threadFactory.newThread(this);
        this.worker.start();
    }

    /*
     *
     */
    public void run() {
        try {
            final byte[] buffer = new byte[512 * 1024];
            while (!this.closed.get()) {
                //
                int r = this.is.read(buffer, 0, buffer.length);
                if (r < 0)
                    throw new EOFException();

                //
                int offset = 0;
                while (r > 0) {
                    final int w = write(buffer, offset, r);
                    r -= w;
                    offset += w;
                }
            }
        } catch (IOException e) {
            this.exception = e;
        } catch (Exception e) {
            logger.error("failed to transfer data", e);
        } finally {
            if (!this.closed.get()) {
                try {
                    close();
                } catch (IOException e) {
                    logger.error("failed to close is", e);
                }
            }
        }
    }

    /*
     *
     */
    @Override
    public int available() throws IOException {
        return this.ringBuffer.size();
    }

    @Override
    public void close() throws IOException {
        //
        if (!this.closed.compareAndSet(false, true))
            return;

        //
        try {
            this.is.close();
        } finally {
            this.lock.lock();
            try {
                this.bufferNotFull.signalAll();
                this.bufferNotEmpty.signalAll();
            } finally {
                this.lock.unlock();
            }
        }
    }

    @Override
    public int read() throws IOException {
        this.lock.lock();
        try {
            //
            while (this.ringBuffer.isEmpty()) {
                if (this.exception != null)
                    throw this.exception;
                this.bufferNotEmpty.awaitUninterruptibly();
                if (this.closed.get())
                    throw new EOFException();
            }

            //
            final int r = this.ringBuffer.read();
            this.bufferNotFull.signal();
            return r;
        } finally {
            this.lock.unlock();
        }
    }

    @Override
    public int read(byte b[], int off, int len) throws IOException {
        this.lock.lock();
        try {
            //
            while (this.ringBuffer.isEmpty()) {
                if (this.exception != null)
                    throw this.exception;
                this.bufferNotEmpty.awaitUninterruptibly();
                if (this.closed.get())
                    throw new EOFException();
            }

            //
            final int r = this.ringBuffer.read(b, off, len);
            this.bufferNotFull.signal();
            return r;
        } finally {
            this.lock.unlock();
        }
    }

    public int write(byte b[], int off, int len) throws IOException {
        this.lock.lock();
        try {
            //
            while (this.ringBuffer.isFull()) {
                this.bufferNotFull.awaitUninterruptibly();
                if (this.closed.get())
                    throw new EOFException();
            }

            //
            final int w = this.ringBuffer.write(b, off, len);
            this.bufferNotEmpty.signal();
            return w;
        } finally {
            this.lock.unlock();
        }
    }

    /*
     *
     */
    private final class ByteRingBuffer {
        //
        private int size;
        private int head; // Write
        private int tail; // Read
        private final byte[] buffer;

        /*
         *
         */
        public ByteRingBuffer(int capacity) {
            this.buffer = new byte[capacity];
        }

        /*
         *
         */
        public int size() {
            return this.size;
        }

        public boolean isEmpty() {
            return this.size == 0;
        }

        public boolean isFull() {
            return this.size == this.buffer.length;
        }

        /*
         *
         */
        public int read() {
            //
            final int r = this.buffer[this.tail] & 0xFF;

            //
            this.tail = (this.tail + 1) % this.buffer.length;
            this.size -= 1;
            return r;
        }

        public int read(byte b[], int off, int len) {
            //
            final int r = Math.min(this.size, len);
            if (this.head > this.tail) {
                System.arraycopy(this.buffer, this.tail, b, off, r);
            } else {
                final int r1 = Math.min(this.buffer.length - this.tail, r);
                System.arraycopy(this.buffer, this.tail, b, off, r1);
                if (r1 < r)
                    System.arraycopy(this.buffer, 0, b, off + r1, r - r1);
            }

            //
            this.tail = (this.tail + r) % this.buffer.length;
            this.size -= r;
            return r;
        }

        public int write(byte b[], int off, int len) {
            //
            final int w = Math.min(this.buffer.length - this.size, len);
            if (this.head < this.tail) {
                System.arraycopy(b, off, this.buffer, this.head, w);
            } else {
                final int w1 = Math.min(this.buffer.length - this.head, w);
                System.arraycopy(b, off, this.buffer, this.head, w1);
                if (w1 < w)
                    System.arraycopy(b, off + w1, this.buffer, 0, w - w1);
            }

            //
            this.head = (this.head + w) % this.buffer.length;
            this.size += w;
            return w;
        }
    }
}