All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xnio.IoUtils Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * 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) {
            msg.tracef("safeClose, ignoring ClosedChannelException exception");
        } 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 futureResource) {
        if (futureResource != null) {
            futureResource.cancel().addNotifier(closingNotifier(), null);
        }
    }

    private static final IoFuture.Notifier ATTACHMENT_CLOSING_NOTIFIER = new IoFuture.Notifier() {
        public void notify(final IoFuture future, final Closeable attachment) {
            IoUtils.safeClose(attachment);
        }
    };

    private static final IoFuture.Notifier CLOSING_NOTIFIER = new IoFuture.HandlingNotifier() {
        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 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 closingNotifier() {
        return CLOSING_NOTIFIER;
    }

    /**
     * Get a notifier that runs the supplied action.
     *
     * @param runnable the notifier type
     * @param  the future type (not used)
     * @return a notifier which will run the given command
     */
    public static  IoFuture.Notifier runnableNotifier(final Runnable runnable) {
        return new IoFuture.Notifier() {
            public void notify(final IoFuture 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  the result type
     * @return the notifier
     */
    @SuppressWarnings({ "unchecked" })
    public static  IoFuture.Notifier> resultNotifier() {
        return RESULT_NOTIFIER;
    }

    /**
     * Get the notifier that invokes the channel listener given as an attachment.
     *
     * @param  the channel type
     * @return the notifier
     */
    @SuppressWarnings({ "unchecked" })
    public static  IoFuture.Notifier> channelListenerNotifier() {
        return CHANNEL_LISTENER_NOTIFIER;
    }

    @SuppressWarnings("rawtypes")
    private static final IoFuture.Notifier CHANNEL_LISTENER_NOTIFIER = new IoFuture.HandlingNotifier>() {
        @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  Future getFuture(final IoFuture ioFuture) {
        return new Future() {
            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 COUNT_DOWN_NOTIFIER = new IoFuture.Notifier() {
        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  the type of the original result
     * @param  the type of the wrapped result
     * @return a wrapper {@code IoFuture}
     */
    public static  IoFuture cast(final IoFuture parent, final Class type) {
        return new CastingIoFuture(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.
     * 

* 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 implements IoFuture { private final IoFuture parent; private final Class type; private CastingIoFuture(final IoFuture parent, final Class type) { this.parent = parent; this.type = type; } public IoFuture 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 IoFuture addNotifier(final Notifier notifier, final A attachment) { parent.addNotifier(new Notifier() { public void notify(final IoFuture 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 the channel type * @return the notifier */ @SuppressWarnings({ "unchecked" }) public static IoFuture.Notifier> getManagerNotifier() { return MANAGER_NOTIFIER; } @SuppressWarnings("rawtypes") private static final ManagerNotifier MANAGER_NOTIFIER = new ManagerNotifier(); private static class ManagerNotifier extends IoFuture.HandlingNotifier> { public void handleCancelled(final FutureResult manager) { manager.setCancelled(); } public void handleFailed(final IOException exception, final FutureResult manager) { manager.setException(exception); } public void handleDone(final T result, final FutureResult 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 the channel type * @return the retrying channel source */ public static ChannelSource getRetryingChannelSource(final ChannelSource delegate, final int maxTries) throws IllegalArgumentException { if (maxTries < 1) { throw msg.minRange("maxTries", 1); } return new RetryingChannelSource(maxTries, delegate); } private static class RetryingNotifier extends IoFuture.HandlingNotifier> { private volatile int remaining; private final int maxTries; private final Result result; private final ChannelSource delegate; private final ChannelListener openListener; RetryingNotifier(final int maxTries, final Result result, final ChannelSource delegate, final ChannelListener openListener) { this.maxTries = maxTries; this.result = result; this.delegate = delegate; this.openListener = openListener; remaining = maxTries; } public void handleFailed(final IOException exception, final Result attachment) { if (remaining-- == 0) { result.setException(new IOException("Failed to create channel after " + maxTries + " tries", exception)); return; } tryOne(attachment); } public void handleCancelled(final Result attachment) { result.setCancelled(); } public void handleDone(final T data, final Result attachment) { result.setResult(data); } void tryOne(final Result attachment) { final IoFuture ioFuture = delegate.open(openListener); ioFuture.addNotifier(this, attachment); } } private static class RetryingChannelSource implements ChannelSource { private final int maxTries; private final ChannelSource delegate; RetryingChannelSource(final int maxTries, final ChannelSource delegate) { this.maxTries = maxTries; this.delegate = delegate; } public IoFuture open(final ChannelListener openListener) { final FutureResult result = new FutureResult(); final IoUtils.RetryingNotifier notifier = new IoUtils.RetryingNotifier(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 extends IoFuture.HandlingNotifier> { public void handleCancelled(final Result result) { result.setCancelled(); } public void handleFailed(final IOException exception, final Result result) { result.setException(exception); } public void handleDone(final T value, final Result 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(); } }