blob: 5be891913cded75a330384abda523bb2cc048329 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 java.nio.channels.spi;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
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;
import java.util.ArrayList;
import java.util.List;
/**
* {@code AbstractSelectableChannel} is the base implementation class for
* selectable channels. It declares methods for registering, unregistering and
* closing selectable channels. It is thread-safe.
*/
public abstract class AbstractSelectableChannel extends SelectableChannel {
private final SelectorProvider provider;
/*
* The collection of key.
*/
private List<SelectionKey> keyList = new ArrayList<SelectionKey>();
// Marker class so lock type shows up in profilers
static private class BlockingLock {
}
private final Object blockingLock = new BlockingLock();
boolean isBlocking = true;
/**
* Constructs a new {@code AbstractSelectableChannel}.
*
* @param selectorProvider
* the selector provider that creates this channel.
*/
protected AbstractSelectableChannel(SelectorProvider selectorProvider) {
super();
provider = selectorProvider;
}
/**
* Returns the selector provider that has created this channel.
*
* @see java.nio.channels.SelectableChannel#provider()
* @return this channel's selector provider.
*/
@Override
public final SelectorProvider provider() {
return provider;
}
/**
* Indicates whether this channel is registered with one or more selectors.
*
* @return {@code true} if this channel is registered with a selector,
* {@code false} otherwise.
*/
@Override
synchronized public final boolean isRegistered() {
return !keyList.isEmpty();
}
/**
* Gets this channel's selection key for the specified selector.
*
* @param selector
* the selector with which this channel has been registered.
* @return the selection key for the channel or {@code null} if this channel
* has not been registered with {@code selector}.
*/
@Override
synchronized public final SelectionKey keyFor(Selector selector) {
for (int i = 0; i < keyList.size(); i++) {
SelectionKey key = keyList.get(i);
if (null != key && key.selector() == selector) {
return key;
}
}
return null;
}
/**
* Registers this channel with the specified selector for the specified
* interest set. If the channel is already registered with the selector, the
* {@link SelectionKey interest set} is updated to {@code interestSet} and
* the corresponding selection key is returned. If the channel is not yet
* registered, this method calls the {@code register} method of
* {@code selector} and adds the selection key to this channel's key set.
*
* @param selector
* the selector with which to register this channel.
* @param interestSet
* this channel's {@link SelectionKey interest set}.
* @param attachment
* the object to attach, can be {@code null}.
* @return the selection key for this registration.
* @throws CancelledKeyException
* if this channel is registered but its key has been canceled.
* @throws ClosedChannelException
* if this channel is closed.
* @throws IllegalArgumentException
* if {@code interestSet} is not supported by this channel.
* @throws IllegalBlockingModeException
* if this channel is in blocking mode.
* @throws IllegalSelectorException
* if this channel does not have the same provider as the given
* selector.
*/
@Override
public final SelectionKey register(Selector selector, int interestSet,
Object attachment) throws ClosedChannelException {
if (!isOpen()) {
throw new ClosedChannelException();
}
if (!((interestSet & ~validOps()) == 0)) {
throw new IllegalArgumentException();
}
synchronized (blockingLock) {
if (isBlocking) {
throw new IllegalBlockingModeException();
}
if (!selector.isOpen()) {
if (0 == interestSet) {
// throw ISE exactly to keep consistency
throw new IllegalSelectorException();
}
// throw NPE exactly to keep consistency
throw new NullPointerException();
}
SelectionKey key = keyFor(selector);
if (null == key) {
key = ((AbstractSelector) selector).register(this, interestSet,
attachment);
keyList.add(key);
} else {
if (!key.isValid()) {
throw new CancelledKeyException();
}
key.interestOps(interestSet);
key.attach(attachment);
}
return key;
}
}
/**
* Implements the channel closing behavior. Calls
* {@code implCloseSelectableChannel()} first, then loops through the list
* of selection keys and cancels them, which unregisters this channel from
* all selectors it is registered with.
*
* @throws IOException
* if a problem occurs while closing the channel.
*/
@Override
synchronized protected final void implCloseChannel() throws IOException {
implCloseSelectableChannel();
for (int i = 0; i < keyList.size(); i++) {
SelectionKey key = keyList.get(i);
if (null != key) {
key.cancel();
}
}
}
/**
* Implements the closing function of the SelectableChannel. This method is
* called from {@code implCloseChannel()}.
*
* @throws IOException
* if an I/O exception occurs.
*/
protected abstract void implCloseSelectableChannel() throws IOException;
/**
* Indicates whether this channel is in blocking mode.
*
* @return {@code true} if this channel is blocking, {@code false}
* otherwise.
*/
@Override
public final boolean isBlocking() {
synchronized (blockingLock) {
return isBlocking;
}
}
/**
* Gets the object used for the synchronization of {@code register} and
* {@code configureBlocking}.
*
* @return the synchronization object.
*/
@Override
public final Object blockingLock() {
return blockingLock;
}
/**
* Sets the blocking mode of this channel. A call to this method blocks if
* other calls to this method or to {@code register} are executing. The
* actual setting of the mode is done by calling
* {@code implConfigureBlocking(boolean)}.
*
* @see java.nio.channels.SelectableChannel#configureBlocking(boolean)
* @param blockingMode
* {@code true} for setting this channel's mode to blocking,
* {@code false} to set it to non-blocking.
* @return this channel.
* @throws ClosedChannelException
* if this channel is closed.
* @throws IllegalBlockingModeException
* if {@code block} is {@code true} and this channel has been
* registered with at least one selector.
* @throws IOException
* if an I/O error occurs.
*/
@Override
public final SelectableChannel configureBlocking(boolean blockingMode) throws IOException {
if (isOpen()) {
synchronized (blockingLock) {
if (isBlocking == blockingMode) {
return this;
}
if (blockingMode && containsValidKeys()) {
throw new IllegalBlockingModeException();
}
implConfigureBlocking(blockingMode);
isBlocking = blockingMode;
}
return this;
}
throw new ClosedChannelException();
}
/**
* Implements the setting of the blocking mode.
*
* @param blockingMode
* {@code true} for setting this channel's mode to blocking,
* {@code false} to set it to non-blocking.
* @throws IOException
* if an I/O error occurs.
*/
protected abstract void implConfigureBlocking(boolean blockingMode)
throws IOException;
/*
* package private for deregister method in AbstractSelector.
*/
synchronized void deregister(SelectionKey k) {
if (null != keyList) {
keyList.remove(k);
}
}
/**
* Returns true if the keyList contains at least 1 valid key and false
* otherwise.
*/
private synchronized boolean containsValidKeys() {
for (int i = 0; i < keyList.size(); i++) {
SelectionKey key = keyList.get(i);
if (key != null && key.isValid()) {
return true;
}
}
return false;
}
}