summaryrefslogtreecommitdiffstats
path: root/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
blob: 04b4730dbf0bd35d20ca8fed87eebc391de8c32d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*
 *  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>();

    private final Object blockingLock = new Object();

    boolean isBlocking = true;

    /**
     * Constructs a new {@code AbstractSelectableChannel}.
     *
     * @param selectorProvider
     *            the selector provider that creates this channel.
     */
    protected AbstractSelectableChannel(SelectorProvider selectorProvider) {
        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 (SelectionKey key : keyList) {
            if (key != null && 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("no valid ops in interest set: " + interestSet);
        }

        synchronized (blockingLock) {
            if (isBlocking) {
                throw new IllegalBlockingModeException();
            }
            if (!selector.isOpen()) {
                if (interestSet == 0) {
                    // throw ISE exactly to keep consistency
                    throw new IllegalSelectorException();
                }
                // throw NPE exactly to keep consistency
                throw new NullPointerException();
            }
            SelectionKey key = keyFor(selector);
            if (key == null) {
                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 (SelectionKey key : keyList) {
            if (key != null) {
                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()) {
            throw new ClosedChannelException();
        }
        synchronized (blockingLock) {
            if (isBlocking == blockingMode) {
                return this;
            }
            if (blockingMode && containsValidKeys()) {
                throw new IllegalBlockingModeException();
            }
            implConfigureBlocking(blockingMode);
            isBlocking = blockingMode;
        }
        return this;
    }

    /**
     * Implements the configuration of blocking/non-blocking mode.
     *
     * @param blocking true for blocking, false for non-blocking.
     * @throws IOException
     *             if an I/O error occurs.
     */
    protected abstract void implConfigureBlocking(boolean blocking) throws IOException;

    /*
     * package private for deregister method in AbstractSelector.
     */
    synchronized void deregister(SelectionKey k) {
        if (keyList != null) {
            keyList.remove(k);
        }
    }

    /**
     * Returns true if the keyList contains at least 1 valid key and false
     * otherwise.
     */
    private synchronized boolean containsValidKeys() {
        for (SelectionKey key : keyList) {
            if (key != null && key.isValid()) {
                return true;
            }
        }
        return false;
    }
}