/*
* JBoss, Home of Professional Open Source
*
* Copyright 2008 Red Hat, Inc. and/or its affiliates.
*
* 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.IOException;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.Selector;
import java.nio.channels.Channel;
import java.nio.channels.WritableByteChannel;
import java.util.Random;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.zip.ZipFile;
import org.xnio.channels.SuspendableReadChannel;
import java.util.logging.Handler;
import static org.xnio._private.Messages.closeMsg;
import static org.xnio._private.Messages.msg;
/**
* General I/O utility methods.
*
* @apiviz.exclude
*/
public final class IoUtils {
private static final Executor NULL_EXECUTOR = new Executor() {
private final String string = String.format("null executor <%s>", Integer.toHexString(hashCode()));
public void execute(final Runnable command) {
// no operation
}
public String toString() {
return string;
}
};
private static final Executor DIRECT_EXECUTOR = new Executor() {
private final String string = String.format("direct executor <%s>", Integer.toHexString(hashCode()));
public void execute(final Runnable command) {
command.run();
}
public String toString() {
return string;
}
};
private static final Closeable NULL_CLOSEABLE = new Closeable() {
private final String string = String.format("null closeable <%s>", Integer.toHexString(hashCode()));
public void close() throws IOException {
// no operation
}
public String toString() {
return string;
}
};
private static final Cancellable NULL_CANCELLABLE = new Cancellable() {
public Cancellable cancel() {
return this;
}
};
@SuppressWarnings("rawtypes")
private static final IoUtils.ResultNotifier RESULT_NOTIFIER = new IoUtils.ResultNotifier();
private IoUtils() {}
/**
* Get the direct executor. This is an executor that executes the provided task in the same thread.
*
* @return a direct executor
*/
public static Executor directExecutor() {
return DIRECT_EXECUTOR;
}
/**
* Get the null executor. This is an executor that never actually executes the provided task.
*
* @return a null executor
*/
public static Executor nullExecutor() {
return NULL_EXECUTOR;
}
/**
* Get the null closeable. This is a simple {@code Closeable} instance that does nothing when its {@code close()}
* method is invoked.
*
* @return the null closeable
*/
public static Closeable nullCloseable() {
return NULL_CLOSEABLE;
}
/**
* Close a resource, logging an error if an error occurs.
*
* @param resource the resource to close
*/
public static void safeClose(final AutoCloseable resource) {
try {
if (resource != null) {
closeMsg.closingResource(resource);
resource.close();
}
} catch (ClosedChannelException ignored) {
} catch (Throwable t) {
closeMsg.resourceCloseFailed(t, resource);
}
}
/**
* Close a resource, logging an error if an error occurs.
*
* @param resource the resource to close
*/
public static void safeClose(final Closeable resource) {
try {
if (resource != null) {
closeMsg.closingResource(resource);
resource.close();
}
} catch (ClosedChannelException ignored) {
} catch (Throwable t) {
closeMsg.resourceCloseFailed(t, resource);
}
}
/**
* Close a series of resources, logging errors if they occur.
*
* @param resources the resources to close
*/
public static void safeClose(final Closeable... resources) {
for (Closeable resource : resources) {
safeClose(resource);
}
}
/**
* Close a resource, logging an error if an error occurs.
*
* @param resource the resource to close
*/
public static void safeClose(final Socket resource) {
try {
if (resource != null) {
closeMsg.closingResource(resource);
resource.close();
}
} catch (ClosedChannelException ignored) {
} catch (Throwable t) {
closeMsg.resourceCloseFailed(t, resource);
}
}
/**
* Close a resource, logging an error if an error occurs.
*
* @param resource the resource to close
*/
public static void safeClose(final DatagramSocket resource) {
try {
if (resource != null) {
closeMsg.closingResource(resource);
resource.close();
}
} catch (Throwable t) {
closeMsg.resourceCloseFailed(t, resource);
}
}
/**
* Close a resource, logging an error if an error occurs.
*
* @param resource the resource to close
*/
public static void safeClose(final Selector resource) {
try {
if (resource != null) {
closeMsg.closingResource(resource);
resource.close();
}
} catch (ClosedChannelException ignored) {
} catch (Throwable t) {
closeMsg.resourceCloseFailed(t, resource);
}
}
/**
* Close a resource, logging an error if an error occurs.
*
* @param resource the resource to close
*/
public static void safeClose(final ServerSocket resource) {
try {
if (resource != null) {
closeMsg.closingResource(resource);
resource.close();
}
} catch (ClosedChannelException ignored) {
} catch (Throwable t) {
closeMsg.resourceCloseFailed(t, resource);
}
}
/**
* Close a resource, logging an error if an error occurs.
*
* @param resource the resource to close
*/
public static void safeClose(final ZipFile resource) {
try {
if (resource != null) {
closeMsg.closingResource(resource);
resource.close();
}
} catch (Throwable t) {
closeMsg.resourceCloseFailed(t, resource);
}
}
/**
* Close a resource, logging an error if an error occurs.
*
* @param resource the resource to close
*/
public static void safeClose(final Handler resource) {
try {
if (resource != null) {
closeMsg.closingResource(resource);
resource.close();
}
} catch (Throwable t) {
closeMsg.resourceCloseFailed(t, resource);
}
}
/**
* Close a future resource, logging an error if an error occurs. Attempts to cancel the operation if it is
* still in progress.
*
* @param futureResource the resource to close
*/
public static void safeClose(final IoFuture<? extends Closeable> futureResource) {
if (futureResource != null) {
futureResource.cancel().addNotifier(closingNotifier(), null);
}
}
private static final IoFuture.Notifier<Object, Closeable> ATTACHMENT_CLOSING_NOTIFIER = new IoFuture.Notifier<Object, Closeable>() {
public void notify(final IoFuture<?> future, final Closeable attachment) {
IoUtils.safeClose(attachment);
}
};
private static final IoFuture.Notifier<Closeable, Void> CLOSING_NOTIFIER = new IoFuture.HandlingNotifier<Closeable, Void>() {
public void handleDone(final Closeable result, final Void attachment) {
IoUtils.safeClose(result);
}
};
/**
* Get a notifier that closes the attachment.
*
* @return a notifier which will close its attachment
*/
public static IoFuture.Notifier<Object, Closeable> attachmentClosingNotifier() {
return ATTACHMENT_CLOSING_NOTIFIER;
}
/**
* Get a notifier that closes the result.
*
* @return a notifier which will close the result of the operation (if successful)
*/
public static IoFuture.Notifier<Closeable, Void> closingNotifier() {
return CLOSING_NOTIFIER;
}
/**
* Get a notifier that runs the supplied action.
*
* @param runnable the notifier type
* @param <T> the future type (not used)
* @return a notifier which will run the given command
*/
public static <T> IoFuture.Notifier<T, Void> runnableNotifier(final Runnable runnable) {
return new IoFuture.Notifier<T, Void>() {
public void notify(final IoFuture<? extends T> future, final Void attachment) {
runnable.run();
}
};
}
/**
* Get the result notifier. This notifier will forward the result of the {@code IoFuture} to the attached
* {@code Result}.
*
* @param <T> the result type
* @return the notifier
*/
@SuppressWarnings({ "unchecked" })
public static <T> IoFuture.Notifier<T, Result<T>> resultNotifier() {
return RESULT_NOTIFIER;
}
/**
* Get the notifier that invokes the channel listener given as an attachment.
*
* @param <T> the channel type
* @return the notifier
*/
@SuppressWarnings({ "unchecked" })
public static <T extends Channel> IoFuture.Notifier<T, ChannelListener<? super T>> channelListenerNotifier() {
return CHANNEL_LISTENER_NOTIFIER;
}
@SuppressWarnings("rawtypes")
private static final IoFuture.Notifier CHANNEL_LISTENER_NOTIFIER = new IoFuture.HandlingNotifier<Channel, ChannelListener<? super Channel>>() {
@SuppressWarnings({ "unchecked" })
public void handleDone(final Channel channel, final ChannelListener channelListener) {
channelListener.handleEvent(channel);
}
};
/**
* Get a {@code java.util.concurrent}-style {@code Future} instance wrapper for an {@code IoFuture} instance.
*
* @param ioFuture the {@code IoFuture} to wrap
* @return a {@code Future}
*/
public static <T> Future<T> getFuture(final IoFuture<T> ioFuture) {
return new Future<T>() {
public boolean cancel(final boolean mayInterruptIfRunning) {
ioFuture.cancel();
return ioFuture.await() == IoFuture.Status.CANCELLED;
}
public boolean isCancelled() {
return ioFuture.getStatus() == IoFuture.Status.CANCELLED;
}
public boolean isDone() {
return ioFuture.getStatus() == IoFuture.Status.DONE;
}
public T get() throws InterruptedException, ExecutionException {
try {
return ioFuture.getInterruptibly();
} catch (IOException e) {
throw new ExecutionException(e);
}
}
public T get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
try {
if (ioFuture.awaitInterruptibly(timeout, unit) == IoFuture.Status.WAITING) {
throw msg.opTimedOut();
}
return ioFuture.getInterruptibly();
} catch (IOException e) {
throw new ExecutionException(e);
}
}
public String toString() {
return String.format("java.util.concurrent.Future wrapper <%s> for %s", Integer.toHexString(hashCode()), ioFuture);
}
};
}
private static final IoFuture.Notifier<Object, CountDownLatch> COUNT_DOWN_NOTIFIER = new IoFuture.Notifier<Object, CountDownLatch>() {
public void notify(final IoFuture<?> future, final CountDownLatch latch) {
latch.countDown();
}
};
/**
* Wait for all the futures to complete.
*
* @param futures the futures to wait for
*/
public static void awaitAll(IoFuture<?>... futures) {
final int len = futures.length;
final CountDownLatch cdl = new CountDownLatch(len);
for (IoFuture<?> future : futures) {
future.addNotifier(COUNT_DOWN_NOTIFIER, cdl);
}
boolean intr = false;
try {
while (cdl.getCount() > 0L) {
try {
cdl.await();
} catch (InterruptedException e) {
intr = true;
}
}
} finally {
if (intr) {
Thread.currentThread().interrupt();
}
}
}
/**
* Wait for all the futures to complete.
*
* @param futures the futures to wait for
* @throws InterruptedException if the current thread is interrupted while waiting
*/
public static void awaitAllInterruptibly(IoFuture<?>... futures) throws InterruptedException {
final int len = futures.length;
final CountDownLatch cdl = new CountDownLatch(len);
for (IoFuture<?> future : futures) {
future.addNotifier(COUNT_DOWN_NOTIFIER, cdl);
}
cdl.await();
}
/**
* Create an {@code IoFuture} which wraps another {@code IoFuture}, but returns a different type.
*
* @param parent the original {@code IoFuture}
* @param type the class of the new {@code IoFuture}
* @param <I> the type of the original result
* @param <O> the type of the wrapped result
* @return a wrapper {@code IoFuture}
*/
public static <I, O> IoFuture<? extends O> cast(final IoFuture<I> parent, final Class<O> type) {
return new CastingIoFuture<O, I>(parent, type);
}
/**
* Safely shutdown reads on the given channel.
*
* @param channel the channel
*/
public static void safeShutdownReads(final SuspendableReadChannel channel) {
if (channel != null) {
try {
channel.shutdownReads();
} catch (IOException e) {
closeMsg.resourceReadShutdownFailed(null, null);
}
}
}
/**
* Platform-independent channel-to-channel transfer method. Uses regular {@code read} and {@code write} operations
* to move bytes from the {@code source} channel to the {@code sink} channel. After this call, the {@code throughBuffer}
* should be checked for remaining bytes; if there are any, they should be written to the {@code sink} channel before
* proceeding. This method may be used with NIO channels, XNIO channels, or a combination of the two.
* <p>
* If either or both of the given channels are blocking channels, then this method may block.
*
* @param source the source channel to read bytes from
* @param count the number of bytes to transfer (must be >= {@code 0L})
* @param throughBuffer the buffer to transfer through (must not be {@code null})
* @param sink the sink channel to write bytes to
* @return the number of bytes actually transferred (possibly 0)
* @throws IOException if an I/O error occurs during the transfer of bytes
*/
public static long transfer(final ReadableByteChannel source, final long count, final ByteBuffer throughBuffer, final WritableByteChannel sink) throws IOException {
long res;
long total = 0L;
throughBuffer.limit(0);
while (total < count) {
throughBuffer.compact();
try {
if (count - total < (long) throughBuffer.remaining()) {
throughBuffer.limit((int) (count - total));
}
res = source.read(throughBuffer);
if (res <= 0) {
return total == 0L ? res : total;
}
} finally {
throughBuffer.flip();
}
res = sink.write(throughBuffer);
if (res == 0) {
return total;
}
total += res;
}
return total;
}
// nested classes
private static class CastingIoFuture<O, I> implements IoFuture<O> {
private final IoFuture<I> parent;
private final Class<O> type;
private CastingIoFuture(final IoFuture<I> parent, final Class<O> type) {
this.parent = parent;
this.type = type;
}
public IoFuture<O> cancel() {
parent.cancel();
return this;
}
public Status getStatus() {
return parent.getStatus();
}
public Status await() {
return parent.await();
}
public Status await(final long time, final TimeUnit timeUnit) {
return parent.await(time, timeUnit);
}
public Status awaitInterruptibly() throws InterruptedException {
return parent.awaitInterruptibly();
}
public Status awaitInterruptibly(final long time, final TimeUnit timeUnit) throws InterruptedException {
return parent.awaitInterruptibly(time, timeUnit);
}
public O get() throws IOException, CancellationException {
return type.cast(parent.get());
}
public O getInterruptibly() throws IOException, InterruptedException, CancellationException {
return type.cast(parent.getInterruptibly());
}
public IOException getException() throws IllegalStateException {
return parent.getException();
}
public <A> IoFuture<O> addNotifier(final Notifier<? super O, A> notifier, final A attachment) {
parent.addNotifier(new Notifier<I, A>() {
public void notify(final IoFuture<? extends I> future, final A attachment) {
notifier.notify(CastingIoFuture.this, attachment);
}
}, attachment);
return this;
}
}
/**
* Get a notifier which forwards the result to another {@code IoFuture}'s manager.
*
* @param <T> the channel type
* @return the notifier
*/
@SuppressWarnings({ "unchecked" })
public static <T> IoFuture.Notifier<T, FutureResult<T>> getManagerNotifier() {
return MANAGER_NOTIFIER;
}
@SuppressWarnings("rawtypes")
private static final ManagerNotifier MANAGER_NOTIFIER = new ManagerNotifier();
private static class ManagerNotifier<T extends Channel> extends IoFuture.HandlingNotifier<T, FutureResult<T>> {
public void handleCancelled(final FutureResult<T> manager) {
manager.setCancelled();
}
public void handleFailed(final IOException exception, final FutureResult<T> manager) {
manager.setException(exception);
}
public void handleDone(final T result, final FutureResult<T> manager) {
manager.setResult(result);
}
}
/**
* A channel source which tries to acquire a channel from a delegate channel source the given number of times before
* giving up.
*
* @param delegate the delegate channel source
* @param maxTries the number of times to retry
* @param <T> the channel type
* @return the retrying channel source
*/
public static <T extends Channel> ChannelSource<T> getRetryingChannelSource(final ChannelSource<T> delegate, final int maxTries) throws IllegalArgumentException {
if (maxTries < 1) {
throw msg.minRange("maxTries", 1);
}
return new RetryingChannelSource<T>(maxTries, delegate);
}
private static class RetryingNotifier<T extends Channel> extends IoFuture.HandlingNotifier<T, Result<T>> {
private volatile int remaining;
private final int maxTries;
private final Result<T> result;
private final ChannelSource<T> delegate;
private final ChannelListener<? super T> openListener;
RetryingNotifier(final int maxTries, final Result<T> result, final ChannelSource<T> delegate, final ChannelListener<? super T> openListener) {
this.maxTries = maxTries;
this.result = result;
this.delegate = delegate;
this.openListener = openListener;
remaining = maxTries;
}
public void handleFailed(final IOException exception, final Result<T> attachment) {
if (remaining-- == 0) {
result.setException(new IOException("Failed to create channel after " + maxTries + " tries", exception));
return;
}
tryOne(attachment);
}
public void handleCancelled(final Result<T> attachment) {
result.setCancelled();
}
public void handleDone(final T data, final Result<T> attachment) {
result.setResult(data);
}
void tryOne(final Result<T> attachment) {
final IoFuture<? extends T> ioFuture = delegate.open(openListener);
ioFuture.addNotifier(this, attachment);
}
}
private static class RetryingChannelSource<T extends Channel> implements ChannelSource<T> {
private final int maxTries;
private final ChannelSource<T> delegate;
RetryingChannelSource(final int maxTries, final ChannelSource<T> delegate) {
this.maxTries = maxTries;
this.delegate = delegate;
}
public IoFuture<T> open(final ChannelListener<? super T> openListener) {
final FutureResult<T> result = new FutureResult<T>();
final IoUtils.RetryingNotifier<T> notifier = new IoUtils.RetryingNotifier<T>(maxTries, result, delegate, openListener);
notifier.tryOne(result);
return result.getIoFuture();
}
}
/**
* A cancellable which closes the given resource on cancel.
*
* @param c the resource
* @return the cancellable
*/
public static Cancellable closingCancellable(final Closeable c) {
return new ClosingCancellable(c);
}
private static class ClosingCancellable implements Cancellable {
private final Closeable c;
ClosingCancellable(final Closeable c) {
this.c = c;
}
public Cancellable cancel() {
safeClose(c);
return this;
}
}
/**
* Get the null cancellable.
*
* @return the null cancellable
*/
public static Cancellable nullCancellable() {
return NULL_CANCELLABLE;
}
private static class ResultNotifier<T> extends IoFuture.HandlingNotifier<T, Result<T>> {
public void handleCancelled(final Result<T> result) {
result.setCancelled();
}
public void handleFailed(final IOException exception, final Result<T> result) {
result.setException(exception);
}
public void handleDone(final T value, final Result<T> result) {
result.setResult(value);
}
}
/**
* Get a thread-local RNG. Do not share this instance with other threads.
*
* @return the thread-local RNG
*/
public static Random getThreadLocalRandom() {
return ThreadLocalRandom.current();
}
}