com.tinspx.util.io.ChannelSource.java Source code

Java tutorial

Introduction

Here is the source code for com.tinspx.util.io.ChannelSource.java

Source

/* Copyright (C) 2013-2014 Ian Teune <ian.teune@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.tinspx.util.io;

import com.google.common.base.Ascii;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import static com.google.common.base.Preconditions.*;
import com.google.common.base.Throwables;
import com.google.common.hash.Funnels;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteProcessor;
import com.google.common.io.ByteSink;
import com.google.common.io.ByteSource;
import com.google.common.io.CharSource;
import com.google.common.io.Closer;
import com.google.common.io.Files;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.Arrays;
import javax.annotation.WillNotClose;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.experimental.Delegate;
import lombok.extern.slf4j.Slf4j;

/**
 * {@link ByteSource} extension that adds {@link #openChannel()}. Only one of
 * {@link #openStream()} or {@link #openChannel()} needs to be implemented.
 * 
 * @author Ian
 */
@Slf4j
public abstract class ChannelSource extends ByteSource {

    @Override
    public InputStream openStream() throws IOException {
        return ByteUtils.asInputStream(openChannel());
    }

    public ReadableByteChannel openChannel() throws IOException {
        return ByteUtils.asReadableByteChannel(openStream());
    }

    @Override
    public boolean contentEquals(ByteSource other) throws IOException {
        return contentEquals(this, other, true);
    }

    @Override
    public <T> T read(ByteProcessor<T> processor) throws IOException {
        ByteUtils.copy(this, processor, Long.MAX_VALUE);
        return processor.getResult();
    }

    @Override
    public byte[] read() throws IOException {
        return ByteUtils.toByteArray(this);
    }

    @Override
    public long copyTo(ByteSink sink) throws IOException {
        Closer closer = Closer.create();
        try {
            if (preferChannel() && sink instanceof ChannelSink && ((ChannelSink) sink).preferChannel()) {
                return copyTo(closer.register(((ChannelSink) sink).openChannel()));
            } else {
                OutputStream out = closer.register(sink.openStream());
                long total = copyTo(out);
                out.flush();
                return total;
            }
        } catch (Throwable t) {
            throw closer.rethrow(t);
        } finally {
            closer.close();
        }
    }

    @Override
    public long copyTo(OutputStream output) throws IOException {
        return ByteUtils.copy(this, output, Long.MAX_VALUE);
    }

    public long copyTo(WritableByteChannel output) throws IOException {
        return ByteUtils.copy(this, output, Long.MAX_VALUE);
    }

    /**
     * Determines if {@link #size() size()} can be cheaply determined. This is
     * generally true when the size can be determined without having to read the
     * entire stream or if reading the entire stream is not an expensive
     * operation. Defaults to {@code false}.
     */
    public boolean hasKnownSize() throws IOException {
        return false;
    }

    /**
     * Indicates if {@link #openChannel() openChannel()} is preferred over
     * {@link #openStream() openStream()}. If this method returns {@code true},
     * consumers of this {@code ChannelSource} are <i>encouraged</i> to use
     * {@code openChannel()} over {@code openStream()}, but are not required to
     * do so. This method provides a hint that {@code openChannel()} will likely
     * be more efficient than {@code openStream()}.
     * <p>
     * Defaults to {@code false},
     */
    public boolean preferChannel() throws IOException {
        return false;
    }

    public static ChannelSource empty() {
        return EmptyChannelSource.INSTANCE;
    }

    static final class EmptyChannelSource extends ChannelSource {
        static final ChannelSource INSTANCE = new EmptyChannelSource();

        @Override
        public ReadableByteChannel openChannel() throws IOException {
            return ByteUtils.emptyReadableByteChannel();
        }

        @Override
        public long copyTo(@NonNull OutputStream output) throws IOException {
            return 0;
        }

        @Override
        public byte[] read() throws IOException {
            return ByteUtils.EMPTY_ARRAY;
        }

