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.Function; import static com.google.common.base.Preconditions.*; import com.google.common.io.ByteSink; import com.google.common.io.Closer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import javax.annotation.WillNotClose; import lombok.NonNull; import lombok.experimental.Delegate; /** * {@link ByteSink} extension that adds {@link #openChannel()}. Only once of * {@link #openStream()} or {@link #openChannel()} needs to be implemented. * * @see BAOutputStream#asByteSink() * @author Ian */ public abstract class ChannelSink extends ByteSink { @Override public OutputStream openStream() throws IOException { return ByteUtils.asOutputStream(openChannel()); } public WritableByteChannel openChannel() throws IOException { return ByteUtils.asWritableByteChannel(openStream()); } @Override public void write(byte[] bytes) throws IOException { write(bytes, 0, bytes.length); } public void write(byte[] bytes, int off, int len) throws IOException { ByteUtils.copy(bytes, off, len, this); } @Override public long writeFrom(InputStream input) throws IOException { return ByteUtils.copy(input, this, Long.MAX_VALUE); } public long writeFrom(ReadableByteChannel input) throws IOException { return ByteUtils.copy(input, this, Long.MAX_VALUE); } /** * Indicates if {@link #openChannel() openChannel()} is preferred over * {@link #openStream() openStream()}. If this method returns {@code true}, * consumers of this {@code ChannelSink} 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() { return false; } /** * Wraps {@code file} as a {@code ChannelSink} that uses * {@link ChannelSource#fromFileOutputStream()} to acquire a * {@link FileChannel} from the {@code file}. The {@code file} is <i>not</i> * opened in append mode. */ public static ChannelSink of(File file) { return new FileChannelSink(file, false, ChannelSource.fromFileOutputStream()); } /** * Wraps {@code file} as a {@code ChannelSink} that uses * {@link ChannelSource#fromFileOutputStream()} to acquire a * {@link FileChannel} from the {@code file}. If {@code append} is true, * the {@code file} is opened in append mode. */ public static ChannelSink of(File file, boolean append) { return new FileChannelSink(file, append, ChannelSource.fromFileOutputStream()); } /** * Wraps {@code file} as a {@code ChannelSink} that uses * {@code fileChannelFunction} to acquire a {@link FileChannel} from the * {@code file}. The {@code file} is <i>not</i> * opened in append mode. {@code fileChannelFunction} must not create a * {@code FileChannel} in append mode; if it does, use * {@link #of(File, boolean, Function)} with {@code append} {@code true} * instead. */ public static ChannelSink of(File file, Function<? super File, ? extends FileChannel> fileChannelFunction) { return new FileChannelSink(file, false, fileChannelFunction); } /** * Wraps {@code file} as a {@code ChannelSink} that uses * {@code fileChannelFunction} to acquire a {@link FileChannel} from the * {@code file}. If {@code append} is true, the {@code file} is opened in * append mode. If {@code fileChannelFunction} creates a {@code FileChannel} * in append mode, {@code append} must be {@code true}. */ public static ChannelSink of(File file, boolean append, Function<? super File, ? extends FileChannel> fileChannelFunction) { return new FileChannelSink(file, append, fileChannelFunction); } static class FileChannelSink extends ChannelSink implements Function<File, FileChannel> { final File file; final boolean append; final Function<? super File, ? extends FileChannel> function; public FileChannelSink(File file, boolean append, Function<? super File, ? extends FileChannel> function) { this.file = checkNotNull(file); this.append = append; this.function = checkNotNull(function); } @Override public FileOutputStream openStream() throws IOException { return new FileOutputStream(file, append); } @Override public FileChannel openChannel() throws IOException { return ChannelSource.apply(file, this); } @Override public boolean preferChannel() { return true; } @Override @SuppressWarnings("null") public FileChannel apply(File input) { boolean error = false; Closer closer = Closer.create(); try { FileChannel fc = closer.register(function.apply(file)); if (append) { final long size = fc.size(); if (fc.position() < size) { fc.position(size); } } return fc; } 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 long writeFrom(InputStream input) throws IOException { if (input instanceof FileInputStream) { return writeFrom(((FileInputStream) input).getChannel()); } else if (input instanceof ReadableByteChannel) { return writeFrom((ReadableByteChannel) input); } else { return super.writeFrom(input); } } @Override public long writeFrom(@WillNotClose ReadableByteChannel from) throws IOException { Closer closer = Closer.create(); try { return ChannelSource.copy(from, closer.register(openChannel())); } catch (Throwable t) { throw closer.rethrow(t); } finally { closer.close(); } } @Override public String toString() { return String.format("ChannelSink.of(%s, append=%b, %s)", file, append, function); } } /** * Returns a {@code ChannelSource} that discards all input. */ public static ChannelSink nullSink() { return NullChannelSink.INSTANCE; } static class NullChannelSink extends ChannelSink { static final ChannelSink INSTANCE = new NullChannelSink(); @Override public void write(@NonNull byte[] bytes) throws IOException { } @Override public void write(@NonNull byte[] bytes, int off, int len) throws IOException { checkPositionIndexes(off, off + len, bytes.length); } @Override public WritableByteChannel openChannel() throws IOException { return ByteUtils.nullWritableByteChannel(); } @Override public OutputStream openStream() throws IOException { return ByteUtils.nullOutputStream(); } @Override public OutputStream openBufferedStream() throws IOException { return ByteUtils.nullOutputStream(); } @Override public String toString() { return " ChannelSink.nullSink()"; } } public static ChannelSink of(ByteSink sink) { return sink instanceof ChannelSink ? (ChannelSink) sink : new ByteSinkChannelSink(sink); } static class ByteSinkChannelSink extends ChannelSink { @Delegate final ByteSink sink; public ByteSinkChannelSink(ByteSink sink) { this.sink = checkNotNull(sink); } @Override public String toString() { return sink.toString(); } } }