/* * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.nio.channels.spi; import java.io.IOException; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedSelectorException; import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.IllegalSelectorException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; /** * Base implementation class for selectable channels. * *

This class defines methods that handle the mechanics of channel * registration, deregistration, and closing. It maintains the current * blocking mode of this channel as well as its current set of selection keys. * It performs all of the synchronization required to implement the {@link * java.nio.channels.SelectableChannel} specification. Implementations of the * abstract protected methods defined in this class need not synchronize * against other threads that might be engaged in the same operations.

* * * @author Mark Reinhold * @author Mike McCloskey * @author JSR-51 Expert Group * @since 1.4 */ public abstract class AbstractSelectableChannel extends SelectableChannel { // The provider that created this channel private final SelectorProvider provider; // Keys that have been created by registering this channel with selectors. // They are saved because if this channel is closed the keys must be // deregistered. Protected by keyLock. // private SelectionKey[] keys = null; private int keyCount = 0; // Lock for key set and count private final Object keyLock = new Object(); // Lock for registration and configureBlocking operations private final Object regLock = new Object(); // True when non-blocking, need regLock to change; private volatile boolean nonBlocking; /** * Initializes a new instance of this class. * * @param provider * The provider that created this channel */ protected AbstractSelectableChannel(SelectorProvider provider) { this.provider = provider; } /** * Returns the provider that created this channel. * * @return The provider that created this channel */ public final SelectorProvider provider() { return provider; } // -- Utility methods for the key set -- private void addKey(SelectionKey k) { assert Thread.holdsLock(keyLock); int i = 0; if ((keys != null) && (keyCount < keys.length)) { // Find empty element of key array for (i = 0; i < keys.length; i++) if (keys[i] == null) break; } else if (keys == null) { keys = new SelectionKey[3]; } else { // Grow key array int n = keys.length * 2; SelectionKey[] ks = new SelectionKey[n]; for (i = 0; i < keys.length; i++) ks[i] = keys[i]; keys = ks; i = keyCount; } keys[i] = k; keyCount++; } private SelectionKey findKey(Selector sel) { synchronized (keyLock) { if (keys == null) return null; for (int i = 0; i < keys.length; i++) if ((keys[i] != null) && (keys[i].selector() == sel)) return keys[i]; return null; } } void removeKey(SelectionKey k) { // package-private synchronized (keyLock) { for (int i = 0; i < keys.length; i++) if (keys[i] == k) { keys[i] = null; keyCount--; } ((AbstractSelectionKey)k).invalidate(); } } private boolean haveValidKeys() { synchronized (keyLock) { if (keyCount == 0) return false; for (int i = 0; i < keys.length; i++) { if ((keys[i] != null) && keys[i].isValid()) return true; } return false; } } // -- Registration -- public final boolean isRegistered() { synchronized (keyLock) { return keyCount != 0; } } public final SelectionKey keyFor(Selector sel) { return findKey(sel); } /** * Registers this channel with the given selector, returning a selection key. * *

This method first verifies that this channel is open and that the * given initial interest set is valid. * *

If this channel is already registered with the given selector then * the selection key representing that registration is returned after * setting its interest set to the given value. * *

Otherwise this channel has not yet been registered with the given * selector, so the {@link AbstractSelector#register register} method of * the selector is invoked while holding the appropriate locks. The * resulting key is added to this channel's key set before being returned. *

* * @throws ClosedSelectorException {@inheritDoc} * * @throws IllegalBlockingModeException {@inheritDoc} * * @throws IllegalSelectorException {@inheritDoc} * * @throws CancelledKeyException {@inheritDoc} * * @throws IllegalArgumentException {@inheritDoc} */ public final SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException { synchronized (regLock) { if (!isOpen()) throw new ClosedChannelException(); if ((ops & ~validOps()) != 0) throw new IllegalArgumentException(); if (isBlocking()) throw new IllegalBlockingModeException(); SelectionKey k = findKey(sel); if (k != null) { k.interestOps(ops); k.attach(att); } if (k == null) { // New registration synchronized (keyLock) { if (!isOpen()) throw new ClosedChannelException(); k = ((AbstractSelector)sel).register(this, ops, att); addKey(k); } } return k; } } // -- Closing -- /** * Closes this channel. * *

This method, which is specified in the {@link * AbstractInterruptibleChannel} class and is invoked by the {@link * java.nio.channels.Channel#close close} method, in turn invokes the * {@link #implCloseSelectableChannel implCloseSelectableChannel} method in * order to perform the actual work of closing this channel. It then * cancels all of this channel's keys.

*/ protected final void implCloseChannel() throws IOException { implCloseSelectableChannel(); synchronized (keyLock) { int count = (keys == null) ? 0 : keys.length; for (int i = 0; i < count; i++) { SelectionKey k = keys[i]; if (k != null) k.cancel(); } } } /** * Closes this selectable channel. * *

This method is invoked by the {@link java.nio.channels.Channel#close * close} method in order to perform the actual work of closing the * channel. This method is only invoked if the channel has not yet been * closed, and it is never invoked more than once. * *

An implementation of this method must arrange for any other thread * that is blocked in an I/O operation upon this channel to return * immediately, either by throwing an exception or by returning normally. *

* * @throws IOException * If an I/O error occurs */ protected abstract void implCloseSelectableChannel() throws IOException; // -- Blocking -- public final boolean isBlocking() { return !nonBlocking; } public final Object blockingLock() { return regLock; } /** * Adjusts this channel's blocking mode. * *

If the given blocking mode is different from the current blocking * mode then this method invokes the {@link #implConfigureBlocking * implConfigureBlocking} method, while holding the appropriate locks, in * order to change the mode.

*/ public final SelectableChannel configureBlocking(boolean block) throws IOException { synchronized (regLock) { if (!isOpen()) throw new ClosedChannelException(); boolean blocking = !nonBlocking; if (block != blocking) { if (block && haveValidKeys()) throw new IllegalBlockingModeException(); implConfigureBlocking(block); nonBlocking = !block; } } return this; } /** * Adjusts this channel's blocking mode. * *

This method is invoked by the {@link #configureBlocking * configureBlocking} method in order to perform the actual work of * changing the blocking mode. This method is only invoked if the new mode * is different from the current mode.

* * @param block If true then this channel will be placed in * blocking mode; if false then it will be placed * non-blocking mode * * @throws IOException * If an I/O error occurs */ protected abstract void implConfigureBlocking(boolean block) throws IOException; }