        @Override
        public <T> T read(ByteProcessor<T> processor) throws IOException {
            return processor.getResult();
        }

        @Override
        public HashCode hash(HashFunction hashFunction) throws IOException {
            return hashFunction.hashBytes(ByteUtils.EMPTY_ARRAY);
        }

        @Override
        public boolean contentEquals(ByteSource other) throws IOException {
            return other.isEmpty();
        }

        @Override
        public InputStream openStream() throws IOException {
            return ByteUtils.emptyInputStream();
        }

        @Override
        public long size() throws IOException {
            return 0;
        }

        @Override
        public boolean isEmpty() throws IOException {
            return true;
        }

        @Override
        public ByteSource slice(long offset, long length) {
            checkArgument(offset >= 0 && length >= 0);
            return this;
        }

        @Override
        public InputStream openBufferedStream() throws IOException {
            return ByteUtils.emptyInputStream();
        }

        @Override
        public CharSource asCharSource(@NonNull Charset charset) {
            return CharSource.empty();
        }

        @Override
        public boolean hasKnownSize() {
            return true;
        }

        @Override
        public long copyTo(@NonNull WritableByteChannel output) throws IOException {
            return 0;
        }

        @Override
        public String toString() {
            return "ChannelSource.empty()";
        }
    }

    /**
     * Returns a {@code ChannelSource} representing {@code content} encoded as
     * {@code UTF-8}.
     */
    public static ChannelSource of(String content) {
        return of(content, Charsets.UTF_8);
    }

    public static ChannelSource of(String content, Charset charset) {
        return of(content.getBytes(charset));
    }

    /**
     * Wraps the {@code b} byte array as a ChannelSource. Same as
     * {@link #of(byte[], int, int) of(b, 0, b.length)}.
     * 
     * @see #of(byte[], int, int)
     * @param b the byte array to wrap
     * @return a ChannelSource representing the byte array {@code b}
     */
    public static ChannelSource of(byte[] b) {
        return new BAChannelSource(b);
    }

    /**
     * Wraps the {@code b} byte array as a ChannelSource. The ChannelSource
     * starts at index {@code off} and has a size of {@code len} bytes. The
     * returned ChannelSource overrides most ByteSource methods to take
     * advantage of more efficient implementations available using a byte array.
     * <p>
     * The ChannelSource returned from this method differs from
     * {@link ByteSource#wrap(byte[])} in the following ways: <br>
     * <ul>
     *   <li>A range of the array may be specified</li>
     *   <li>The byte array returned by {@link ByteSource#read()} returns the
     *       backing byte array, not a copy of the backing array. Therefore,
     *       changes to the returned array will be reflected in the ByteSource.
     *   </li>
     *   <li>The InputStreams are {@link BAInputStream}s, not
     *       {@link java.io.ByteArrayInputStream}. The only difference is that
     *       the methods are not synchronized.
     *   </li>
     *   <li>The {@link ByteSource#slice(long, long)} has the same behavior as
     *       calling this method on a sub-range of the byte array. This is more
     *       efficient than using the default ByteSource#SlicedByteSource
     *       ByteSource.
     *   </li>
     *   <li>{@link ByteSource#contentEquals(com.google.common.io.ByteSource)}
     *       and {@link ByteSource#copyTo(com.google.common.io.ByteSink)} are
     *       overridden to provide more efficient implementation.
     *   </li>
     * </ul>
     * 
     * @param b the byte array to wrap
     * @param off the first index into {@code b} that the ByteSource stream will
     * contain
     * @param len the length of the ByteSource stream starting at index
     * {@code off}
     * @return a ChannelSource representing a range of bytes in {@code b}
     */
    public static ChannelSource of(byte[] b, int off, int len) {
        return new BAChannelSource(b, off, len);
    }

    static class BAChannelSource extends ChannelSource {

        final byte[] buf;
        final int off, len;

        public BAChannelSource(byte[] buf) {
            this(buf, 0, buf.length);
        }

