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

org.xnio.channels.TranslatingSuspendableChannel Maven / Gradle / Ivy

There is a newer version: 3.8.16.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.xnio.channels;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.Option;
import org.xnio.ReadChannelThread;
import org.xnio.WriteChannelThread;

/**
 * An abstract wrapped channel.
 *
 * @param  the channel type implemented by this class
 * @param  the channel type being wrapped by this class
 *
 * @author David M. Lloyd
 */
public abstract class TranslatingSuspendableChannel implements SuspendableChannel, WrappedChannel {

    /**
     * The wrapped channel.
     */
    protected final W channel;

    private final ChannelListener.SimpleSetter readSetter = new ChannelListener.SimpleSetter();
    private final ChannelListener.SimpleSetter writeSetter = new ChannelListener.SimpleSetter();
    private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter();

    private boolean readsRequested;
    private boolean writesRequested;

    /**
     * The readiness of this channel implementation.
     */
    protected enum Readiness {
        /**
         * The channel is ready, regardless of the underlying channel status.
         */
        ALWAYS,
        /**
         * The channel is ready if the underlying channel is ready.
         */
        OKAY,
        /**
         * The channel is not ready even if the underlying channel is.
         */
        NEVER,
    }

    private final Runnable readListenerCommand = new Runnable() {
        public void run() {
            boolean doReads;
            do {
                ChannelListeners.invokeChannelListener(channel, readListener);
                synchronized (getReadLock()) {
                    final Readiness readiness = isReadable();
                    doReads = readsRequested && readiness == Readiness.ALWAYS;
                    if (readiness == Readiness.NEVER) {
                        channel.suspendReads();
                    }
                }
            } while (doReads);
        }

        public String toString() {
            return "Read listener command for " + TranslatingSuspendableChannel.this;
        }
    };
    private final ChannelListener readListener = new ChannelListener() {
        public void handleEvent(final W channel) {
            handleReadable(channel);
        }

        public String toString() {
            return "Read listener for " + TranslatingSuspendableChannel.this;
        }
    };

    private final Runnable writeListenerCommand = new Runnable() {
        public void run() {
            boolean doWrites;
            do {
                ChannelListeners.invokeChannelListener(channel, writeListener);
                synchronized (getWriteLock()) {
                    final Readiness readiness = isWritable();
                    doWrites = writesRequested && readiness == Readiness.ALWAYS;
                    if (readiness == Readiness.NEVER) {
                        channel.suspendWrites();
                    }
                }
            } while (doWrites);
        }

        public String toString() {
            return "Write listener command for " + TranslatingSuspendableChannel.this;
        }
    };
    private final ChannelListener writeListener = new ChannelListener() {
        public void handleEvent(final W channel) {
            handleWritable(channel);
        }

        public String toString() {
            return "Write listener for " + TranslatingSuspendableChannel.this;
        }
    };
    private final ChannelListener closeListener = new ChannelListener() {
        public void handleEvent(final W channel) {
            ChannelListeners.invokeChannelListener(thisTyped(), closeSetter.get());
        }

        public String toString() {
            return "Close listener for " + TranslatingSuspendableChannel.this;
        }
    };

    /**
     * Construct a new instance.
     *
     * @param channel the channel being wrapped
     */
    @SuppressWarnings("unchecked")
    protected TranslatingSuspendableChannel(final W channel) {
        if (channel == null) {
            throw new IllegalArgumentException("channel is null");
        }
        this.channel = channel;
        final ChannelListener.Setter readSetter = (ChannelListener.Setter) channel.getReadSetter();
        readSetter.set(readListener);
        final ChannelListener.Setter writeSetter = (ChannelListener.Setter) channel.getWriteSetter();
        writeSetter.set(writeListener);
        final ChannelListener.Setter setter = (ChannelListener.Setter) channel.getCloseSetter();
        setter.set(closeListener);
    }

    /**
     * Called when the underlying channel is readable.
     *
     * @param channel the underlying channel
     */
    protected void handleReadable(final W channel) {
        final ChannelListener listener = readSetter.get();
        if (listener == null) {
            synchronized (getReadLock()) {
                channel.suspendReads();
                readsRequested = false;
            }
            return;
        }
        ChannelListeners.invokeChannelListener(thisTyped(), listener);
    }

    /**
     * Called when the underlying channel is writable.
     *
     * @param channel the underlying channel
     */
    protected void handleWritable(final W channel) {
        final ChannelListener listener = writeSetter.get();
        if (listener == null) {
            synchronized (getWriteLock()) {
                channel.suspendWrites();
                writesRequested = false;
            }
            return;
        }
        ChannelListeners.invokeChannelListener(thisTyped(), listener);
    }

    /**
     * Get this channel, cast to the implemented channel type.
     *
     * @return {@code this}
     */
    @SuppressWarnings("unchecked")
    protected final C thisTyped() {
        return (C) this;
    }

    /** {@inheritDoc} */
    public ChannelListener.Setter getCloseSetter() {
        return closeSetter;
    }

    /** {@inheritDoc} */
    public ChannelListener.Setter getReadSetter() {
        return readSetter;
    }

    /** {@inheritDoc} */
    public ChannelListener.Setter getWriteSetter() {
        return writeSetter;
    }

    /** {@inheritDoc} */
    public void suspendReads() {
        synchronized (getReadLock()) {
            readsRequested = false;
            channel.suspendReads();
        }
    }

    /** {@inheritDoc} */
    public void resumeReads() {
        synchronized (getReadLock()) {
            readsRequested = true;
            switch (isReadable()) {
                case NEVER: channel.suspendReads(); break;
                case OKAY: channel.resumeReads(); // fall thru
                case ALWAYS: scheduleReadTask(); break;
            }
        }
    }

