content: /*
   * Copyright (c) 2000, 2013, 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.*;
  
  
  /**
   * Base implementation class for selectable channels.
   *
   * <p> 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
   * 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.  </p>
   *
   *
   * @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();
  
      // Blocking mode, protected by regLock
      boolean blocking = true;
  
      /**
       * 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.
       *
       * <p>  This method first verifies that this channel is open and that the
       * given initial interest set is valid.
       *
       * <p> 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.
       *
       * <p> 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.
       * </p>
       *
       * @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 (blocking)
                  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.
       *
       * <p> This method, which is specified in the {@link
       * AbstractInterruptibleChannel} class and is invoked by the {@link
       * 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.  </p>
       */
      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.
       *
       * <p> This method is invoked by the {@link 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.
       *
       * <p> 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.
       * </p>
       *
       * @throws  IOException
       *          If an I/O error occurs
       */
      protected abstract void implCloseSelectableChannel() throws IOException;
  
  
      // -- Blocking --
  
      public final boolean isBlocking() {
          synchronized (regLock) {
              return blocking;
          }
      }
  
      public final Object blockingLock() {
          return regLock;
      }
  
      /**
       * Adjusts this channel's blocking mode.
       *
       * <p> 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.  </p>
       */
      public final SelectableChannel configureBlocking(boolean block)
          throws IOException
      {
          synchronized (regLock) {
              if (!isOpen())
                  throw new ClosedChannelException();
              if (blocking == block)
                  return this;
              if (block && haveValidKeys())
                  throw new IllegalBlockingModeException();
              implConfigureBlocking(block);
              blocking = block;
          }
          return this;
      }
  
      /**
       * Adjusts this channel's blocking mode.
       *
       * <p> 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.  </p>
       *
       * @param  block  If <tt>true</tt> then this channel will be placed in
       *                blocking mode; if <tt>false</tt> then it will be placed
       *                non-blocking mode
       *
       * @throws IOException
       *         If an I/O error occurs
       */
      protected abstract void implConfigureBlocking(boolean block)
          throws IOException;
  
  }
  