        public BAChannelSource(byte[] buf, int off, int len) {
            this.buf = checkNotNull(buf);
            checkPositionIndexes(off, off + len, buf.length);
            this.off = off;
            this.len = len;
        }

        @Override
        public InputStream openStream() {
            return new BAInputStream(buf, off, len);
        }

        @Override
        public InputStream openBufferedStream() {
            return openStream();
        }

        @Override
        public ReadableByteChannel openChannel() throws IOException {
            return new BAInputStream(buf, off, len);
        }

        @Override
        public boolean hasKnownSize() {
            return true;
        }

        @Override
        public boolean contentEquals(ByteSource other) throws IOException {
            return other == this || contentEquals(other, buf, off, len);
        }

        @Override
        public HashCode hash(HashFunction hashFunction) throws IOException {
            return hashFunction.hashBytes(buf, off, len);
        }

        @Override
        public <T> T read(ByteProcessor<T> processor) throws IOException {
            processor.processBytes(buf, off, len);
            return processor.getResult();
        }

        @Override
        public byte[] read() {
            return off == 0 && len == buf.length ? buf : Arrays.copyOfRange(buf, off, off + len);
        }

        @Override
        public long copyTo(OutputStream output) throws IOException {
            output.write(buf, off, len);
            return len;
        }

        @Override
        public long copyTo(WritableByteChannel output) throws IOException {
            ByteUtils.copy(ByteBuffer.wrap(buf, off, len), output);
            return len;
        }

        @Override
        public long size() throws IOException {
            return len;
        }

        @Override
        public boolean isEmpty() throws IOException {
            return len == 0;
        }

        @Override
        public ByteSource slice(long offset, long length) {
            checkArgument(offset >= 0, "offset (%s) may not be negative", offset);
            checkArgument(length >= 0, "length (%s) may not be negative", length);
            if (offset == 0 && length >= len) {
                return this;
            } else if (offset >= len || length == 0) {
                return empty();
            } else {
                return new BAChannelSource(buf, off + (int) offset, (int) Math.min(length, len - offset));
            }
        }

        @Override
        public String toString() {
            return "ChannelSource.of(" + truncateBase16(buf, off, len, 30) + ")";
        }
    }

    static String truncateBase16(byte[] bytes, int maxLength) {
        return truncateBase16(bytes, 0, bytes.length, maxLength);
    }

    static String truncateBase16(byte[] bytes, int off, int len, int maxLength) {
        checkPositionIndexes(off, off + len, bytes.length);
        checkArgument(maxLength >= 0);
        return Ascii.truncate(BaseEncoding.base16().encode(bytes, off, Math.min(len, maxLength + 1)),
                Math.max(3, maxLength), "...");
    }

    /**
    * Wraps a ByteBuffer as a ChannelSource. Opening a stream will return a
    * {@link ByteBufferInputStream}. A duplicate of the {@code buffer} argument
    * is made; therefore, use of the returned ByteSource will not affect the
    * state of the provided ByteBuffer.
    * <p>
    * The returned ChannelSource overrides most ByteSource methods to take
    * advantage of more efficient implementations available using a ByteBuffer.
    *
    * @param buffer the ByteBuffer to wrap
    * @return a ChannelSource representing ByteBuffer {@code buffer}
    */
    public static ChannelSource of(ByteBuffer buffer) {
        return new ByteBufferChannelSource(buffer.duplicate());
    }

    static class ByteBufferChannelSource extends ChannelSource {

        final ByteBuffer buf;

        public ByteBufferChannelSource(ByteBuffer buf) {
            this.buf = checkNotNull(buf);
        }

        @Override
        public InputStream openStream() throws IOException {
            return new ByteBufferInputStream(buf.duplicate());
        }

        @Override
        public InputStream openBufferedStream() throws IOException {
            return openStream();
        }

        @Override
        public ReadableByteChannel openChannel() throws IOException {
            return new ByteBufferInputStream(buf.duplicate());
        }

        @Override
        public boolean hasKnownSize() {
            return true;
        }