    /**
     * Resume reads if the user has requested so.
     */
    protected void resumeReadsIfRequested() {
        synchronized (getReadLock()) {
            if (readsRequested) {
                channel.resumeReads();
            }
        }
    }

    /**
     * Schedule the read listener to run even if the underlying channel isn't currently readable.
     */
    protected void scheduleReadTask() {
        getReadThread().execute(readListenerCommand);
    }

    /** {@inheritDoc} */
    public void suspendWrites() {
        synchronized (getWriteLock()) {
            writesRequested = false;
            channel.suspendWrites();
        }
    }

    /** {@inheritDoc} */
    public void resumeWrites() {
        synchronized (getWriteLock()) {
            writesRequested = true;
            switch (isWritable()) {
                case NEVER: channel.suspendWrites(); break;
                case OKAY: channel.resumeWrites(); // fall thru
                case ALWAYS: scheduleWriteTask(); break;
            }
        }
    }

    /**
     * Resume writes if the user has requested so.
     */
    protected void resumeWritesIfRequested() {
        synchronized (getWriteLock()) {
            if (writesRequested) {
                channel.resumeWrites();
            }
        }
    }

    /**
     * Schedule the write listener to run even if the underlying channel isn't currently writable.
     */
    protected void scheduleWriteTask() {
        getWriteThread().execute(writeListenerCommand);
    }

    /** {@inheritDoc} */
    public boolean supportsOption(final Option option) {
        return channel.supportsOption(option);
    }

    /** {@inheritDoc} */
    public  T getOption(final Option option) throws IOException {
        return channel.getOption(option);
    }

    /** {@inheritDoc} */
    public  T setOption(final Option option, final T value) throws IllegalArgumentException, IOException {
        return channel.setOption(option, value);
    }

    /** {@inheritDoc} */
    public void setReadThread(final ReadChannelThread thread) throws IllegalArgumentException {
        channel.setReadThread(thread);
    }

    /** {@inheritDoc} */
    public ReadChannelThread getReadThread() {
        return channel.getReadThread();
    }

    /** {@inheritDoc} */
    public void setWriteThread(final WriteChannelThread thread) throws IllegalArgumentException {
        channel.setWriteThread(thread);
    }

    /** {@inheritDoc} */
    public WriteChannelThread getWriteThread() {
        return channel.getWriteThread();
    }

    /**
     * Base implementation which delegates the flush request to the channel.  Subclasses should override this
     * method as appropriate.
     *
     * @return {@code true} if the flush completed, or {@code false} if the operation would block
     * @throws IOException if an error occurs
     */
    public boolean flush() throws IOException {
        return channel.flush();
    }

    /**
     * Base implementation method which simply delegates the shutdown request to the delegate channel.  Subclasses may
     * override this method and call up to this method as appropriate.
     *
     * @throws IOException if an I/O error occurs
     */
    public void shutdownReads() throws IOException {
        channel.shutdownReads();
    }

    /**
     * Base implementation method which simply delegates the shutdown request to the delegate channel.  Subclasses may
     * override this method and call up to this method as appropriate.
     *
     * @return {@code true} if the channel was shut down, or {@code false} if the operation would block
     * @throws IOException if an I/O error occurs
     */
    public boolean shutdownWrites() throws IOException {
        return channel.shutdownWrites();
    }

    /** {@inheritDoc} */
    public void awaitReadable() throws IOException {
        synchronized (getReadLock()) {
            if (isReadable() == Readiness.ALWAYS) {
                return;
            }
        }
        channel.awaitReadable();
    }

    /** {@inheritDoc} */
    public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException {
        synchronized (getReadLock()) {
            if (isReadable() == Readiness.ALWAYS) {
                return;
            }
        }
        channel.awaitReadable(time, timeUnit);
    }

    /** {@inheritDoc} */
    public void awaitWritable() throws IOException {
        synchronized (getWriteLock()) {
            if (isWritable() == Readiness.ALWAYS) {
                return;
            }
        }
        channel.awaitWritable();
    }

    /** {@inheritDoc} */
    public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException {
        synchronized (getWriteLock()) {
            if (isWritable() == Readiness.ALWAYS) {
                return;
            }
        }
        channel.awaitWritable(time, timeUnit);
    }

    /**
     * Base channel close implementation.  Delegates to the delegate channel by default.
     *
     * @throws IOException if an I/O error occurs
     */
    public void close() throws IOException {
        channel.close();
    }

    /** {@inheritDoc} */
    public boolean isOpen() {
        return channel.isOpen();
    }

    /** {@inheritDoc} */
    public W getChannel() {
        return channel;
    }

    /**
     * Determine whether this channel is known to be (or to not be) readable.  Called under the read lock.
     *
     * @return the readiness of this channel
     */
    protected abstract Readiness isReadable();

    /**
     * Get the object to use as a read lock.  Ensures that suspend/resume reads is atomic with respect to listener
     * invocation.
     *
     * @return the read lock
     */
    protected abstract Object getReadLock();

    /**
     * Determine whether this channel is known to be (or to not be) writable.  Called under the write lock.
     *
     * @return the readiness of this channel
     */
    protected abstract Readiness isWritable();

    /**
     * Get the object to use as a write lock.  Ensures that suspend/resume writes is atomic with respect to listener
     * invocation.
     *
     * @return the write lock
     */
    protected abstract Object getWriteLock();

    /** {@inheritDoc} */
    public String toString() {
        return getClass().getName() + " around " + channel;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy