/** This is synchronized to ensure that we process the queue serially. */
  private synchronized void complete(String message) {
    // This is a weird problem with writing stuff while idling. Need to investigate it more, but
    // for now just ignore it.
    if (MESSAGE_COULDNT_BE_FETCHED_REGEX.matcher(message).matches()) {
      log.warn(
          "Some messages in the batch could not be fetched for {}\n"
              + "---cmd---\n{}\n---wire---\n{}\n---end---\n",
          new Object[] {config.getUsername(), getCommandTrace(), getWireTrace()});
      errorStack.push(new Error(completions.peek(), message, wireTrace.list()));
      throw new RuntimeException(
          "Some messages in the batch could not be fetched for user " + config.getUsername());
    }

    CommandCompletion completion = completions.peek();
    if (completion == null) {
      if ("+ idling".equalsIgnoreCase(message)) {
        synchronized (idleMutex) {
          idler.idleStart();
          log.trace("IDLE entered.");
          idleAcknowledged.set(true);
        }
      } else {
        log.error("Could not find the completion for message {} (Was it ever issued?)", message);
        errorStack.push(new Error(null, "No completion found!", wireTrace.list()));
      }
      return;
    }

    if (completion.complete(message)) {
      completions.poll();
    }
  }
 @Override
 public String expected() {
   return completion == null ? null : completion.toString();
 }
  private void processMessage(String message) throws Exception {
    Boolean toStdOut = logAllMessagesForUsers.get(config.getUsername());
    if (toStdOut != null) {
      if (toStdOut) System.out.println("IMAPrcv[" + config.getUsername() + "]: " + message);
      else log.info("IMAPrcv[{}]: {}", config.getUsername(), message);
    }

    wireTrace.add(message);
    log.trace(message);
    if (SYSTEM_ERROR_REGEX.matcher(message).matches()
        || ". NO [ALERT] Account exceeded command or bandwidth limits. (Failure)"
            .equalsIgnoreCase(message.trim())) {
      log.warn(
          "{} disconnected by IMAP Server due to system error: {}", config.getUsername(), message);
      disconnectAbnormally(message);
      return;
    }

    try {
      if (halt) {
        log.error(
            "Mail client for {} is halted but continues to receive messages, ignoring!",
            config.getUsername());
        return;
      }
      if (loginSuccess.getCount() > 0) {
        if (message.startsWith(CAPABILITY_PREFIX)) {
          this.capabilities =
              Arrays.asList(message.substring(CAPABILITY_PREFIX.length() + 1).split("[ ]+"));
          return;
        } else if (AUTH_SUCCESS_REGEX.matcher(message).matches()) {
          log.info("Authentication success for user {}", config.getUsername());
          loginSuccess.countDown();
        } else {
          Matcher matcher = COMMAND_FAILED_REGEX.matcher(message);
          if (matcher.find()) {
            // WARNING: DO NOT COUNTDOWN THE LOGIN LATCH ON FAILURE!!!

            log.warn("Authentication failed for {} due to: {}", config.getUsername(), message);
            errorStack.push(
                new Error(
                    null /* logins have no completion */, extractError(matcher), wireTrace.list()));
            disconnectAbnormally(message);
          }
        }
        return;
      }

      // Copy to local var as the value can change underneath us.
      FolderObserver observer = this.observer;
      if (idleRequested.get() || idleAcknowledged.get()) {
        synchronized (idleMutex) {
          if (IDLE_ENDED_REGEX.matcher(message).matches()) {
            idleRequested.compareAndSet(true, false);
            idleAcknowledged.set(false);

            // Now fire the events.
            PushedData data = pushedData;
            pushedData = null;

            idler.idleEnd();
            observer.changed(
                data.pushAdds.isEmpty() ? null : data.pushAdds,
                data.pushRemoves.isEmpty() ? null : data.pushRemoves);
            return;
          }

          // Queue up any push notifications to publish to the client in a second.
          Matcher existsMatcher = IDLE_EXISTS_REGEX.matcher(message);
          boolean matched = false;
          if (existsMatcher.matches()) {
            int number = Integer.parseInt(existsMatcher.group(1));
            pushedData.pushAdds.add(number);
            pushedData.pushRemoves.remove(number);
            matched = true;
          } else {
            Matcher expungeMatcher = IDLE_EXPUNGE_REGEX.matcher(message);
            if (expungeMatcher.matches()) {
              int number = Integer.parseInt(expungeMatcher.group(1));
              pushedData.pushRemoves.add(number);
              pushedData.pushAdds.remove(number);
              matched = true;
            }
          }

          // Stop idling, when we get the idle ended message (next cycle) we can publish what's been
          // gathered.
          if (matched) {
            if (!pushedData.idleExitSent) {
              idler.done();
              pushedData.idleExitSent = true;
            }
            return;
          }
        }
      }

      complete(message);
    } catch (Exception ex) {
      CommandCompletion completion = completions.poll();
      if (completion != null) completion.error(message, ex);
      else {
        log.error(
            "Strange exception during mail processing (no completions available!): {}",
            message,
            ex);
        errorStack.push(new Error(null, "No completions available!", wireTrace.list()));
      }
      throw ex;
    }
  }
 // DO NOT synchronize!
 public void enqueue(CommandCompletion completion) {
   completions.add(completion);
   commandTrace.add(new Date().toString() + " " + completion.toString());
 }