1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.nio.channels.spi;
19
20import java.io.IOException;
21import java.nio.channels.CancelledKeyException;
22import java.nio.channels.ClosedChannelException;
23import java.nio.channels.IllegalBlockingModeException;
24import java.nio.channels.IllegalSelectorException;
25import java.nio.channels.SelectableChannel;
26import java.nio.channels.SelectionKey;
27import java.nio.channels.Selector;
28import java.util.ArrayList;
29import java.util.List;
30
31/**
32 * {@code AbstractSelectableChannel} is the base implementation class for
33 * selectable channels. It declares methods for registering, unregistering and
34 * closing selectable channels. It is thread-safe.
35 */
36public abstract class AbstractSelectableChannel extends SelectableChannel {
37
38    private final SelectorProvider provider;
39
40    /*
41     * The collection of key.
42     */
43    private List<SelectionKey> keyList = new ArrayList<SelectionKey>();
44
45    private final Object blockingLock = new Object();
46
47    boolean isBlocking = true;
48
49    /**
50     * Constructs a new {@code AbstractSelectableChannel}.
51     *
52     * @param selectorProvider
53     *            the selector provider that creates this channel.
54     */
55    protected AbstractSelectableChannel(SelectorProvider selectorProvider) {
56        provider = selectorProvider;
57    }
58
59    /**
60     * Returns the selector provider that has created this channel.
61     *
62     * @see java.nio.channels.SelectableChannel#provider()
63     * @return this channel's selector provider.
64     */
65    @Override
66    public final SelectorProvider provider() {
67        return provider;
68    }
69
70    /**
71     * Indicates whether this channel is registered with one or more selectors.
72     *
73     * @return {@code true} if this channel is registered with a selector,
74     *         {@code false} otherwise.
75     */
76    @Override
77    synchronized public final boolean isRegistered() {
78        return !keyList.isEmpty();
79    }
80
81    /**
82     * Gets this channel's selection key for the specified selector.
83     *
84     * @param selector
85     *            the selector with which this channel has been registered.
86     * @return the selection key for the channel or {@code null} if this channel
87     *         has not been registered with {@code selector}.
88     */
89    @Override
90    synchronized public final SelectionKey keyFor(Selector selector) {
91        for (SelectionKey key : keyList) {
92            if (key != null && key.selector() == selector) {
93                return key;
94            }
95        }
96        return null;
97    }
98
99    /**
100     * Registers this channel with the specified selector for the specified
101     * interest set. If the channel is already registered with the selector, the
102     * {@link SelectionKey interest set} is updated to {@code interestSet} and
103     * the corresponding selection key is returned. If the channel is not yet
104     * registered, this method calls the {@code register} method of
105     * {@code selector} and adds the selection key to this channel's key set.
106     *
107     * @param selector
108     *            the selector with which to register this channel.
109     * @param interestSet
110     *            this channel's {@link SelectionKey interest set}.
111     * @param attachment
112     *            the object to attach, can be {@code null}.
113     * @return the selection key for this registration.
114     * @throws CancelledKeyException
115     *             if this channel is registered but its key has been canceled.
116     * @throws ClosedChannelException
117     *             if this channel is closed.
118     * @throws IllegalArgumentException
119     *             if {@code interestSet} is not supported by this channel.
120     * @throws IllegalBlockingModeException
121     *             if this channel is in blocking mode.
122     * @throws IllegalSelectorException
123     *             if this channel does not have the same provider as the given
124     *             selector.
125     */
126    @Override
127    public final SelectionKey register(Selector selector, int interestSet,
128            Object attachment) throws ClosedChannelException {
129        if (!isOpen()) {
130            throw new ClosedChannelException();
131        }
132        if (!((interestSet & ~validOps()) == 0)) {
133            throw new IllegalArgumentException("no valid ops in interest set: " + interestSet);
134        }
135
136        synchronized (blockingLock) {
137            if (isBlocking) {
138                throw new IllegalBlockingModeException();
139            }
140            if (!selector.isOpen()) {
141                if (interestSet == 0) {
142                    // throw ISE exactly to keep consistency
143                    throw new IllegalSelectorException();
144                }
145                // throw NPE exactly to keep consistency
146                throw new NullPointerException("selector not open");
147            }
148            SelectionKey key = keyFor(selector);
149            if (key == null) {
150                key = ((AbstractSelector) selector).register(this, interestSet, attachment);
151                keyList.add(key);
152            } else {
153                if (!key.isValid()) {
154                    throw new CancelledKeyException();
155                }
156                key.interestOps(interestSet);
157                key.attach(attachment);
158            }
159            return key;
160        }
161    }
162
163    /**
164     * Implements the channel closing behavior. Calls
165     * {@code implCloseSelectableChannel()} first, then loops through the list
166     * of selection keys and cancels them, which unregisters this channel from
167     * all selectors it is registered with.
168     *
169     * @throws IOException
170     *             if a problem occurs while closing the channel.
171     */
172    @Override
173    synchronized protected final void implCloseChannel() throws IOException {
174        implCloseSelectableChannel();
175        for (SelectionKey key : keyList) {
176            if (key != null) {
177                key.cancel();
178            }
179        }
180    }
181
182    /**
183     * Implements the closing function of the SelectableChannel. This method is
184     * called from {@code implCloseChannel()}.
185     *
186     * @throws IOException
187     *             if an I/O exception occurs.
188     */
189    protected abstract void implCloseSelectableChannel() throws IOException;
190
191    /**
192     * Indicates whether this channel is in blocking mode.
193     *
194     * @return {@code true} if this channel is blocking, {@code false}
195     *         otherwise.
196     */
197    @Override
198    public final boolean isBlocking() {
199        synchronized (blockingLock) {
200            return isBlocking;
201        }
202    }
203
204    /**
205     * Gets the object used for the synchronization of {@code register} and
206     * {@code configureBlocking}.
207     *
208     * @return the synchronization object.
209     */
210    @Override
211    public final Object blockingLock() {
212        return blockingLock;
213    }
214
215    /**
216     * Sets the blocking mode of this channel. A call to this method blocks if
217     * other calls to this method or to {@code register} are executing. The
218     * actual setting of the mode is done by calling
219     * {@code implConfigureBlocking(boolean)}.
220     *
221     * @see java.nio.channels.SelectableChannel#configureBlocking(boolean)
222     * @param blockingMode
223     *            {@code true} for setting this channel's mode to blocking,
224     *            {@code false} to set it to non-blocking.
225     * @return this channel.
226     * @throws ClosedChannelException
227     *             if this channel is closed.
228     * @throws IllegalBlockingModeException
229     *             if {@code block} is {@code true} and this channel has been
230     *             registered with at least one selector.
231     * @throws IOException
232     *             if an I/O error occurs.
233     */
234    @Override
235    public final SelectableChannel configureBlocking(boolean blockingMode) throws IOException {
236        if (!isOpen()) {
237            throw new ClosedChannelException();
238        }
239        synchronized (blockingLock) {
240            if (isBlocking == blockingMode) {
241                return this;
242            }
243            if (blockingMode && containsValidKeys()) {
244                throw new IllegalBlockingModeException();
245            }
246            implConfigureBlocking(blockingMode);
247            isBlocking = blockingMode;
248        }
249        return this;
250    }
251
252    /**
253     * Implements the configuration of blocking/non-blocking mode.
254     *
255     * @param blocking true for blocking, false for non-blocking.
256     * @throws IOException
257     *             if an I/O error occurs.
258     */
259    protected abstract void implConfigureBlocking(boolean blocking) throws IOException;
260
261    /*
262     * package private for deregister method in AbstractSelector.
263     */
264    synchronized void deregister(SelectionKey k) {
265        if (keyList != null) {
266            keyList.remove(k);
267        }
268    }
269
270    /**
271     * Returns true if the keyList contains at least 1 valid key and false
272     * otherwise.
273     */
274    private synchronized boolean containsValidKeys() {
275        for (SelectionKey key : keyList) {
276            if (key != null && key.isValid()) {
277                return true;
278            }
279        }
280        return false;
281    }
282}
283