/**
  * Add a {@link SelectorHandler}
  *
  * @param selectorHandler - the {@link SelectorHandler}
  */
 public void addSelectorHandler(SelectorHandler selectorHandler) {
   selectorHandlers.add(selectorHandler);
   if (stateHolder.getState(false) != null && !State.STOPPED.equals(stateHolder.getState())) {
     addSelectorHandlerOnReadControllers(selectorHandler);
     if (readySelectorHandlerCounter != null) {
       readySelectorHandlerCounter.incrementAndGet();
     }
     if (stoppedSelectorHandlerCounter != null) {
       stoppedSelectorHandlerCounter.incrementAndGet();
     }
     startSelectorHandlerRunner(selectorHandler, true);
   }
 }
 /**
  * This method initializes this Controller's default thread pool, default
  * ProtocolChainInstanceHandler, default SelectorHandler(s) and default ConnectorHandlerPool.
  * These defaults can be overridden after this Controller constructor is called and before calling
  * Controller.start() using this Controller's mutator methods to set a different thread pool,
  * ProtocolChainInstanceHandler, SelectorHandler(s) or ConnectorHandlerPool.
  */
 private void initializeDefaults() {
   if (threadPool == null) {
     threadPool = new DefaultThreadPool();
   }
   if (instanceHandler == null) {
     instanceHandler = new DefaultProtocolChainInstanceHandler();
   }
   if (selectorHandlers == null) {
     selectorHandlers = new LinkedTransferQueue<SelectorHandler>();
   }
   if (connectorHandlerPool == null) {
     connectorHandlerPool = new DefaultConnectorHandlerPool(this);
   }
   controllers.add(this);
 }
  /**
   * Start the Controller. If the thread pool and/or Handler has not been defined, the default will
   * be used.
   */
  public void start() throws IOException {
    stateHolder.getStateLocker().writeLock().lock();
    boolean isUnlocked = false;
    try {
      if (stateHolder.getState(false) == null || stateHolder.getState(false) == State.STOPPED) {
        // if selectorHandlers were not set by user explicitly,
        // add TCPSelectorHandler by default
        if (selectorHandlers.isEmpty()) {
          SelectorHandler selectorHandler = new TCPSelectorHandler();
          selectorHandlers.add(selectorHandler);
        }

        if (readThreadsCount > 0) {
          initReadThreads();
          multiReadThreadSelectorHandler = new RoundRobinSelectorHandler(readThreadControllers);
        }

        stateHolder.setState(State.STARTED, false);
        notifyStarted();

        int selectorHandlerCount = selectorHandlers.size();
        readySelectorHandlerCounter = new AtomicInteger(selectorHandlerCount);
        stoppedSelectorHandlerCounter = new AtomicInteger(selectorHandlerCount);

        Iterator<SelectorHandler> it = selectorHandlers.iterator();
        if (selectorHandlerCount > 1) {
          for (; it.hasNext() && selectorHandlerCount-- > 0; ) {
            SelectorHandler selectorHandler = it.next();
            startSelectorHandlerRunner(selectorHandler, true);
          }
        } else if (it.hasNext()) {
          SelectorHandler selectorHandler = it.next();
          stateHolder.getStateLocker().writeLock().unlock();
          isUnlocked = true;
          startSelectorHandlerRunner(selectorHandler, false);
        }
      }
    } finally {
      if (!isUnlocked) {
        stateHolder.getStateLocker().writeLock().unlock();
      }
    }

    waitUntilSeletorHandlersStop();

    if (readThreadsCount > 0) {
      multiReadThreadSelectorHandler.shutdown();
      multiReadThreadSelectorHandler = null;

      for (Controller readController : readThreadControllers) {
        try {
          readController.stop();
        } catch (IOException e) {
          logger.log(Level.WARNING, "Exception occured when stopping read Controller!", e);
        }
      }

      readThreadControllers = null;
    }

    selectorHandlers.clear();
    threadPool.shutdown();
    attributes = null;

    // Notify Controller listeners
    for (ControllerStateListener stateListener : stateListeners) {
      stateListener.onStopped();
    }
  }