/*
* JBoss, Home of Professional Open Source
*
* Copyright 2011 Red Hat, Inc. and/or its affiliates, and individual
* contributors as indicated by the @author tags.
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.xnio;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.xnio.channels.AcceptingChannel;
import org.xnio.channels.Channels;
import org.xnio.channels.ConnectedChannel;
import org.xnio.channels.StreamSinkChannel;
import org.xnio.channels.StreamSourceChannel;
import org.xnio.channels.SuspendableReadChannel;
import org.xnio.channels.SuspendableWriteChannel;
import org.xnio.channels.WritableMessageChannel;
import static org.xnio._private.Messages.listenerMsg;
import static org.xnio._private.Messages.msg;
/**
* Channel listener utility methods.
*
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
@SuppressWarnings("unused")
public final class ChannelListeners {
private static final ChannelListener<Channel> NULL_LISTENER = new ChannelListener<Channel>() {
public void handleEvent(final Channel channel) {
}
public String toString() {
return "Null channel listener";
}
};
private static final ChannelListener.Setter<?> NULL_SETTER = new ChannelListener.Setter<Channel>() {
public void set(final ChannelListener<? super Channel> channelListener) {
}
public String toString() {
return "Null channel listener setter";
}
};
private static ChannelListener<Channel> CLOSING_CHANNEL_LISTENER = new ChannelListener<Channel>() {
public void handleEvent(final Channel channel) {
IoUtils.safeClose(channel);
}
public String toString() {
return "Closing channel listener";
}
};
private ChannelListeners() {
}
/**
* Invoke a channel listener on a given channel, logging any errors.
*
* @param channel the channel
* @param channelListener the channel listener
* @param <T> the channel type
* @return {@code true} if the listener completed successfully, or {@code false} if it failed
*/
public static <T extends Channel> boolean invokeChannelListener(T channel, ChannelListener<? super T> channelListener) {
if (channelListener != null) try {
listenerMsg.tracef("Invoking listener %s on channel %s", channelListener, channel);
channelListener.handleEvent(channel);
} catch (Throwable t) {
listenerMsg.listenerException(t);
return false;
}
return true;
}
/**
* Invoke a channel listener on a given channel, logging any errors, using the given executor.
*
* @param executor the executor
* @param channel the channel
* @param channelListener the channel listener
* @param <T> the channel type
*/
public static <T extends Channel> void invokeChannelListener(Executor executor, T channel, ChannelListener<? super T> channelListener) {
try {
executor.execute(getChannelListenerTask(channel, channelListener));
} catch (RejectedExecutionException ree) {
invokeChannelListener(channel, channelListener);
}
}
/**
* Safely invoke a channel exception handler, logging any errors.
*
* @param channel the channel
* @param exceptionHandler the exception handler
* @param exception the exception to pass in
* @param <T> the exception type
*/
public static <T extends Channel> void invokeChannelExceptionHandler(final T channel, final ChannelExceptionHandler<? super T> exceptionHandler, final IOException exception) {
try {
exceptionHandler.handleException(channel, exception);
} catch (Throwable t) {
listenerMsg.exceptionHandlerException(t);
}
}
/**
* Get a task which invokes the given channel listener on the given channel.
*
* @param channel the channel
* @param channelListener the channel listener
* @param <T> the channel type
* @return the runnable task
*/
public static <T extends Channel> Runnable getChannelListenerTask(final T channel, final ChannelListener<? super T> channelListener) {
return new Runnable() {
public String toString() {
return "Channel listener task for " + channel + " -> " + channelListener;
}
public void run() {
invokeChannelListener(channel, channelListener);
}
};
}
/**
* Get a task which invokes the given channel listener on the given channel via its setter.
*
* @param channel the channel
* @param setter the setter for the channel listener
* @param <T> the channel type
* @return the runnable task
*/
public static <T extends Channel> Runnable getChannelListenerTask(final T channel, final ChannelListener.SimpleSetter<T> setter) {
return new Runnable() {
public String toString() {
return "Channel listener task for " + channel + " -> " + setter;
}
public void run() {
invokeChannelListener(channel, setter.get());
}
};
}
/**
* Get a channel listener which closes the channel when notified.
*
* @return the channel listener
*/
public static ChannelListener<Channel> closingChannelListener() {
return CLOSING_CHANNEL_LISTENER;
}
/**
* Get a channel listener which closes the given resource when notified.
*
* @param resource the resource to close
* @return the channel listener
*/
public static ChannelListener<Channel> closingChannelListener(final Closeable resource) {
return new ChannelListener<Channel>() {
public void handleEvent(final Channel channel) {
IoUtils.safeClose(resource);
}
public String toString() {
return "Closing channel listener for " + resource;
}
};
}
/**
* Get a channel listener which closes the given resources when notified.
*
* @param resources the resources to close
* @return the channel listener
*/
public static ChannelListener<Channel> closingChannelListener(final Closeable... resources) {
return new ChannelListener<Channel>() {
public void handleEvent(final Channel channel) {
IoUtils.safeClose(resources);
}
public String toString() {
return "Closing channel listener for " + resources.length + " items";
}
};
}
/**
* Get a channel listener which closes the given resource when notified.
*
* @param delegate the listener to call next
* @param resource the resource to close
* @return the channel listener
*/
public static <T extends Channel> ChannelListener<T> closingChannelListener(final ChannelListener<T> delegate, final Closeable resource) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
IoUtils.safeClose(resource);
delegate.handleEvent(channel);
}
public String toString() {
return "Closing channel listener for " + resource + " -> " + delegate;
}
};
}
/**
* Get a channel listener which closes the given resource when notified.
*
* @param delegate the listener to call next
* @param resources the resource to close
* @return the channel listener
*/
public static <T extends Channel> ChannelListener<T> closingChannelListener(final ChannelListener<T> delegate, final Closeable... resources) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
IoUtils.safeClose(resources);
delegate.handleEvent(channel);
}
public String toString() {
return "Closing channel listener for " + resources.length + " items -> " + delegate;
}
};
}
/**
* Get a channel listener which does nothing.
*
* @return the null channel listener
*/
public static ChannelListener<Channel> nullChannelListener() {
return NULL_LISTENER;
}
/**
* Get a channel exception handler which closes the channel upon exception.
*
* @return the channel exception handler
*/
public static ChannelExceptionHandler<Channel> closingChannelExceptionHandler() {
return CLOSING_HANDLER;
}
/**
* Create an open listener adapter which automatically accepts connections and invokes an open listener.
*
* @param openListener the channel open listener
* @param <C> the connected channel type
* @return a channel accept listener
*/
public static <C extends ConnectedChannel> ChannelListener<AcceptingChannel<C>> openListenerAdapter(final ChannelListener<? super C> openListener) {
if (openListener == null) {
throw msg.nullParameter("openListener");
}
return new ChannelListener<AcceptingChannel<C>>() {
public void handleEvent(final AcceptingChannel<C> channel) {
try {
final C accepted = channel.accept();
if (accepted != null) {
invokeChannelListener(accepted, openListener);
}
} catch (IOException e) {
listenerMsg.acceptFailed(channel, e);
}
}
public String toString() {
return "Accepting listener for " + openListener;
}
};
}
/**
* Get a setter based on an atomic reference field updater. Used by channel implementations to avoid having to
* define an anonymous class for each listener field.
*
* @param channel the channel
* @param updater the updater
* @param <T> the channel type
* @param <C> the holding class
* @return the setter
* @deprecated Not recommended as a security manager will enforce unreasonable restrictions on the updater.
*/
@Deprecated
public static <T extends Channel, C> ChannelListener.Setter<T> getSetter(final C channel, final AtomicReferenceFieldUpdater<C, ChannelListener> updater) {
return new ChannelListener.Setter<T>() {
public void set(final ChannelListener<? super T> channelListener) {
updater.set(channel, channelListener);
}
public String toString() {
return "Atomic reference field updater setter for " + updater;
}
};
}
/**
* Get a setter based on an atomic reference. Used by channel implementations to avoid having to
* define an anonymous class for each listener field.
*
* @param atomicReference the atomic reference
* @param <T> the channel type
* @return the setter
*/
public static <T extends Channel> ChannelListener.Setter<T> getSetter(final AtomicReference<ChannelListener<? super T>> atomicReference) {
return new ChannelListener.Setter<T>() {
public void set(final ChannelListener<? super T> channelListener) {
atomicReference.set(channelListener);
}
public String toString() {
return "Atomic reference setter (currently=" + atomicReference.get() + ")";
}
};
}
/**
* Get a channel listener setter which delegates to the given target setter with a different channel type.
*
* @param target the target setter
* @param realChannel the channel to send in to the listener
* @param <T> the real channel type
* @return the delegating setter
*/
public static <T extends Channel> ChannelListener.Setter<T> getDelegatingSetter(final ChannelListener.Setter<? extends Channel> target, final T realChannel) {
return target == null ? null : new DelegatingSetter<T>(target, realChannel);
}
/**
* Get a channel listener setter which does nothing.
*
* @param <T> the channel type
* @return a setter which does nothing
*/
@SuppressWarnings({ "unchecked" })
public static <T extends Channel> ChannelListener.Setter<T> nullSetter() {
return (ChannelListener.Setter<T>) NULL_SETTER;
}
/**
* Get a channel listener which executes a delegate channel listener via an executor. If an exception occurs
* submitting the task, the associated channel is closed.
*
* @param listener the listener to invoke
* @param executor the executor with which to invoke the listener
* @param <T> the channel type
* @return a delegating channel listener
*/
public static <T extends Channel> ChannelListener<T> executorChannelListener(final ChannelListener<T> listener, final Executor executor) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
try {
executor.execute(getChannelListenerTask(channel, listener));
} catch (RejectedExecutionException e) {
listenerMsg.executorSubmitFailed(e, channel);
IoUtils.safeClose(channel);
}
}
public String toString() {
return "Executor channel listener -> " + listener;
}
};
}
/**
* A flushing channel listener. Flushes the channel and then calls the delegate listener. Calls the exception
* handler if an exception occurs. The delegate listener should ensure that the channel write listener is appropriately set.
* <p>
* The returned listener is stateless and may be reused on any number of channels concurrently or sequentially.
*
* @param delegate the delegate listener
* @param exceptionHandler the exception handler
* @param <T> the channel type
* @return the flushing channel listener
*/
public static <T extends SuspendableWriteChannel> ChannelListener<T> flushingChannelListener(final ChannelListener<? super T> delegate, final ChannelExceptionHandler<? super T> exceptionHandler) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
final boolean result;
try {
result = channel.flush();
} catch (IOException e) {
channel.suspendWrites();
invokeChannelExceptionHandler(channel, exceptionHandler, e);
return;
}
if (result) {
Channels.setWriteListener(channel, delegate);
invokeChannelListener(channel, delegate);
} else {
Channels.setWriteListener(channel, this);
channel.resumeWrites();
}
}
public String toString() {
return "Flushing channel listener -> " + delegate;
}
};
}
/**
* A write shutdown channel listener. Shuts down and flushes the channel and calls the delegate listener. Calls
* the exception handler if an exception occurs. When the delegate listener is called, the channel's write side will be shut down and flushed.
* The delegate listener should ensure that the channel write listener is appropriately set.
*
* @param delegate the delegate listener
* @param exceptionHandler the exception handler
* @param <T> the channel type
* @return the channel listener
*/
public static <T extends SuspendableWriteChannel> ChannelListener<T> writeShutdownChannelListener(final ChannelListener<? super T> delegate, final ChannelExceptionHandler<? super T> exceptionHandler) {
final ChannelListener<T> flushingListener = flushingChannelListener(delegate, exceptionHandler);
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
try {
channel.shutdownWrites();
} catch (IOException e) {
invokeChannelExceptionHandler(channel, exceptionHandler, e);
return;
}
invokeChannelListener(channel, flushingListener);
}
public String toString() {
return "Write shutdown channel listener -> " + delegate;
}
};
}
/**
* A writing channel listener. Writes the buffer to the channel and then calls the delegate listener. Calls the exception
* handler if an exception occurs. The delegate listener should ensure that the channel write listener is appropriately set.
* <p>
* The returned listener is stateful and will not execute properly if reused.
*
* @param pooled the buffer to write
* @param delegate the delegate listener
* @param exceptionHandler the exception handler
* @param <T> the channel type
* @return the writing channel listener
*/
public static <T extends StreamSinkChannel> ChannelListener<T> writingChannelListener(final Pooled<ByteBuffer> pooled, final ChannelListener<? super T> delegate, final ChannelExceptionHandler<? super T> exceptionHandler) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
final ByteBuffer buffer = pooled.getResource();
int result;
boolean ok = false;
do {
try {
result = channel.write(buffer);
ok = true;
} catch (IOException e) {
channel.suspendWrites();
pooled.free();
invokeChannelExceptionHandler(channel, exceptionHandler, e);
return;
} finally {
if (! ok) {
pooled.free();
}
}
if (result == 0) {
Channels.setWriteListener(channel, this);
channel.resumeWrites();
return;
}
} while (buffer.hasRemaining());
pooled.free();
invokeChannelListener(channel, delegate);
}
public String toString() {
return "Writing channel listener -> " + delegate;
}
};
}
/**
* A sending channel listener. Writes the buffer to the channel and then calls the delegate listener. Calls the exception
* handler if an exception occurs. The delegate listener should ensure that the channel write listener is appropriately set.
* <p>
* The returned listener is stateful and will not execute properly if reused.
*
* @param pooled the buffer to send
* @param delegate the delegate listener
* @param exceptionHandler the exception handler
* @param <T> the channel type
* @return the sending channel listener
*/
public static <T extends WritableMessageChannel> ChannelListener<T> sendingChannelListener(final Pooled<ByteBuffer> pooled, final ChannelListener<? super T> delegate, final ChannelExceptionHandler<? super T> exceptionHandler) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
final ByteBuffer buffer = pooled.getResource();
boolean free = true;
try {
if (! (free = channel.send(buffer))) {
Channels.setWriteListener(channel, this);
channel.resumeWrites();
return;
}
} catch (IOException e) {
channel.suspendWrites();
pooled.free();
invokeChannelExceptionHandler(channel, exceptionHandler, e);
return;
} finally {
if (free) pooled.free();
}
invokeChannelListener(channel, delegate);
}
public String toString() {
return "Sending channel listener -> " + delegate;
}
};
}
/**
* A file-sending channel listener. Writes the file to the channel and then calls the delegate listener. Calls the exception
* handler if an exception occurs. The delegate listener should ensure that the channel write listener is appropriately set.
* <p>
* The returned listener is stateful and will not execute properly if reused.
*
* @param source the file to read from
* @param position the position in the source file to read from
* @param count the number of bytes to read
* @param delegate the listener to call when the file is sent
* @param exceptionHandler the exception handler to call if a problem occurs
* @param <T> the channel type
* @return the channel listener
*/
public static <T extends StreamSinkChannel> ChannelListener<T> fileSendingChannelListener(final FileChannel source, final long position, final long count, final ChannelListener<? super T> delegate, final ChannelExceptionHandler<? super T> exceptionHandler) {
if (count == 0L) {
return delegatingChannelListener(delegate);
}
return new ChannelListener<T>() {
private long p = position;
private long cnt = count;
public void handleEvent(final T channel) {
long result;
long cnt = this.cnt;
long p = this.p;
try {
do {
try {
result = channel.transferFrom(source, p, cnt);
} catch (IOException e) {
invokeChannelExceptionHandler(channel, exceptionHandler, e);
return;
}
if (result == 0L) {
Channels.setWriteListener(channel, this);
channel.resumeWrites();
return;
}
p += result;
cnt -= result;
} while (cnt > 0L);
// cnt is 0
invokeChannelListener(channel, delegate);
return;
} finally {
this.p = p;
this.cnt = cnt;
}
}
public String toString() {
return "File sending channel listener (" + source + ") -> " + delegate;
}
};
}
/**
* A file-receiving channel listener. Writes the file from the channel and then calls the delegate listener. Calls the exception
* handler if an exception occurs. The delegate listener should ensure that the channel read listener is appropriately set.
* <p>
* The returned listener is stateful and will not execute properly if reused.
*
* @param target the file to write to
* @param position the position in the target file to write to
* @param count the number of bytes to write
* @param delegate the listener to call when the file is sent
* @param exceptionHandler the exception handler to call if a problem occurs
* @param <T> the channel type
* @return the channel listener
*/
public static <T extends StreamSourceChannel> ChannelListener<T> fileReceivingChannelListener(final FileChannel target, final long position, final long count, final ChannelListener<? super T> delegate, final ChannelExceptionHandler<? super T> exceptionHandler) {
if (count == 0L) {
return delegatingChannelListener(delegate);
}
return new ChannelListener<T>() {
private long p = position;
private long cnt = count;
public void handleEvent(final T channel) {
long result;
long cnt = this.cnt;
long p = this.p;
try {
do {
try {
result = channel.transferTo(p, cnt, target);
} catch (IOException e) {
invokeChannelExceptionHandler(channel, exceptionHandler, e);
return;
}
if (result == 0L) {
Channels.setReadListener(channel, this);
channel.resumeReads();
return;
}
p += result;
cnt -= result;
} while (cnt > 0L);
// cnt = 0
invokeChannelListener(channel, delegate);
return;
} finally {
this.p = p;
this.cnt = cnt;
}
}
public String toString() {
return "File receiving channel listener (" + target + ") -> " + delegate;
}
};
}
/**
* A delegating channel listener which passes an event to another listener of the same or a super type.
*
* @param delegate the delegate channel listener
* @param <T> the channel type
* @return the listener
*/
public static <T extends Channel> ChannelListener<T> delegatingChannelListener(final ChannelListener<? super T> delegate) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
invokeChannelListener(channel, delegate);
}
public String toString() {
return "Delegating channel listener -> " + delegate;
}
};
}
/**
* A delegating channel listener which passes an event to the listener stored in the given setter.
*
* @param channel the channel to pass in
* @param setter the channel listener setter
* @param <C> the listener channel type
* @param <T> the passed in channel type
* @return the listener
*/
public static <C extends Channel, T extends Channel> ChannelListener<C> delegatingChannelListener(final T channel, final ChannelListener.SimpleSetter<T> setter) {
return new SetterDelegatingListener<C, T>(setter, channel);
}
/**
* A write-suspending channel listener. The returned listener will suspend writes when called. Useful for chaining
* writing listeners to a flush listener to this listener. The delegate listener should ensure that the channel write listener is appropriately set.
*
* @param delegate the delegate channel listener
* @return the suspending channel listener
*/
public static <T extends SuspendableWriteChannel> ChannelListener<T> writeSuspendingChannelListener(final ChannelListener<? super T> delegate) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
channel.suspendWrites();
invokeChannelListener(channel, delegate);
}
public String toString() {
return "Write-suspending channel listener -> " + delegate;
}
};
}
/**
* A read-suspending channel listener. The returned listener will suspend read when called.
* The delegate listener should ensure that the channel read listener is appropriately set.
*
* @param delegate the delegate channel listener
* @return the suspending channel listener
*/
public static <T extends SuspendableReadChannel> ChannelListener<T> readSuspendingChannelListener(final ChannelListener<? super T> delegate) {
return new ChannelListener<T>() {
public void handleEvent(final T channel) {
channel.suspendReads();
invokeChannelListener(channel, delegate);
}
public String toString() {
return "Read-suspending channel listener -> " + delegate;
}
};
}
static final class TransferListener<I extends StreamSourceChannel, O extends StreamSinkChannel> implements ChannelListener<Channel> {
private final Pooled<ByteBuffer> pooledBuffer;
private final I source;
private final O sink;
private final ChannelListener<? super I> sourceListener;
private final ChannelListener<? super O> sinkListener;
private final ChannelExceptionHandler<? super O> writeExceptionHandler;
private final ChannelExceptionHandler<? super I> readExceptionHandler;
private long count;
private volatile int state;
TransferListener(final long count, final Pooled<ByteBuffer> pooledBuffer, final I source, final O sink, final ChannelListener<? super I> sourceListener, final ChannelListener<? super O> sinkListener, final ChannelExceptionHandler<? super O> writeExceptionHandler, final ChannelExceptionHandler<? super I> readExceptionHandler, final int state) {
this.count = count;
this.pooledBuffer = pooledBuffer;
this.source = source;
this.sink = sink;
this.sourceListener = sourceListener;
this.sinkListener = sinkListener;
this.writeExceptionHandler = writeExceptionHandler;
this.readExceptionHandler = readExceptionHandler;
this.state = state;
}
public void handleEvent(final Channel channel) {
final ByteBuffer buffer = pooledBuffer.getResource();
int state = this.state;
// always read after and write before state
long count = this.count;
long lres;
int ires;
switch (state) {
case 0: {
// read listener
for (;;) {
try {
lres = source.transferTo(count, buffer, sink);
} catch (IOException e) {
readFailed(e);
return;
}
if (lres == 0 && !buffer.hasRemaining()) {
this.count = count;
return;
}
if (lres == -1) {
// possibly unexpected EOF
if (count == Long.MAX_VALUE) {
// it's OK; just be done
done();
return;
} else {
readFailed(new EOFException());
return;
}
}
if (count != Long.MAX_VALUE) {
count -= lres;
}
while (buffer.hasRemaining()) {
try {
ires = sink.write(buffer);
} catch (IOException e) {
writeFailed(e);
return;
}
if (count != Long.MAX_VALUE) {
count -= ires;
}
if (ires == 0) {
this.count = count;
this.state = 1;
source.suspendReads();
sink.resumeWrites();
return;
}
}
if (count == 0) {
done();
return;
}
}
}
case 1: {
// write listener
for (;;) {
while (buffer.hasRemaining()) {
try {
ires = sink.write(buffer);
} catch (IOException e) {
writeFailed(e);
return;
}
if (count != Long.MAX_VALUE) {
count -= ires;
}
if (ires == 0) {
return;
}
}
try {
lres = source.transferTo(count, buffer, sink);
} catch (IOException e) {
readFailed(e);
return;
}
if (lres == 0 && !buffer.hasRemaining()) {
this.count = count;
this.state = 0;
sink.suspendWrites();
source.resumeReads();
return;
}
if (lres == -1) {
// possibly unexpected EOF
if (count == Long.MAX_VALUE) {
// it's OK; just be done
done();
return;
} else {
readFailed(new EOFException());
return;
}
}
if (count != Long.MAX_VALUE) {
count -= lres;
}
if (count == 0) {
done();
return;
}
}
}
}
}
private void writeFailed(final IOException e) {
try {
source.suspendReads();
sink.suspendWrites();
invokeChannelExceptionHandler(sink, writeExceptionHandler, e);
} finally {
pooledBuffer.free();
}
}
private void readFailed(final IOException e) {
try {
source.suspendReads();
sink.suspendWrites();
invokeChannelExceptionHandler(source, readExceptionHandler, e);
} finally {
pooledBuffer.free();
}
}
private void done() {
try {
final ChannelListener<? super I> sourceListener = this.sourceListener;
final ChannelListener<? super O> sinkListener = this.sinkListener;
final I source = this.source;
final O sink = this.sink;
Channels.setReadListener(source, sourceListener);
if (sourceListener == null) {
source.suspendReads();
} else {
source.wakeupReads();
}
Channels.setWriteListener(sink, sinkListener);
if (sinkListener == null) {
sink.suspendWrites();
} else {
sink.wakeupWrites();
}
} finally {
pooledBuffer.free();
}
}
public String toString() {
return "Transfer channel listener (" + source + " to " + sink + ") -> (" + sourceListener + " and " + sinkListener + ")";
}
}
/**
* Initiate a low-copy transfer between two stream channels. The pool should be a direct buffer pool for best
* performance. The channels will be closed when the transfer completes or if there is an error.
*
* @param source the source channel
* @param sink the target channel
* @param pool the pool from which the transfer buffer should be allocated
* @param <I> the source stream type
* @param <O> the sink stream type
*/
public static <I extends StreamSourceChannel, O extends StreamSinkChannel> void initiateTransfer(final I source, final O sink, Pool<ByteBuffer> pool) {
initiateTransfer(Long.MAX_VALUE, source, sink, CLOSING_CHANNEL_LISTENER, CLOSING_CHANNEL_LISTENER, CLOSING_HANDLER, CLOSING_HANDLER, pool);
}
/**
* Initiate a low-copy transfer between two stream channels. The pool should be a direct buffer pool for best
* performance.
*
* @param count the number of bytes to transfer, or {@link Long#MAX_VALUE} to transfer all remaining bytes
* @param source the source channel
* @param sink the target channel
* @param sourceListener the source listener to set and call when the transfer is complete, or {@code null} to clear the listener at that time
* @param sinkListener the target listener to set and call when the transfer is complete, or {@code null} to clear the listener at that time
* @param readExceptionHandler the read exception handler to call if an error occurs during a read operation
* @param writeExceptionHandler the write exception handler to call if an error occurs during a write operation
* @param pool the pool from which the transfer buffer should be allocated
*/
public static <I extends StreamSourceChannel, O extends StreamSinkChannel> void initiateTransfer(long count, final I source, final O sink, final ChannelListener<? super I> sourceListener, final ChannelListener<? super O> sinkListener, final ChannelExceptionHandler<? super I> readExceptionHandler, final ChannelExceptionHandler<? super O> writeExceptionHandler, Pool<ByteBuffer> pool) {
if (pool == null) {
throw msg.nullParameter("pool");
}
final Pooled<ByteBuffer> allocated = pool.allocate();
boolean free = true;
try {
final ByteBuffer buffer = allocated.getResource();
long transferred;
for(;;) {
try {
transferred = source.transferTo(count, buffer, sink);
} catch (IOException e) {
invokeChannelExceptionHandler(source, readExceptionHandler, e);
return;
}
if (transferred == 0 && !buffer.hasRemaining()) {
break;
}
if (transferred == -1) {
if (count == Long.MAX_VALUE) {
Channels.setReadListener(source, sourceListener);
if (sourceListener == null) {
source.suspendReads();
} else {
source.wakeupReads();
}
Channels.setWriteListener(sink, sinkListener);
if (sinkListener == null) {
sink.suspendWrites();
} else {
sink.wakeupWrites();
}
} else {
source.suspendReads();
sink.suspendWrites();
invokeChannelExceptionHandler(source, readExceptionHandler, new EOFException());
}
return;
}
if (count != Long.MAX_VALUE) {
count -= transferred;
}
while (buffer.hasRemaining()) {
final int res;
try {
res = sink.write(buffer);
} catch (IOException e) {
invokeChannelExceptionHandler(sink, writeExceptionHandler, e);
return;
}
if (res == 0) {
// write first listener
final TransferListener<I, O> listener = new TransferListener<I, O>(count, allocated, source, sink, sourceListener, sinkListener, writeExceptionHandler, readExceptionHandler, 1);
source.suspendReads();
source.getReadSetter().set(listener);
sink.getWriteSetter().set(listener);
sink.resumeWrites();
free = false;
return;
} else if (count != Long.MAX_VALUE) {
count -= res;
}
}
if (count == 0) {
//we are done
Channels.setReadListener(source, sourceListener);
if (sourceListener == null) {
source.suspendReads();
} else {
source.wakeupReads();
}
Channels.setWriteListener(sink, sinkListener);
if (sinkListener == null) {
sink.suspendWrites();
} else {
sink.wakeupWrites();
}
return;
}
}
// read first listener
final TransferListener<I, O> listener = new TransferListener<I, O>(count, allocated, source, sink, sourceListener, sinkListener, writeExceptionHandler, readExceptionHandler, 0);
sink.suspendWrites();
sink.getWriteSetter().set(listener);
source.getReadSetter().set(listener);
source.resumeReads();
free = false;
return;
} finally {
if (free) allocated.free();
}
}
/**
* Create a channel listener which automatically drains the given number of bytes from the channel and then calls
* a listener.
*
* @param bytes the number of bytes to drain, or {@code Long.MAX_VALUE} to drain the channel completely
* @param finishListener the listener to call when the drain is complete
* @param exceptionHandler the handler to call if the drain fails
* @param <T> the channel type
* @return the channel listener
*/
public static <T extends StreamSourceChannel> ChannelListener<T> drainListener(long bytes, ChannelListener<? super T> finishListener, ChannelExceptionHandler<? super T> exceptionHandler) {
return new DrainListener<T>(finishListener, exceptionHandler, bytes);
}
private static class DelegatingSetter<T extends Channel> implements ChannelListener.Setter<T> {
private final ChannelListener.Setter<? extends Channel> setter;
private final T realChannel;
DelegatingSetter(final ChannelListener.Setter<? extends Channel> setter, final T realChannel) {
this.setter = setter;
this.realChannel = realChannel;
}
public void set(final ChannelListener<? super T> channelListener) {
setter.set(channelListener == null ? null : new DelegatingChannelListener<T>(channelListener, realChannel));
}
public String toString() {
return "Delegating setter -> " + setter;
}
}
private static class DelegatingChannelListener<T extends Channel> implements ChannelListener<Channel> {
private final ChannelListener<? super T> channelListener;
private final T realChannel;
public DelegatingChannelListener(final ChannelListener<? super T> channelListener, final T realChannel) {
this.channelListener = channelListener;
this.realChannel = realChannel;
}
public void handleEvent(final Channel channel) {
invokeChannelListener(realChannel, channelListener);
}
public String toString() {
return "Delegating channel listener -> " + channelListener;
}
}
private static class SetterDelegatingListener<C extends Channel, T extends Channel> implements ChannelListener<C> {
private final SimpleSetter<T> setter;
private final T channel;
public SetterDelegatingListener(final SimpleSetter<T> setter, final T channel) {
this.setter = setter;
this.channel = channel;
}
public void handleEvent(final C channel) {
invokeChannelListener(this.channel, setter.get());
}
public String toString() {
return "Setter delegating channel listener -> " + setter;
}
}
private static final ChannelExceptionHandler<Channel> CLOSING_HANDLER = new ChannelExceptionHandler<Channel>() {
public void handleException(final Channel channel, final IOException exception) {
IoUtils.safeClose(channel);
}
};
private static class DrainListener<T extends StreamSourceChannel> implements ChannelListener<T> {
private final ChannelListener<? super T> finishListener;
private final ChannelExceptionHandler<? super T> exceptionHandler;
private long count;
private DrainListener(final ChannelListener<? super T> finishListener, final ChannelExceptionHandler<? super T> exceptionHandler, final long count) {
this.finishListener = finishListener;
this.exceptionHandler = exceptionHandler;
this.count = count;
}
public void handleEvent(final T channel) {
try {
long count = this.count;
try {
long res;
for (;;) {
res = Channels.drain(channel, count);
if (res == -1 || res == count) {
this.count = 0L;
invokeChannelListener(channel, finishListener);
return;
} else if (res == 0) {
return;
} else if (count < Long.MAX_VALUE) {
// MAX_VALUE means drain to EOF
count -= res;
}
}
} finally {
this.count = count;
}
} catch (IOException e) {
this.count = 0L;
if (exceptionHandler != null) {
invokeChannelExceptionHandler(channel, exceptionHandler, e);
} else {
IoUtils.safeShutdownReads(channel);
}
}
}
public String toString() {
return "Draining channel listener (" + count + " bytes) -> " + finishListener;
}
}
}