/* 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 org.apache.harmony.nio.internal; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedSelectorException; import java.nio.channels.IllegalSelectorException; import java.nio.channels.Pipe; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.AbstractSelectionKey; import java.nio.channels.spi.AbstractSelector; import java.nio.channels.spi.SelectorProvider; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.harmony.luni.platform.FileDescriptorHandler; import org.apache.harmony.luni.platform.Platform; /* * Default implementation of java.nio.channels.Selector * */ final class SelectorImpl extends AbstractSelector { private static final int MOCK_WRITEBUF_SIZE = 1; private static final int MOCK_READBUF_SIZE = 8; private static final int NA = 0; private static final int READABLE = 1; private static final int WRITEABLE = 2; private static final int SELECT_BLOCK = -1; private static final int SELECT_NOW = 0; // keysLock is used to brief synchronization when get selectionKeys snapshot // before selection final Object keysLock = new Object(); private final Set<SelectionKey> keys = new HashSet<SelectionKey>(); private Set<SelectionKey> unmodifiableKeys = Collections .unmodifiableSet(keys); private final Set<SelectionKey> selectedKeys = new HashSet<SelectionKey>(); private Set<SelectionKey> unaddableSelectedKeys = new UnaddableSet<SelectionKey>( selectedKeys); // sink and source are used by wakeup() private Pipe.SinkChannel sink; private Pipe.SourceChannel source; private FileDescriptor sourcefd; private SelectionKey[] readableChannels; private SelectionKey[] writableChannels; private List<FileDescriptor> readableFDs = new ArrayList<FileDescriptor>(); private List<FileDescriptor> writableFDs = new ArrayList<FileDescriptor>(); private FileDescriptor[] readable; private FileDescriptor[] writable; public SelectorImpl(SelectorProvider selectorProvider) { super(selectorProvider); try { Pipe mockSelector = selectorProvider.openPipe(); sink = mockSelector.sink(); source = mockSelector.source(); sourcefd = ((FileDescriptorHandler)source).getFD(); source.configureBlocking(false); } catch (IOException e) { // do nothing } } /* * @see java.nio.channels.spi.AbstractSelector#implCloseSelector() */ protected void implCloseSelector() throws IOException { doCancel(); for (SelectionKey sk : keys) { deregister((AbstractSelectionKey) sk); } wakeup(); } /* * @see java.nio.channels.spi.AbstractSelector#register(java.nio.channels.spi.AbstractSelectableChannel, * int, java.lang.Object) */ protected SelectionKey register(AbstractSelectableChannel channel, int operations, Object attachment) { if (!provider().equals(channel.provider())) { throw new IllegalSelectorException(); } synchronized (this) { synchronized (keys) { SelectionKey sk = new SelectionKeyImpl(channel, operations, attachment, this); keys.add(sk); return sk; } } } /* * @see java.nio.channels.Selector#keys() */ public synchronized Set<SelectionKey> keys() { closeCheck(); return unmodifiableKeys; } private void closeCheck() { if (!isOpen()) { throw new ClosedSelectorException(); } } /* * @see java.nio.channels.Selector#select() */ public int select() throws IOException { return selectInternal(SELECT_BLOCK); } /* * @see java.nio.channels.Selector#select(long) */ public int select(long timeout) throws IOException { if (timeout < 0) { throw new IllegalArgumentException(); } return selectInternal((0 == timeout) ? SELECT_BLOCK : timeout); } /* * @see java.nio.channels.Selector#selectNow() */ public int selectNow() throws IOException { return selectInternal(SELECT_NOW); } private int selectInternal(long timeout) throws IOException { closeCheck(); synchronized (this) { synchronized (keys) { synchronized (selectedKeys) { doCancel(); int[] readyChannels = null; boolean isBlock = (SELECT_NOW != timeout); // BEGIN android-removed // copied from newer version of harmony // if (keys.size() == 0) { // return 0; // } // END android-removed prepareChannels(); try { if (isBlock) { begin(); } readyChannels = Platform.getNetworkSystem().select(readable, writable, timeout); } finally { // clear results for next select readableFDs.clear(); writableFDs.clear(); if (isBlock) { end(); } } return processSelectResult(readyChannels); } } } } private boolean isConnected(SelectionKeyImpl key) { SelectableChannel channel = key.channel(); if (channel instanceof SocketChannel) { return ((SocketChannel) channel).isConnected(); } return true; } // Prepares and adds channels to list for selection private void prepareChannels() { readableFDs.add(sourcefd); List<SelectionKey> readChannelList = new ArrayList<SelectionKey>(); readChannelList.add(source.keyFor(this)); List<SelectionKey> writeChannelList = new ArrayList<SelectionKey>(); synchronized (keysLock) { for (Iterator<SelectionKey> i = keys.iterator(); i.hasNext();) { SelectionKeyImpl key = (SelectionKeyImpl) i.next(); key.oldInterestOps = key.interestOps(); boolean isReadableChannel = ((SelectionKey.OP_ACCEPT | SelectionKey.OP_READ) & key.oldInterestOps) != 0; boolean isWritableChannel = ((SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE) & key.oldInterestOps) != 0; SelectableChannel channel = key.channel(); if (isReadableChannel) { readChannelList.add(channel.keyFor(this)); readableFDs.add(((FileDescriptorHandler)channel).getFD()); } if (isWritableChannel) { writeChannelList.add(channel.keyFor(this)); writableFDs.add(((FileDescriptorHandler)channel).getFD()); } } } readableChannels = readChannelList.toArray(new SelectionKey[0]); writableChannels = writeChannelList.toArray(new SelectionKey[0]); readable = readableFDs.toArray(new FileDescriptor[0]); writable = writableFDs.toArray(new FileDescriptor[0]); } // Analyses selected channels and adds keys of ready channels to // selectedKeys list private int processSelectResult(int[] readyChannels) throws IOException { if (0 == readyChannels.length) { return 0; } // if the mock channel is selected, read the content. if (READABLE == readyChannels[0]) { ByteBuffer readbuf = ByteBuffer.allocate(MOCK_READBUF_SIZE); while (source.read(readbuf) > 0) { readbuf.flip(); } } int selected = 0; for (int i = 1; i < readyChannels.length; i++) { SelectionKeyImpl key = (SelectionKeyImpl) (i >= readable.length ? writableChannels[i - readable.length] : readableChannels[i]); if (null == key) { continue; } boolean isOldSelectedKey = selectedKeys.contains(key); int selectedOp = 0; // set ready ops switch (readyChannels[i]) { case NA: selectedOp = 0; break; case READABLE: selectedOp = (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT) & key.oldInterestOps; break; case WRITEABLE: if (isConnected(key)) { selectedOp = SelectionKey.OP_WRITE & key.oldInterestOps; } else { selectedOp = SelectionKey.OP_CONNECT & key.oldInterestOps; } break; } if (0 != selectedOp) { if (isOldSelectedKey && key.readyOps() != selectedOp) { key.setReadyOps(key.readyOps() | selectedOp); selected++; } else if (!isOldSelectedKey) { key.setReadyOps(selectedOp); selectedKeys.add(key); selected++; } } } readableChannels = null; writableChannels = null; return selected; } /* * @see java.nio.channels.Selector#selectedKeys() */ public synchronized Set<SelectionKey> selectedKeys() { closeCheck(); return unaddableSelectedKeys; } private void doCancel() { Set<SelectionKey> cancelledKeys = cancelledKeys(); synchronized (cancelledKeys) { if (cancelledKeys.size() > 0) { for (SelectionKey currentkey : cancelledKeys) { deregister((AbstractSelectionKey) currentkey); keys.remove(currentkey); selectedKeys.remove(currentkey); } } cancelledKeys.clear(); } } /* * @see java.nio.channels.Selector#wakeup() */ public Selector wakeup() { try { sink.write(ByteBuffer.allocate(MOCK_WRITEBUF_SIZE)); } catch (IOException e) { // do nothing } return this; } private static class UnaddableSet<E> implements Set<E> { private Set<E> set; UnaddableSet(Set<E> set) { this.set = set; } public boolean equals(Object object) { return set.equals(object); } public int hashCode() { return set.hashCode(); } public boolean add(E object) { throw new UnsupportedOperationException(); } public boolean addAll(Collection<? extends E> c) { throw new UnsupportedOperationException(); } public void clear() { set.clear(); } public boolean contains(Object object) { return set.contains(object); } public boolean containsAll(Collection<?> c) { return set.containsAll(c); } public boolean isEmpty() { return set.isEmpty(); } public Iterator<E> iterator() { return set.iterator(); } public boolean remove(Object object) { return set.remove(object); } public boolean removeAll(Collection<?> c) { return set.removeAll(c); } public boolean retainAll(Collection<?> c) { return set.retainAll(c); } public int size() { return set.size(); } public Object[] toArray() { return set.toArray(); } public <T> T[] toArray(T[] a) { return set.toArray(a); } } }