io.jsync.file.impl.DefaultAsyncFile.java Source code

Java tutorial

Introduction

Here is the source code for io.jsync.file.impl.DefaultAsyncFile.java

Source

/*
 * Copyright (c) 2011-2013 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */

package io.jsync.file.impl;

import io.jsync.AsyncResult;
import io.jsync.Handler;
import io.jsync.buffer.Buffer;
import io.jsync.file.AsyncFile;
import io.jsync.file.FileSystemException;
import io.jsync.impl.AsyncInternal;
import io.jsync.impl.BlockingAction;
import io.jsync.impl.DefaultContext;
import io.jsync.impl.DefaultFutureResult;
import io.jsync.logging.Logger;
import io.jsync.logging.impl.LoggerFactory;
import io.netty.buffer.ByteBuf;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;

/**
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class DefaultAsyncFile implements AsyncFile {

    public static final int BUFFER_SIZE = 8192;
    private static final Logger log = LoggerFactory.getLogger(AsyncFile.class);
    private final AsyncInternal async;
    private final AsynchronousFileChannel ch;
    private final DefaultContext context;
    private boolean closed;
    private Runnable closedDeferred;
    private long writesOutstanding;

    private Handler<Throwable> exceptionHandler;
    private Handler<Void> drainHandler;

    private long writePos;
    private int maxWrites = 128 * 1024; // TODO - we should tune this for best performance
    private int lwm = maxWrites / 2;

    private boolean paused;
    private Handler<Buffer> dataHandler;
    private Handler<Void> endHandler;
    private long readPos;
    private boolean readInProgress;

    DefaultAsyncFile(final AsyncInternal async, final String path, String perms, final boolean read,
            final boolean write, final boolean createNew, final boolean flush, final DefaultContext context) {
        if (!read && !write) {
            throw new FileSystemException("Cannot open file for neither reading nor writing");
        }
        this.async = async;
        Path file = Paths.get(path);
        HashSet<OpenOption> options = new HashSet<>();
        if (read)
            options.add(StandardOpenOption.READ);
        if (write)
            options.add(StandardOpenOption.WRITE);
        if (createNew)
            options.add(StandardOpenOption.CREATE);
        if (flush)
            options.add(StandardOpenOption.DSYNC);
        try {
            if (perms != null) {
                FileAttribute<?> attrs = PosixFilePermissions
                        .asFileAttribute(PosixFilePermissions.fromString(perms));
                ch = AsynchronousFileChannel.open(file, options, async.getBackgroundPool(), attrs);
            } else {
                ch = AsynchronousFileChannel.open(file, options, async.getBackgroundPool());
            }
        } catch (IOException e) {
            throw new FileSystemException(e);
        }
        this.context = context;
    }

    @Override
    public void close() {
        closeInternal(null);
    }

    @Override
    public void close(Handler<AsyncResult<Void>> handler) {
        closeInternal(handler);
    }

    @Override
    public AsyncFile write(Buffer buffer, long position, final Handler<AsyncResult<Void>> handler) {
        check();
        final ByteBuf buf = buffer.getByteBuf();
        if (buf.nioBufferCount() > 1) {
            final Iterator<ByteBuffer> buffers = Arrays.asList(buf.nioBuffers()).iterator();
            doWrite(buffers, position, handler);
        } else {
            ByteBuffer bb = buf.nioBuffer();
            doWrite(bb, position, bb.limit(), handler);
        }
        return this;
    }

    private void doWrite(final Iterator<ByteBuffer> buffers, final long position,
            final Handler<AsyncResult<Void>> handler) {
        final ByteBuffer b = buffers.next();
        final int limit = b.limit();
        doWrite(b, position, limit, new Handler<AsyncResult<Void>>() {
            @Override
            public void handle(AsyncResult<Void> event) {
                if (event.failed()) {
                    handler.handle(event);
                } else {
                    if (buffers.hasNext()) {
                        doWrite(buffers, position + limit, handler);
                    } else {
                        handler.handle(event);
                    }
                }
            }
        });
    }

    @Override
    public AsyncFile read(Buffer buffer, int offset, long position, int length,
            Handler<AsyncResult<Buffer>> handler) {
        check();
        ByteBuffer bb = ByteBuffer.allocate(length);
        doRead(buffer, offset, bb, position, handler);
        return this;
    }

    @Override
    public AsyncFile write(Buffer buffer) {
        check();
        final int length = buffer.length();
        Handler<AsyncResult<Void>> handler = new Handler<AsyncResult<Void>>() {

            public void handle(AsyncResult<Void> deferred) {
                if (deferred.succeeded()) {
                    checkContext();
                    checkDrained();
                    if (writesOutstanding == 0 && closedDeferred != null) {
                        closedDeferred.run();
                    }
                } else {
                    handleException(deferred.cause());
                }
            }
        };

        ByteBuf buf = buffer.getByteBuf();
        if (buf.nioBufferCount() > 1) {
            final Iterator<ByteBuffer> buffers = Arrays.asList(buf.nioBuffers()).iterator();
            doWrite(buffers, writePos, handler);
        } else {
            ByteBuffer bb = buf.nioBuffer();
            doWrite(bb, writePos, bb.limit(), handler);
        }
        writePos += length;
        return this;
    }

    private void checkDrained() {
        if (drainHandler != null && writesOutstanding <= lwm) {
            Handler<Void> handler = drainHandler;
            drainHandler = null;
            handler.handle(null);
        }
    }

    @Override
    public AsyncFile setWriteQueueMaxSize(int maxSize) {
        check();
        this.maxWrites = maxSize;
        this.lwm = maxWrites / 2;
        return this;
    }

    @Override
    public boolean writeQueueFull() {
        check();
        return writesOutstanding >= maxWrites;
    }

    @Override
    public AsyncFile drainHandler(Handler<Void> handler) {
        check();
        this.drainHandler = handler;
        checkDrained();
        return this;
    }

    @Override
    public AsyncFile exceptionHandler(Handler<Throwable> handler) {
        check();
        this.exceptionHandler = handler;
        return this;
    }

    private void handleException(Throwable t) {
        if (exceptionHandler != null && t instanceof Exception) {
            exceptionHandler.handle(t);
        } else {
            log.error("Unhandled exception", t);

        }
    }

    private void doRead() {
        if (!readInProgress) {
            readInProgress = true;
            Buffer buff = new Buffer(BUFFER_SIZE);
            read(buff, 0, readPos, BUFFER_SIZE, new Handler<AsyncResult<Buffer>>() {

                public void handle(AsyncResult<Buffer> ar) {
                    if (ar.succeeded()) {
                        readInProgress = false;
                        Buffer buffer = ar.result();
                        if (buffer.length() == 0) {
                            // Empty buffer represents end of file
                            handleEnd();
                        } else {
                            readPos += buffer.length();
                            handleData(buffer);
                            if (!paused && dataHandler != null) {
                                doRead();
                            }
                        }
                    } else {
                        handleException(ar.cause());
                    }
                }
            });
        }
    }

    @Override
    public AsyncFile dataHandler(Handler<Buffer> handler) {
        check();
        this.dataHandler = handler;
        if (dataHandler != null && !paused && !closed) {
            doRead();
        }
        return this;
    }

    @Override
    public AsyncFile endHandler(Handler<Void> handler) {
        check();
        this.endHandler = handler;
        return this;
    }

    @Override
    public AsyncFile pause() {
        check();
        paused = true;
        return this;
    }

    @Override
    public AsyncFile resume() {
        check();
        if (paused && !closed) {
            paused = false;
            if (dataHandler != null) {
                doRead();
            }
        }
        return this;
    }

    private void handleData(Buffer buffer) {
        if (dataHandler != null) {
            checkContext();
            dataHandler.handle(buffer);
        }
    }

    private void handleEnd() {
        if (endHandler != null) {
            checkContext();
            endHandler.handle(null);
        }
    }

    @Override
    public AsyncFile flush() {
        doFlush(null);
        return this;
    }

    @Override
    public AsyncFile flush(Handler<AsyncResult<Void>> handler) {
        doFlush(handler);
        return this;
    }

    private void doFlush(Handler<AsyncResult<Void>> handler) {
        checkClosed();
        checkContext();
        new BlockingAction<Void>(async, handler) {
            public Void action() {
                try {
                    ch.force(false);
                    return null;
                } catch (IOException e) {
                    throw new FileSystemException(e);
                }
            }
        }.run();
    }

    private void doWrite(final ByteBuffer buff, final long position, final long toWrite,
            final Handler<AsyncResult<Void>> handler) {
        writesOutstanding += toWrite;
        writeInternal(buff, position, handler);
    }

    private void writeInternal(final ByteBuffer buff, final long position,
            final Handler<AsyncResult<Void>> handler) {

        ch.write(buff, position, null, new java.nio.channels.CompletionHandler<Integer, Object>() {

            public void completed(Integer bytesWritten, Object attachment) {

                long pos = position;

                if (buff.hasRemaining()) {
                    // partial write
                    pos += bytesWritten;
                    // resubmit
                    writeInternal(buff, pos, handler);
                } else {
                    // It's been fully written
                    context.execute(new Runnable() {
                        public void run() {
                            writesOutstanding -= buff.limit();
                            handler.handle(new DefaultFutureResult<Void>().setResult(null));
                        }
                    });
                }
            }

            public void failed(Throwable exc, Object attachment) {
                if (exc instanceof Exception) {
                    final Exception e = (Exception) exc;
                    context.execute(new Runnable() {
                        public void run() {
                            handler.handle(new DefaultFutureResult<Void>().setResult(null));
                        }
                    });
                } else {
                    log.error("Error occurred", exc);
                }
            }
        });
    }

    private void doRead(final Buffer writeBuff, final int offset, final ByteBuffer buff, final long position,
            final Handler<AsyncResult<Buffer>> handler) {

        ch.read(buff, position, null, new java.nio.channels.CompletionHandler<Integer, Object>() {

            final DefaultFutureResult<Buffer> result = new DefaultFutureResult<>();
            long pos = position;

            private void done() {
                context.execute(new Runnable() {
                    public void run() {
                        buff.flip();
                        writeBuff.setBytes(offset, buff);
                        result.setResult(writeBuff).setHandler(handler);
                    }
                });
            }

            public void completed(Integer bytesRead, Object attachment) {
                if (bytesRead == -1) {
                    //End of file
                    done();
                } else if (buff.hasRemaining()) {
                    // partial read
                    pos += bytesRead;
                    // resubmit
                    doRead(writeBuff, offset, buff, pos, handler);
                } else {
                    // It's been fully written
                    done();
                }
            }

            public void failed(final Throwable t, Object attachment) {
                context.execute(new Runnable() {
                    public void run() {
                        result.setFailure(t).setHandler(handler);
                    }
                });
            }
        });
    }

    private void check() {
        checkClosed();
        checkContext();
    }

    private void checkClosed() {
        if (closed) {
            throw new IllegalStateException("File handle is closed");
        }
    }

    private void checkContext() {
        if (!async.getContext().equals(context)) {
            throw new IllegalStateException("AsyncFile must only be used in the context that created it, expected: "
                    + context + " actual " + async.getContext());
        }
    }

    private void doClose(Handler<AsyncResult<Void>> handler) {
        DefaultFutureResult<Void> res = new DefaultFutureResult<>();
        try {
            ch.close();
            res.setResult(null);
        } catch (IOException e) {
            res.setFailure(e);
        }
        if (handler != null) {
            handler.handle(res);
        }
    }

    private void closeInternal(final Handler<AsyncResult<Void>> handler) {
        check();

        closed = true;

        if (writesOutstanding == 0) {
            doClose(handler);
        } else {
            closedDeferred = new Runnable() {
                public void run() {
                    doClose(handler);
                }
            };
        }
    }

}