Java tutorial
/* 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); } } }