        @Override
        public boolean contentEquals(ByteSource other) throws IOException {
            if (other == this) {
                return true;
            }
            if (buf.hasArray()) {
                return contentEquals(other, buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
            } else {
                return ByteUtils.contentEquals(other, buf.duplicate());
            }
        }

        @Override
        public HashCode hash(HashFunction hashFunction) throws IOException {
            if (buf.hasArray()) {
                return hashFunction.hashBytes(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
            } else {
                Hasher hasher = hashFunction.newHasher(buf.remaining());
                ByteUtils.copy(buf.duplicate(), Funnels.asOutputStream(hasher));
                return hasher.hash();
            }
        }

        @Override
        public <T> T read(ByteProcessor<T> processor) throws IOException {
            ByteUtils.copy(buf.duplicate(), processor);
            return processor.getResult();
        }

        @Override
        public byte[] read() {
            return ByteUtils.toByteArray(buf);
        }

        @Override
        public long copyTo(OutputStream output) throws IOException {
            ByteUtils.copy(buf.duplicate(), output);
            return buf.remaining();
        }

        @Override
        public long copyTo(WritableByteChannel output) throws IOException {
            ByteUtils.copy(buf.duplicate(), output);
            return buf.remaining();
        }

        @Override
        public long size() {
            return buf.remaining();
        }

        @Override
        public boolean isEmpty() {
            return !buf.hasRemaining();
        }

        @Override
        public ByteSource slice(long offset, long length) {
            checkArgument(offset >= 0, "offset (%s) may not be negative", offset);
            checkArgument(length >= 0, "length (%s) may not be negative", length);
            if (offset == 0 && length >= buf.remaining()) {
                return this;
            } else if (offset >= buf.remaining() || length == 0) {
                return EmptyChannelSource.INSTANCE;
            } else {
                ByteBuffer slice = buf.duplicate();
                slice.position(slice.position() + (int) offset);
                slice.limit(slice.position() + (int) Math.min(length, slice.remaining()));
                return new ByteBufferChannelSource(slice);
            }
        }

        @Override
        public String toString() {
            return "ChannelSource.of(" + buf + ")";
        }
    }

    /**
     * Returns a {@code ByteSource} that will memoize/cache the contents of
     * {@code source}. The {@code source} stream will be read only once. All
     * subsequent reads will be read from an in memory cache of the stream
     * contents. The {@code source} {@code ByteSource} is not read immediately,
     * but is cached lazily on the first call to
     * {@link ByteSource#openStream()}.
     *
     * @param source the ByteSource to cache
     * @return a memoizing {@code ByteSource} wrapping {@code source}
     */
    public static ChannelSource memoize(ByteSource source) {
        if (source instanceof BAChannelSource || source instanceof ByteBufferChannelSource
                || source instanceof MemoizeChannelSource) {
            return (ChannelSource) source;
        } else {
            return new MemoizeChannelSource(of(source));
        }
    }

    static class MemoizeChannelSource extends ForwardingChannelSource {

        ChannelSource delegate;
        volatile boolean cached;

        public MemoizeChannelSource(ChannelSource source) {
            this.delegate = checkNotNull(source);
        }

        @Override
        protected ChannelSource delegate() {
            if (cached) {
                return delegate;
            }
            try {
                return delegateThrow();
            } catch (IOException ex) {
                return delegate;
            }
        }

        @Override
        protected ChannelSource delegateThrow() throws IOException {
            if (!cached) {
                synchronized (this) {
                    if (!cached) {
                        delegate = new BAChannelSource(delegate.read());
                        cached = true;
                    }
                }
            }
            return delegate;
        }
    }

    public static ChannelSource of(ByteSource delegate) {
        return delegate instanceof ChannelSource ? (ChannelSource) delegate : new ByteSourceChannelSource(delegate);
    }

    static class ByteSourceChannelSource extends ChannelSource {
        @Delegate
        final ByteSource delegate;

        public ByteSourceChannelSource(ByteSource delegate) {
            this.delegate = checkNotNull(delegate);
        }

        @Override
        public String toString() {
            return delegate.toString();
        }
    }

    public static ByteSource of(final @NonNull URL url) {
        return new ChannelSource() {
            @Override
            public InputStream openStream() throws IOException {
                return url.openStream();
            }

            @Override
            public String toString() {
                return String.format("ChannelSource.of(%s)", url);
            }
        };
    }

    /**
     * Wraps {@code file} as a {@code ChannelSource} that uses
     * {@link #fromFileInputStream()} to acquire a {@link FileChannel} from the
     * {@code file}.
     * <p>
     * Differs from {@link Files#asByteSource(File)} in that {@link FileChannel}
     * methods are used whenever possible when reading or copying bytes.
     * {@link FileChannel#transferFrom(ReadableByteChannel, long, long) transferFrom}
     * and
     * {@link FileChannel#transferTo(long, long, WritableByteChannel) transferTo}
     * are used when possible. This will possibly provide better performance
     * especially when used in conjunction with {@link ChannelSink#of(File)}.
     * 
     * @see #of(File, Function)
     */
    public static ChannelSource of(File file) {
        return new FileChannelSource(file, FIS_FUNCTION);
    }

    /**
     * Wraps {@code file} as a {@code ChannelSource} that uses
     * {@code fileChannelFunction} to acquire a {@link FileChannel} from the
     * {@code file}. Any {@code IOException} encountered by
     * {@code fileChannelFunction} when creating the {@code FileChannel} should
     * be wrapped and rethrown as a {@code RuntimeException}.
     * 
     * @see #of(File)
     */
    public static ChannelSource of(File file, Function<? super File, ? extends FileChannel> fileChannelFunction) {
        return new FileChannelSource(file, fileChannelFunction);
    }

    static class FileChannelSource extends ChannelSource {

        final File file;
        final Function<? super File, ? extends FileChannel> function;

        public FileChannelSource(File file, Function<? super File, ? extends FileChannel> function) {
            this.file = checkNotNull(file);
            this.function = checkNotNull(function);
        }

        @Override
        public FileChannel openChannel() throws IOException {
            return apply(file, function);
        }

        @Override
        public FileInputStream openStream() throws IOException {
            return new FileInputStream(file);
        }

        @Override
        public boolean hasKnownSize() {
            return true;
        }

        @Override
        public boolean preferChannel() {
            return true;
        }

        @Override
        public long size() throws IOException {
            if (!file.isFile()) {
                throw new FileNotFoundException(file + " is not a file");
            }
            return file.length();
        }

        @Override
        public long copyTo(OutputStream output) throws IOException {
            if (output instanceof FileOutputStream) {
                return copyTo(((FileOutputStream) output).getChannel());
            } else if (output instanceof WritableByteChannel) {
                return copyTo((WritableByteChannel) output);
            } else {
                return super.copyTo(output);
            }
        }

        @Override
        public long copyTo(WritableByteChannel to) throws IOException {
            Closer closer = Closer.create();
            try {
                return copy(closer.register(openChannel()), to);
            } catch (Throwable t) {
                throw closer.rethrow(t);
            } finally {
                closer.close();
            }
        }

        @Override
        public String toString() {
            return String.format("ChannelSource.of(%s, %s)", file, function);
        }
    }

    /**
     * Applies {@code fileChannelFunction} to {@code file}. Any
     * {@code IOException} wrapped in a {@code RuntimeException} will be
     * correctly rethrown.
     */
    public static FileChannel apply(File file, Function<? super File, ? extends FileChannel> fileChannelFunction)
            throws IOException {
        try {
            return fileChannelFunction.apply(file);
        } catch (RuntimeException ex) {
            Throwables.propagateIfInstanceOf(ex.getCause(), IOException.class);
            throw ex;
        }
    }

    static long copy(@WillNotClose FileChannel from, @WillNotClose WritableByteChannel to) throws IOException {
        final ByteBuffer buf = ByteUtils.threadLocalBuffer8K();
        final long initPos = from.position();
        long pos = initPos;
        for (;;) {
            pos += from.transferTo(pos, Long.MAX_VALUE, to);
            from.position(pos);
            if (from.read(buf) < 0) {
                return pos - initPos;
            }
            buf.flip();
            if (buf.hasRemaining()) {
                pos += buf.remaining();
                ByteUtils.copy(buf, to);
            }
            buf.clear();
        }
    }

    static long copy(@WillNotClose ReadableByteChannel from, @WillNotClose FileChannel to) throws IOException {
        final ByteBuffer buf = ByteUtils.threadLocalBuffer8K();
        final long initPos = to.position();
        long pos = initPos;
        for (;;) {
            pos += to.transferFrom(from, pos, Long.MAX_VALUE);
            to.position(pos);
            if (from.read(buf) < 0) {
                return pos - initPos;
            }
            buf.flip();
            if (buf.hasRemaining()) {
                pos += buf.remaining();
                ByteUtils.copy(buf, to);
            }
            buf.clear();
        }
    }

    /**
     * Creates the {@link FileChannel} from a {@link FileInputStream}.
     * <p>
     * {@code new FileInputStream(input).getChannel()}
     */
    public static Function<File, FileChannel> fromFileInputStream() {
        return FIS_FUNCTION;
    }

    static final Function<File, FileChannel> FIS_FUNCTION = new Function<File, FileChannel>() {
        @Override
        public FileChannel apply(File input) {
            boolean error = false;
            Closer closer = Closer.create();
            try {
                return closer.register(new FileInputStream(input)).getChannel();
            } catch (Throwable t) {
                error = true;
                try {
                    throw closer.rethrow(t);
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            } finally {
                if (error) {
                    try {
                        closer.close();
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                }
            }
        }

        @Override
        public String toString() {
            return "FileInputStream(File).getChannel()";
        }
    };

    /**
     * Creates the {@link FileChannel} from a {@link FileOutputStream}. The
     * {@code FileOutputStream} is <i>not</i> opened in append mode.
     * <p>
     * {@code new FileOutputStream(input, false).getChannel()}
     */
    public static Function<File, FileChannel> fromFileOutputStream() {
        return FOSFunction.NORMAL;
    }

    /**
     * Creates the {@link FileChannel} from a {@link FileOutputStream} opened
     * in append mode if {@code append} is {@code true}.
     * <p>
     * {@code new FileOutputStream(input, append).getChannel()}
     */
    public static Function<File, FileChannel> fromFileOutputStream(boolean append) {
        return append ? FOSFunction.APPEND : FOSFunction.NORMAL;
    }

    @AllArgsConstructor
    static class FOSFunction implements Function<File, FileChannel> {
        static final FOSFunction NORMAL = new FOSFunction(false);
        static final FOSFunction APPEND = new FOSFunction(true);

        final boolean append;

        @Override
        public FileChannel apply(File input) {
            boolean error = false;
            Closer closer = Closer.create();
            try {
                return closer.register(new FileOutputStream(input, append)).getChannel();
            } catch (Throwable t) {
                error = true;
                try {
                    throw closer.rethrow(t);
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            } finally {
                if (error) {
                    try {
                        closer.close();
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                }
            }
        }

        @Override
        public String toString() {
            return String.format("FileOutputStream(File, %b).getChannel()", append);
        }
    }

    /**
     * Creates the {@link FileChannel} from a {@link RandomAccessFile} opened
     * in "rw" mode.
     * <p>
     * {@code new RandomAccessFile(input, "rw").getChannel()}
     */
    public static Function<File, FileChannel> fromRandomAccessFile() {
        return RAFFunction.RW;
    }

    /**
     * Creates the {@link FileChannel} from a {@link RandomAccessFile} opened in
     * the specified mode. {@code mode} must be one of "r", "rw", "rws", "rwd".
     * <p>
     * {@code new RandomAccessFile(input, mode).getChannel()}
     */
    public static Function<File, FileChannel> fromRandomAccessFile(@NonNull String mode) {
        if ("r".equals(mode)) {
            return RAFFunction.R;
        } else if ("rw".equals(mode)) {
            return RAFFunction.RW;
        } else if ("rws".equals(mode)) {
            return RAFFunction.RWS;
        } else if ("rwd".equals(mode)) {
            return RAFFunction.RWD;
        } else {
            throw new IllegalArgumentException("invalid mode: " + mode);
        }
    }

    static class RAFFunction implements Function<File, FileChannel> {
        static final RAFFunction R = new RAFFunction("r");
        static final RAFFunction RW = new RAFFunction("rw");
        static final RAFFunction RWS = new RAFFunction("rws");
        static final RAFFunction RWD = new RAFFunction("rwd");

        final String mode;

        public RAFFunction(String mode) {
            this.mode = checkNotNull(mode);
        }

        @Override
        public FileChannel apply(File input) {
            boolean error = false;
            Closer closer = Closer.create();
            try {
                return closer.register(new RandomAccessFile(input, mode)).getChannel();
            } catch (Throwable t) {
                error = true;
                try {
                    throw closer.rethrow(t);
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            } finally {
                if (error) {
                    try {
                        closer.close();
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                }
            }
        }

        @Override
        public String toString() {
            return String.format("RandomAccessFile(File, \"%s\").getChannel()", mode);
        }
    }

    @ByteUtils.ThreadLocalArray(8192)
    public static boolean contentEquals(ByteSource source, byte[] bytes, int off, int len) throws IOException {
        if (source instanceof BAChannelSource) {
            BAChannelSource o = (BAChannelSource) source;
            return ByteUtils.rangeEquals(bytes, off, len, o.buf, o.off, o.len);
        } else if (source instanceof ByteArrayChannelSource) {
            byte[] b = ((ByteArrayChannelSource) source).read();
            return ByteUtils.rangeEquals(bytes, off, len, b, 0, b.length);
        } else if (source instanceof BAOutputStream.BAOSChannelSource) {
            return ((BAOutputStream.BAOSChannelSource) source).contentEquls(bytes, off, len);
        } else if (source instanceof ByteBufferChannelSource) {
            ByteBuffer b = ((ByteBufferChannelSource) source).buf;
            if (b.hasArray()) {
                return ByteUtils.rangeEquals(bytes, off, len, b.array(), b.arrayOffset() + b.position(),
                        b.remaining());
            }
        } else if (source instanceof MemoizeChannelSource) {
            return contentEquals(((MemoizeChannelSource) source).delegateThrow(), bytes, off, len);
        }
        return ByteUtils.contentEqualsImpl(source, bytes, off, len);
    }

    @ByteUtils.ThreadLocalArray(8192)
    public static boolean contentEquals(ByteSource source1, ByteSource source2) throws IOException {
        return contentEquals(source1, source2, true);
    }

    @ByteUtils.ThreadLocalArray(8192)
    static boolean contentEquals(ByteSource source, ByteSource other, boolean first) throws IOException {
        if (source instanceof BAChannelSource) {
            BAChannelSource o = (BAChannelSource) source;
            return contentEquals(other, o.buf, o.off, o.len);
        } else if (source instanceof ByteArrayChannelSource) {
            byte[] b = ((ByteArrayChannelSource) source).read();
            return contentEquals(other, b, 0, b.length);
        } else if (source instanceof BAOutputStream.BAOSChannelSource) {
            return source.contentEquals(other);
        } else if (source instanceof ByteBufferChannelSource) {
            ByteBuffer b = ((ByteBufferChannelSource) source).buf;
            if (b.hasArray()) {
                return contentEquals(other, b.array(), b.arrayOffset() + b.position(), b.remaining());
            }
        } else if (source instanceof MemoizeChannelSource) {
            return contentEquals(((MemoizeChannelSource) source).delegateThrow(), other, first);
        }
        if (first) {
            return contentEquals(other, source, false);
        } else {
            return ByteUtils.contentEqualsImpl(other, source);
        }
    }
}