Open Source Repository

Home /mail/mail-1.4.3 | Repository Home



com/sun/mail/imap/IMAPFolder.java
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.mail.imap;

import java.util.Date;
import java.util.Vector;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.io.*;

import javax.mail.*;
import javax.mail.event.*;
import javax.mail.internet.*;
import javax.mail.search.*;

import com.sun.mail.util.*;
import com.sun.mail.iap.*;
import com.sun.mail.imap.protocol.*;

/**
 * This class implements an IMAP folder. <p>
 *
 * A closed IMAPFolder object shares a protocol connection with its IMAPStore
 * object. When the folder is opened, it gets its own protocol connection. <p>
 *
 * Applications that need to make use of IMAP-specific features may cast
 * a <code>Folder</code> object to an <code>IMAPFolder</code> object and
 * use the methods on this class. The {@link #getQuota getQuota} and
 {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
 * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
 * for more information. <p>
 *
 * The {@link #getACL getACL}{@link #addACL addACL},
 {@link #removeACL removeACL}{@link #addRights addRights},
 {@link #removeRights removeRights}{@link #listRights listRights}, and
 {@link #myRights myRights} methods support the IMAP ACL extension.
 * Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A>
 * for more information. <p>
 *
 * The {@link #doCommand doCommand} method and
 {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand}
 * interface support use of arbitrary IMAP protocol commands. <p>
 *
 * See the <a href="package-summary.html">com.sun.mail.imap</a> package
 * documentation for further information on the IMAP protocol provider. <p>
 *
 <strong>WARNING:</strong> The APIs unique to this class should be
 * considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
 * future in ways that are incompatible with applications using the
 * current APIs.
 *
 @author  John Mani
 @author  Bill Shannon
 @author  Jim Glennon
 */

/*
 * The folder object itself serves as a lock for the folder's state
 * EXCEPT for the message cache (see below), typically by using
 * synchronized methods.  When checking that a folder is open or
 * closed, the folder's lock must be held.  It's important that the
 * folder's lock is acquired before the messageCacheLock (see below).
 * Thus, the locking hierarchy is that the folder lock, while optional,
 * must be acquired before the messageCacheLock, if it's acquired at
 * all.  Be especially careful of callbacks that occur while holding
 * the messageCacheLock into (e.g.) superclass Folder methods that are
 * synchronized.  Note that methods in IMAPMessage will acquire the
 * messageCacheLock without acquiring the folder lock. <p>
 *
 * When a folder is opened, it creates a messageCache (a Vector) of 
 * empty IMAPMessage objects. Each Message has a messageNumber - which
 * is its index into the messageCache, and a sequenceNumber - which is
 * its IMAP sequence-number. All operations on a Message which involve
 * communication with the server, use the message's sequenceNumber. <p>
 *
 * The most important thing to note here is that the server can send
 * unsolicited EXPUNGE notifications as part of the responses for "most"
 * commands. Refer RFC2060, sections 5.3 &  5.5 for gory details. Also, 
 * the server sends these  notifications AFTER the message has been 
 * expunged. And once a message is expunged, the sequence-numbers of 
 * those messages after the expunged one are renumbered. This essentially
 * means that the mapping between *any* Message and its sequence-number 
 * can change in the period when a IMAP command is issued and its responses
 * are processed. Hence we impose a strict locking model as follows: <p>
 *
 * We define one mutex per folder - this is just a Java Object (named 
 * messageCacheLock). Any time a command is to be issued to the IMAP
 * server (i.e., anytime the corresponding IMAPProtocol method is
 * invoked), follow the below style:
 *    
 *  synchronized (messageCacheLock) { // ACQUIRE LOCK
 *      issue command ()
 *      
 *      // The response processing is typically done within
 *      // the handleResponse() callback. A few commands (Fetch,
 *      // Expunge) return *all* responses and hence their
 *      // processing is done here itself. Now, as part of the
 *      // processing unsolicited EXPUNGE responses, we renumber
 *      // the necessary sequence-numbers. Thus the renumbering
 *      // happens within this critical-region, surrounded by
 *      // locks.
 *      process responses ()
 *  } // RELEASE LOCK
 *
 * This technique is used both by methods in IMAPFolder and by methods
 * in IMAPMessage and other classes that operate on data in the folder.
 * Note that holding the messageCacheLock has the side effect of
 * preventing the folder from being closed, and thus ensuring that the
 * folder's protocol object is still valid.  The protocol object should
 * only be accessed while holding the messageCacheLock (except for calls
 * to IMAPProtocol.isREV1(), which don't need to be protected because it
 * doesn't access the server).
 *      
 * Note that interactions with the Store's protocol connection do
 * not have to be protected as above, since the Store's protocol is
 * never in a "meaningful" SELECT-ed state.
 */

public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
    
    protected String fullName;    // full name
    protected String name;    // name
    protected int type;      // folder type. 
    protected char separator;    // separator
    protected Flags availableFlags;   // available flags
    protected Flags permanentFlags;   // permanent flags
    protected boolean exists = false;   // whether this folder really exists ?
    protected boolean isNamespace = false// folder is a namespace name
    protected String[] attributes;  // name attributes from LIST response

    protected IMAPProtocol protocol;   // this folder's own protocol object
    protected MessageCache messageCache;// message cache
    // accessor lock for message cache
    protected final Object messageCacheLock = new Object();

    protected Hashtable uidTable;  // UID->Message hashtable

    /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL).
     * We use '\uffff' (a non 7bit character) to indicate that we havent
     * yet determined what the separator character is.
     * We use '\u0000' (NUL) to indicate that no separator character
     * exists, i.e., a flat hierarchy
     */
    static final protected char UNKNOWN_SEPARATOR = '\uffff';

    private boolean opened = false;   // is this folder opened ?

    /* This field tracks the state of this folder. If the folder is closed
     * due to external causes (i.e, not thru the close() method), then
     * this field will remain false. If the folder is closed thru the
     * close() method, then this field is set to true.
     *
     * If reallyClosed is false, then a FolderClosedException is
     * generated when a method is invoked on any Messaging object
     * owned by this folder. If reallyClosed is true, then the
     * IllegalStateException runtime exception is thrown.
     */
    private boolean reallyClosed = true;

    /*
     * The idleState field supports the IDLE command.
     * Normally when executing an IMAP command we hold the
     * messageCacheLock and often the folder lock (see above).
     * While executing the IDLE command we can't hold either
     * of these locks or it would prevent other threads from
     * entering Folder methods even far enough to check whether
     * an IDLE command is in progress.  We need to check before
     * issuing another command so that we can abort the IDLE
     * command.
     *
     * The idleState field is protected by the messageCacheLock.
     * The RUNNING state is the normal state and means no IDLE
     * command is in progress.  The IDLE state means we've issued
     * an IDLE command and are reading responses.  The ABORTING
     * state means we've sent the DONE continuation command and
     * are waiting for the thread running the IDLE command to
     * break out of its read loop.
     *
     * When an IDLE command is in progress, the thread calling
     * the idle method will be reading from the IMAP connection
     * while holding neither the folder lock nor the messageCacheLock.
     * It's obviously critical that no other thread try to send a
     * command or read from the connection while in this state.
     * However, other threads can send the DONE continuation
     * command that will cause the server to break out of the IDLE
     * loop and send the ending tag response to the IDLE command.
     * The thread in the idle method that's reading the responses
     * from the IDLE command will see this ending response and
     * complete the idle method, setting the idleState field back
     * to RUNNING, and notifying any threads waiting to use the
     * connection.
     *
     * All uses of the IMAP connection (IMAPProtocol object) must
     * be done while holding the messageCacheLock and must be
     * preceeded by a check to make sure an IDLE command is not
     * running, and abort the IDLE command if necessary.  While
     * waiting for the IDLE command to complete, these other threads
     * will give up the messageCacheLock, but might still be holding
     * the folder lock.  This check is done by the getProtocol()
     * method, resulting in a typical usage pattern of:
     *
     *      synchronized (messageCacheLock) {
     *    IMAPProtocol p = getProtocol();  // may block waiting for IDLE
     *    // ... use protocol
     *      }
     */
    private static final int RUNNING = 0;  // not doing IDLE command
    private static final int IDLE = 1;    // IDLE command in effect
    private static final int ABORTING = 2;  // IDLE command aborting
    private int idleState = RUNNING;

    private int total = -1;    // total number of messages in the
          // message cache
    private int recent = -1;    // number of recent messages
    private int realTotal = -1;    // total number of messages on
              // the server
    private long uidvalidity = -1;  // UIDValidity
    private long uidnext = -1;    // UIDNext
    private boolean doExpungeNotification = true// used in expunge handler

    private Status cachedStatus = null;
    private long cachedStatusTime = 0;

    private boolean hasMessageCountListener = false;  // optimize notification

    private boolean debug = false;
    private PrintStream out;    // debug output stream

    private boolean connectionPoolDebug;

    /**
     * A fetch profile item for fetching headers.
     * This inner class extends the <code>FetchProfile.Item</code>
     * class to add new FetchProfile item types, specific to IMAPFolders.
     *
     @see FetchProfile
     */
    public static class FetchProfileItem extends FetchProfile.Item {
  protected FetchProfileItem(String name) {
      super(name);
  }

  /**
   * HEADERS is a fetch profile item that can be included in a
   <code>FetchProfile</code> during a fetch request to a Folder.
   * This item indicates that the headers for messages in the specified 
   * range are desired to be prefetched. <p>
   
   * An example of how a client uses this is below: <p>
   <blockquote><pre>
   *
   *   FetchProfile fp = new FetchProfile();
   *  fp.add(IMAPFolder.FetchProfileItem.HEADERS);
   *  folder.fetch(msgs, fp);
   *
   </pre></blockquote><p>
   */ 
  public static final FetchProfileItem HEADERS = 
    new FetchProfileItem("HEADERS");

  /**
   * SIZE is a fetch profile item that can be included in a
   <code>FetchProfile</code> during a fetch request to a Folder.
   * This item indicates that the sizes of the messages in the specified 
   * range are desired to be prefetched. <p>
   *
   * SIZE should move to FetchProfile.Item in JavaMail 1.3.
   */
  public static final FetchProfileItem SIZE = 
    new FetchProfileItem("SIZE");
    }

    /**
     * Constructor used to create a possibly non-existent folder.
     *
     @param fullName  fullname of this folder
     @param separator the default separator character for this 
     *      folder's namespace
     @param store  the Store
     */
    protected IMAPFolder(String fullName, char separator, IMAPStore store) {
  super(store);
  if (fullName == null)
      throw new NullPointerException("Folder name is null");
  this.fullName = fullName;
  this.separator = separator;
        debug = store.getSession().getDebug();
        connectionPoolDebug = ((IMAPStore)store).getConnectionPoolDebug();
  out = store.getSession().getDebugOut();
  if (out == null)  // should never happen
      out = System.out;

  /*
   * Work around apparent bug in Exchange.  Exchange
   * will return a name of "Public Folders/" from
   * LIST "%".
   *
   * If name has one separator, and it's at the end,
   * assume this is a namespace name and treat it
   * accordingly.  Usually this will happen as a result
   * of the list method, but this also allows getFolder
   * to work with namespace names.
   */
  this.isNamespace = false;
  if (separator != UNKNOWN_SEPARATOR && separator != '\0') {
      int i = this.fullName.indexOf(separator);
      if (i > && i == this.fullName.length() 1) {
    this.fullName = this.fullName.substring(0, i);
    this.isNamespace = true;
      }
  }
    }

    /**
     * Constructor used to create a possibly non-existent folder.
     *
     @param fullName  fullname of this folder
     @param separator the default separator character for this 
     *      folder's namespace
     @param store  the Store
     */
    protected IMAPFolder(String fullName, char separator, IMAPStore store,
        boolean isNamespace) {
  this(fullName, separator, store);
  this.isNamespace = isNamespace;
    }

    /**
     * Constructor used to create an existing folder.
     */
    protected IMAPFolder(ListInfo li, IMAPStore store) {
  this(li.name, li.separator, store);

  if (li.hasInferiors)
      type |= HOLDS_FOLDERS;
  if (li.canOpen)
      type |= HOLDS_MESSAGES;
  exists = true;
  attributes = li.attrs;
    }
  
    /*
     * Ensure that this folder exists. If 'exists' has been set to true,
     * we don't attempt to validate it with the server again. Note that
     * this can result in a possible loss of sync with the server.
     * ASSERT: Must be called with this folder's synchronization lock held.
     */
    private void checkExists() throws MessagingException {
  // If the boolean field 'exists' is false, check with the
  // server by invoking exists() ..
  if (!exists && !exists())
      throw new FolderNotFoundException(
    this, fullName + " not found");
    }

    /*
     * Ensure the folder is closed.
     * ASSERT: Must be called with this folder's synchronization lock held.
     */
    private void checkClosed() {
  if (opened)
      throw new IllegalStateException(
    "This operation is not allowed on an open folder"
    );
    }

    /*
     * Ensure the folder is open.
     * ASSERT: Must be called with this folder's synchronization lock held.
     */
    private void checkOpened() throws FolderClosedException {
  assert Thread.holdsLock(this);
  if (!opened) {
      if (reallyClosed)
    throw new IllegalStateException(
        "This operation is not allowed on a closed folder"
        );
      else // Folder was closed "implicitly"
    throw new FolderClosedException(this, 
        "Lost folder connection to server"
    );
  }
    }

    /*
     * Check that the given message number is within the range
     * of messages present in this folder. If the message
     * number is out of range, we ping the server to obtain any
     * pending new message notifications from the server.
     */
    private void checkRange(int msgnothrows MessagingException {
  if (msgno < 1// message-numbers start at 1
      throw new IndexOutOfBoundsException("message number < 1");

  if (msgno <= total)
      return;

  // Out of range, let's ping the server and see if
  // the server has more messages for us.

  synchronized(messageCacheLock) { // Acquire lock
      try {
    keepConnectionAlive(false);
      catch (ConnectionException cex) {
    // Oops, lost connection
    throw new FolderClosedException(this, cex.getMessage());
      catch (ProtocolException pex) { 
    throw new MessagingException(pex.getMessage(), pex);
      }
  // Release lock

  if (msgno > total// Still out of range ? Throw up ...
      throw new IndexOutOfBoundsException(msgno + " > " + total);
    }

    /*
     * Check whether the given flags are supported by this server,
     * and also verify that the folder allows setting flags.
     */
    private void checkFlags(Flags flagsthrows MessagingException {
  assert Thread.holdsLock(this);
  if (mode != READ_WRITE)
      throw new IllegalStateException(
    "Cannot change flags on READ_ONLY folder: " + fullName
    );
  /*
  if (!availableFlags.contains(flags))
      throw new MessagingException(
    "These flags are not supported by this implementation"
    );
  */
    }

    /**
     * Get the name of this folder.
     */
    public synchronized String getName() {
  /* Return the last component of this Folder's full name.
   * Folder components are delimited by the separator character.
   */
  if (name == null) {
      try {
    name =   fullName.substring(
          fullName.lastIndexOf(getSeparator()) 1
      );
      catch (MessagingException mex) { }
  }
  return name;
    }

    /**
     * Get the fullname of this folder.
     */
    public synchronized String getFullName() {
  return fullName;  
    }

    /**
     * Get this folder's parent.
     */
    public synchronized Folder getParent() throws MessagingException {
  char c = getSeparator();
  int index;
  if ((index = fullName.lastIndexOf(c)) != -1)
      return new IMAPFolder(fullName.substring(0, index)
          c, (IMAPStore)store);
  else
      return new DefaultFolder((IMAPStore)store);
    }

    /**
     * Check whether this folder really exists on the server.
     */
    public synchronized boolean exists() throws MessagingException {
  // Check whether this folder exists ..
  ListInfo[] li = null;
  final String lname;
  if (isNamespace && separator != '\0')
      lname = fullName + separator;
  else
      lname = fullName;

  li = (ListInfo[])doCommand(new ProtocolCommand() {
      public Object doCommand(IMAPProtocol pthrows ProtocolException {
    return p.list("", lname);
      }
  });

  if (li != null) {
      int i = findName(li, lname);
      fullName = li[i].name;
      separator = li[i].separator;
      int len = fullName.length();
      if (separator != '\0' && len > &&
        fullName.charAt(len - 1== separator) {
    fullName = fullName.substring(0, len - 1);
      }
      type = 0;
      if (li[i].hasInferiors)
    type |= HOLDS_FOLDERS;
      if (li[i].canOpen)
    type |= HOLDS_MESSAGES;
      exists = true;
      attributes = li[i].attrs;
  else {
      exists = opened;
      attributes = null;
  }

  return exists;
    }

    /**
     * Which entry in <code>li</code> matches <code>lname</code>?
     * If the name contains wildcards, more than one entry may be
     * returned.
     */
    private int findName(ListInfo[] li, String lname) {
  int i;
  // if the name contains a wildcard, there might be more than one
  for (i = 0; i < li.length; i++) {
      if (li[i].name.equals(lname))
    break;
  }
  if (i >= li.length) {  // nothing matched exactly
      // XXX - possibly should fail?  But what if server
      // is case insensitive and returns the preferred
      // case of the name here?
      i = 0;    // use first one
  }
  return i;
    }

    /**
     * List all subfolders matching the specified pattern.
     */
    public Folder[] list(String patternthrows MessagingException {
  return doList(pattern, false);
    }

    /**
     * List all subscribed subfolders matching the specified pattern.
     */
    public Folder[] listSubscribed(String patternthrows MessagingException {
  return doList(pattern, true);
    }

    private synchronized Folder[] doList(final String pattern,
    final boolean subscribedthrows MessagingException {
  checkExists()// insure that this folder does exist.
  
  // Why waste a roundtrip to the server?
  if (attributes != null && !isDirectory())
      return new Folder[0];

  final char c = getSeparator();

  ListInfo[] li = (ListInfo[])doCommandIgnoreFailure(
      new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        if (subscribed)
      return p.lsub("", fullName + c + pattern);
        else 
      return p.list("", fullName + c + pattern);
    }
      });

  if (li == null)
      return new Folder[0];

  /*
   * The UW based IMAP4 servers (e.g. SIMS2.0) include
   * current folder (terminated with the separator), when
   * the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%> 
   * returns "mail/" as the first LIST response.
   *
   * Doesn't make sense to include the current folder in this
   * case, so we filter it out. Note that I'm assuming that
   * the offending response is the *first* one, my experiments
   * with the UW & SIMS2.0 servers indicate that .. 
   */
  int start = 0;
  // Check the first LIST response.
  if (li.length > && li[0].name.equals(fullName + c)) 
      start = 1// start from index = 1

  IMAPFolder[] folders = new IMAPFolder[li.length - start];
  for (int i = start; i < li.length; i++)
      folders[i-startnew IMAPFolder(li[i](IMAPStore)store);
  return folders;
    }

    /**
     * Get the separator character.
     */
    public synchronized char getSeparator() throws MessagingException {
  if (separator == UNKNOWN_SEPARATOR) {
      ListInfo[] li = null;

      li = (ListInfo[])doCommand(new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        // REV1 allows the following LIST format to obtain
        // the hierarchy delimiter of non-existent folders
        if (p.isREV1()) // IMAP4rev1
            return p.list(fullName, "");
        else // IMAP4, note that this folder must exist for this
            // to work :(
            return p.list("", fullName);
    }
      });

      if (li != null
    separator = li[0].separator;
      else
    separator = '/'// punt !
  }
  return separator;
    }

    /**
     * Get the type of this folder.
     */
    public synchronized int getType() throws MessagingException {
  if (opened) {
      // never throw FolderNotFoundException if folder is open
      if (attributes == null)
    exists();  // try to fetch attributes
  else {
      checkExists();
  }
  return type;
    }
    
    /**
     * Check whether this folder is subscribed. <p>
     */
    public synchronized boolean isSubscribed() {
  ListInfo[] li = null;
  final String lname;
  if (isNamespace && separator != '\0')
      lname = fullName + separator;
  else
      lname = fullName;

  try {
      li = (ListInfo[])doProtocolCommand(new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        return p.lsub("", lname);
    }
      });
  catch (ProtocolException pex) {
        }

  if (li != null) {
      int i = findName(li, lname);
      return li[i].canOpen;
  else
      return false;
    }

    /**
     * Subscribe/Unsubscribe this folder.
     */
    public synchronized void setSubscribed(final boolean subscribe
      throws MessagingException {
  doCommandIgnoreFailure(new ProtocolCommand() {
      public Object doCommand(IMAPProtocol pthrows ProtocolException {
    if (subscribe)
        p.subscribe(fullName);
    else
        p.unsubscribe(fullName);
    return null;
      }
  });
    }
  
    /**
     * Create this folder, with the specified type.
     */
    public synchronized boolean create(final int type)
        throws MessagingException {

  char c = 0;
  if ((type & HOLDS_MESSAGES== 0)  // only holds folders
      c = getSeparator();
  final char sep = c;
  Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        if ((type & HOLDS_MESSAGES== 0)  // only holds folders
      p.create(fullName + sep);
        else {
      p.create(fullName);

      // Certain IMAP servers do not allow creation of folders
      // that can contain messages *and* subfolders. So, if we
      // were asked to create such a folder, we should verify
      // that we could indeed do so.
      if ((type & HOLDS_FOLDERS!= 0) {
          // we want to hold subfolders and messages. Check
          // whether we could create such a folder.
          ListInfo[] li = p.list("", fullName);
          if (li != null && !li[0].hasInferiors) {
        // Hmm ..the new folder 
        // doesn't support Inferiors ? Fail
        p.delete(fullName);
        throw new ProtocolException("Unsupported type");
          }
      }
        }
        return Boolean.TRUE;
    }
      });

  if (ret == null)
      return false// CREATE failure, maybe this 
        // folder already exists ?

  // exists = true;
  // this.type = type;
  boolean retb = exists();  // set exists, type, and attributes
  if (retb)    // Notify listeners on self and our Store
      notifyFolderListeners(FolderEvent.CREATED);
  return retb;
    }

    /**
     * Check whether this folder has new messages.
     */
    public synchronized boolean hasNewMessages() throws MessagingException {
  if (opened) {  // If we are open, we already have this information
      // Folder is open, make sure information is up to date
      synchronized(messageCacheLock) {
    // tickle the folder and store connections.
    try {
        keepConnectionAlive(true);
    catch (ConnectionException cex) {
        throw new FolderClosedException(this, cex.getMessage());
    catch (ProtocolException pex) {
        throw new MessagingException(pex.getMessage(), pex);
    }
    return recent > true false;
      }
  }

  // First, the cheap way - use LIST and look for the \Marked
  // or \Unmarked tag

  ListInfo[] li = null;
  final String lname;
  if (isNamespace && separator != '\0')
      lname = fullName + separator;
  else
      lname = fullName;
  li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() {
      public Object doCommand(IMAPProtocol pthrows ProtocolException {
    return p.list("", lname);
      }
  });

  // if folder doesn't exist, throw exception
  if (li == null)
      throw new FolderNotFoundException(this, fullName + " not found");

  int i = findName(li, lname);
  if (li[i].changeState == ListInfo.CHANGED)
      return true;
  else if (li[i].changeState == ListInfo.UNCHANGED)
      return false;

  // LIST didn't work. Try the hard way, using STATUS
  try {
      Status status = getStatus();
      if (status.recent > 0)
    return true;
      else
    return false;
  catch (BadCommandException bex) {
      // Probably doesn't support STATUS, tough luck.
      return false;
  catch (ConnectionException cex) {
      throw new StoreClosedException(store, cex.getMessage());
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  }
    }

    /**
     * Get the named subfolder. <p>
     */
    public synchronized Folder getFolder(String name)
        throws MessagingException {
  // If we know that this folder is *not* a directory, don't
  // send the request to the server at all ...
  if (attributes != null && !isDirectory())
      throw new MessagingException("Cannot contain subfolders");

  char c = getSeparator();
  return new IMAPFolder(fullName + c + name, c, (IMAPStore)store);
    }

    /**
     * Delete this folder.
     */
    public synchronized boolean delete(boolean recurse
      throws MessagingException {  
  checkClosed()// insure that this folder is closed.

  if (recurse) {
      // Delete all subfolders.
      Folder[] f = list();
      for (int i = 0; i < f.length; i++)
    f[i].delete(recurse)// ignore intermediate failures
  }

  // Attempt to delete this folder

  Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
      public Object doCommand(IMAPProtocol pthrows ProtocolException {
    p.delete(fullName);
    return Boolean.TRUE;
      }
  });

  if (ret == null)
      // Non-existent folder/No permission ??
      return false;

  // DELETE succeeded.
  exists = false;
  attributes = null;

  // Notify listeners on self and our Store
  notifyFolderListeners(FolderEvent.DELETED);
  return true;
    }

    /**
     * Rename this folder. <p>
     */
    public synchronized boolean renameTo(final Folder f)
        throws MessagingException {
  checkClosed()// insure that we are closed.
  checkExists();
  if (f.getStore() != store)
      throw new MessagingException("Can't rename across Stores");


  Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
      public Object doCommand(IMAPProtocol pthrows ProtocolException {
    p.rename(fullName, f.getFullName());
    return Boolean.TRUE;
      }
  });

  if (ret == null)
      return false;

  exists = false;
  attributes = null;
  notifyFolderRenamedListeners(f);
  return true;
    }

    /**
     * Open this folder in the given mode.
     */
    public synchronized void open(int modethrows MessagingException {
  checkClosed()// insure that we are not already open
  
  MailboxInfo mi = null;
  // Request store for our own protocol connection.
  protocol = ((IMAPStore)store).getProtocol(this);

  synchronized(messageCacheLock) { // Acquire messageCacheLock

      /*
       * Add response handler right away so we get any alerts or
       * notifications that occur during the SELECT or EXAMINE.
       * Have to be sure to remove it if we fail to open the
       * folder.
       */
      protocol.addResponseHandler(this);

      try {
    if (mode == READ_ONLY)
        mi = protocol.examine(fullName);
    else
        mi = protocol.select(fullName);
      catch (CommandFailedException cex) {
    /*
     * Handle SELECT or EXAMINE failure.
     * Try to figure out why the operation failed so we can
     * report a more reasonable exception.
     *
     * Will use our existing protocol object.
     */
    try {
        checkExists()// throw exception if folder doesn't exist

        if ((type & HOLDS_MESSAGES== 0)
      throw new MessagingException(
          "folder cannot contain messages");
        throw new MessagingException(cex.getMessage(), cex);

    finally {
        // folder not open, don't keep this information
        exists = false;
        attributes = null;
        type = 0;
        // connection still good, return it
        releaseProtocol(true);
    }
    // NOTREACHED
      catch (ProtocolException pex) {
    // got a BAD or a BYE; connection may be bad, close it
    try {
        protocol.logout();
    catch (ProtocolException pex2) {
        // ignore
    finally {
        releaseProtocol(false);
        throw new MessagingException(pex.getMessage(), pex);
    }
      }

      if (mi.mode != mode) {
    if (mode == READ_WRITE && mi.mode == READ_ONLY &&
      ((IMAPStore)store).allowReadOnlySelect()) {
        ;    // all ok, allow it
    else {  // otherwise, it's an error
        try {
      // close mailbox and return connection
      protocol.close();
      releaseProtocol(true);
        catch (ProtocolException pex) {
      // something went wrong, close connection
      try {
          protocol.logout();
      catch (ProtocolException pex2) {
          // ignore
      finally {
          releaseProtocol(false);
      }
        finally {
      throw new ReadOnlyFolderException(this,
              "Cannot open in desired mode");
        }

    }
            }
  
      // Initialize stuff.
      opened = true;
      reallyClosed = false;
      this.mode = mi.mode;
      availableFlags = mi.availableFlags;
      permanentFlags = mi.permanentFlags;
      total = realTotal = mi.total;
      recent = mi.recent;
      uidvalidity = mi.uidvalidity;
      uidnext = mi.uidnext;

      // Create the message cache of appropriate size
      messageCache = new MessageCache(this, (IMAPStore)store, total);

  // Release lock

  exists = true;    // if we opened it, it must exist
  attributes = null;  // but we don't yet know its attributes
  type = HOLDS_MESSAGES;  // lacking more info, we know at least this much

  // notify listeners
  notifyConnectionListeners(ConnectionEvent.OPENED);
    }

    /**
     * Prefetch attributes, based on the given FetchProfile.
     */
    public synchronized void fetch(Message[] msgs, FetchProfile fp)
      throws MessagingException {
  checkOpened();
  IMAPMessage.fetch(this, msgs, fp);
    }

    /**
     * Set the specified flags for the given array of messages.
     */
    public synchronized void setFlags(Message[] msgs, Flags flag, boolean value)
      throws MessagingException {
  checkOpened();
  checkFlags(flag)// validate flags

  if (msgs.length == 0// boundary condition
      return;

  synchronized(messageCacheLock) {
      try {
    IMAPProtocol p = getProtocol();
    MessageSet[] ms = Utility.toMessageSet(msgs, null);
    if (ms == null)
        throw new MessageRemovedException(
          "Messages have been removed");
    p.storeFlags(ms, flag, value);
      catch (ConnectionException cex) {
    throw new FolderClosedException(this, cex.getMessage());
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      }
  }
    }

    /**
     * Close this folder.
     */
    public synchronized void close(boolean expungethrows MessagingException {
  close(expunge, false);
    }

    /**
     * Close this folder without waiting for the server.
     */
    public synchronized void forceClose() throws MessagingException {
  close(false, true);
    }

    /*
     * Common close method.
     */
    private void close(boolean expunge, boolean force)
        throws MessagingException {
  assert Thread.holdsLock(this);
  synchronized(messageCacheLock) {
      /*
       * If we already know we're closed, this is illegal.
       * Can't use checkOpened() because if we were forcibly
       * closed asynchronously we just want to complete the
       * closing here.
       */
      if (!opened && reallyClosed)
    throw new IllegalStateException(
        "This operation is not allowed on a closed folder"
    );

      reallyClosed = true// Ok, lets reset

      // Maybe this folder is already closed, or maybe another
      // thread which had the messageCacheLock earlier, found
      // that our server connection is dead and cleaned up
      // everything ..
      if (!opened)
    return;

      try {
    waitIfIdle();
    if (force) {
                    if (debug)
                        out.println("DEBUG: forcing folder " + fullName +
          " to close");
        if (protocol != null)
      protocol.disconnect();
                else if (((IMAPStore)store).isConnectionPoolFull()) {
        // If the connection pool is full, logout the connection
                    if (debug)
                        out.println("DEBUG: pool is full, not adding " +
                            "an Authenticated connection");

        // If the expunge flag is set, close the folder first.
        if (expunge)
      protocol.close();

        if (protocol != null)
      protocol.logout();
                else {
        // If the expunge flag is set or we're open read-only we
        // can just close the folder, otherwise open it read-only
        // before closing.
                    if (!expunge && mode == READ_WRITE) {
                        try {
                            MailboxInfo mi = protocol.examine(fullName);
                        catch (ProtocolException pex2) {
                            if (protocol != null)
        protocol.disconnect();
                        }
                    }
        if (protocol != null)
      protocol.close();
                }
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      finally {
    // cleanup if we haven't already
    if (opened)
        cleanup(true);
      }
  }
    }

    // NOTE: this method can currently be invoked from close() or
    // from handleResponses(). Both invocations are conditional,
    // based on the "opened" flag, so we are sure that multiple
    // Connection.CLOSED events are not generated. Also both
    // invocations are from within messageCacheLock-ed areas.
    private void cleanup(boolean returnToPool) {
  assert Thread.holdsLock(messageCacheLock);
        releaseProtocol(returnToPool);
  messageCache = null;
  uidTable = null;
  exists = false// to force a recheck in exists().
  attributes = null;
        opened = false;
  idleState = RUNNING;  // just in case
  notifyConnectionListeners(ConnectionEvent.CLOSED);
    }

    /**
     * Check whether this connection is really open.
     */
    public synchronized boolean isOpen() {
  synchronized(messageCacheLock) {
      // Probe the connection to make sure its really open.
      if (opened) {
    try {
        keepConnectionAlive(false);
    catch (ProtocolException pex) { }
      }
  }

  return opened;
    }

    /**
     * Return the permanent flags supported by the server.
     */
    public synchronized Flags getPermanentFlags() {
  return (Flags)(permanentFlags.clone());
    }

    /**
     * Get the total message count.
     */
    public synchronized int getMessageCount() throws MessagingException {
  if (!opened) {
      checkExists();
      // If this folder is not yet open, we use STATUS to
      // get the total message count
      try {
    Status status = getStatus();
    return status.total;
      catch (BadCommandException bex) {
    // doesn't support STATUS, probably vanilla IMAP4 ..
    // lets try EXAMINE
                IMAPProtocol p = null;

          try {
              p = getStoreProtocol();  // XXX
        MailboxInfo minfo = p.examine(fullName);
        p.close();
        return minfo.total;
    catch (ProtocolException pex) {
        // Give up.
        throw new MessagingException(pex.getMessage(), pex);
    finally {
                    releaseStoreProtocol(p);
                }
      catch (ConnectionException cex) {
                throw new StoreClosedException(store, cex.getMessage());
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      }
  }

  // Folder is open, we know what the total message count is ..
  synchronized(messageCacheLock) {
      // tickle the folder and store connections.
      try {
    keepConnectionAlive(true);
    return total;
      catch (ConnectionException cex) {
    throw new FolderClosedException(this, cex.getMessage());
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      }
  }
    }

    /**
     * Get the new message count.
     */
    public synchronized int getNewMessageCount()
      throws MessagingException {
  if (!opened) {
      checkExists();
      // If this folder is not yet open, we use STATUS to
      // get the new message count
      try {
    Status status = getStatus();
    return status.recent;
      catch (BadCommandException bex) {
    // doesn't support STATUS, probably vanilla IMAP4 ..
    // lets try EXAMINE
                IMAPProtocol p = null;

          try {
              p = getStoreProtocol();  // XXX
        MailboxInfo minfo = p.examine(fullName);
        p.close();
        return minfo.recent;
    catch (ProtocolException pex) {
        // Give up.
        throw new MessagingException(pex.getMessage(), pex);
    finally {
                    releaseStoreProtocol(p);
                }
      catch (ConnectionException cex) {
    throw new StoreClosedException(store, cex.getMessage());
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      }
  }

  // Folder is open, we know what the new message count is ..
  synchronized(messageCacheLock) {
      // tickle the folder and store connections.
      try {
    keepConnectionAlive(true);
    return recent;
      catch (ConnectionException cex) {
    throw new FolderClosedException(this, cex.getMessage());
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      }
  }
    }

    /**
     * Get the unread message count.
     */
    public synchronized int getUnreadMessageCount()
      throws MessagingException {
  if (!opened) {
      checkExists();
      // If this folder is not yet open, we use STATUS to
      // get the unseen message count
      try {
    Status status = getStatus();
    return status.unseen;
      catch (BadCommandException bex) {
    // doesn't support STATUS, probably vanilla IMAP4 ..
    // Could EXAMINE, SEARCH for UNREAD messages and
    // return the count .. bah, not worth it.
    return -1;
      catch (ConnectionException cex) {
    throw new StoreClosedException(store, cex.getMessage());
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      }
  }

  // if opened, issue server-side search for messages that do
  // *not* have the SEEN flag.
  Flags f = new Flags();
  f.add(Flags.Flag.SEEN);
  try {
      synchronized(messageCacheLock) {
    int[] matches = getProtocol().search(new FlagTerm(f, false));
    return matches.length; // NOTE: 'matches' is never null
      }
  catch (ConnectionException cex) {
      throw new FolderClosedException(this, cex.getMessage());
  catch (ProtocolException pex) {
      // Shouldn't happen
      throw new MessagingException(pex.getMessage(), pex);
  }
    }

    /**
     * Get the deleted message count.
     */
    public synchronized int getDeletedMessageCount()
      throws MessagingException {
  if (!opened) {
      checkExists();
      // no way to do this on closed folders
      return -1;
  }

  // if opened, issue server-side search for messages that do
  // have the DELETED flag.
  Flags f = new Flags();
  f.add(Flags.Flag.DELETED);
  try {
      synchronized(messageCacheLock) {
    int[] matches = getProtocol().search(new FlagTerm(f, true));
    return matches.length; // NOTE: 'matches' is never null
      }
  catch (ConnectionException cex) {
      throw new FolderClosedException(this, cex.getMessage());
  catch (ProtocolException pex) {
      // Shouldn't happen
      throw new MessagingException(pex.getMessage(), pex);
  }
    }

    /*
     * Get results of STATUS command for this folder, checking cache first.
     * ASSERT: Must be called with this folder's synchronization lock held.
     * ASSERT: The folder must be closed.
     */
    private Status getStatus() throws ProtocolException {
  int statusCacheTimeout = ((IMAPStore)store).getStatusCacheTimeout();

  // if allowed to cache and our cache is still valid, return it
  if (statusCacheTimeout > && cachedStatus != null &&
      System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout)
      return cachedStatus;

        IMAPProtocol p = null;

  try {
      p = getStoreProtocol();  // XXX
      Status s = p.status(fullName, null)
      // if allowed to cache, do so
      if (statusCacheTimeout > 0) {
    cachedStatus = s;
    cachedStatusTime = System.currentTimeMillis();
      }
      return s;
        finally {
            releaseStoreProtocol(p);
        }
    }

    /**
     * Get the specified message.
     */
    public synchronized Message getMessage(int msgnum
    throws MessagingException {
  checkOpened();
  checkRange(msgnum);

  return messageCache.getMessage(msgnum);
    }

    /**
     * Append the given messages into this folder.
     */
    public synchronized void appendMessages(Message[] msgs)
        throws MessagingException {
  checkExists()// verify that self exists

  // XXX - have to verify that messages are in a different
  // store (if any) than target folder, otherwise could
  // deadlock trying to fetch messages on the same connection
  // we're using for the append.

  int maxsize = ((IMAPStore)store).getAppendBufferSize();

  for (int i = 0; i < msgs.length; i++) {
      final Message m = msgs[i];
      Date d = m.getReceivedDate()// retain dates
      if (d == null)
    d = m.getSentDate();
      final Date dd = d;
      final Flags f = m.getFlags();

      final MessageLiteral mos;
      try {
    // if we know the message is too big, don't buffer any of it
    mos = new MessageLiteral(m,
        m.getSize() > maxsize ? : maxsize);
      catch (IOException ex) {
    throw new MessagingException(
        "IOException while appending messages", ex);
      catch (MessageRemovedException mrex) {
    continue// just skip this expunged message
      }

      doCommand(new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        p.append(fullName, f, dd, mos);
        return null;
    }
      });
  }
    }

    /**
     * Append the given messages into this folder.
     * Return array of AppendUID objects containing
     * UIDs of these messages in the destination folder.
     * Each element of the returned array corresponds to
     * an element of the <code>msgs</code> array.  A null
     * element means the server didn't return UID information
     * for the appended message.  <p>
     *
     * Depends on the APPENDUID response code defined by the
     * UIDPLUS extension -
     * <A HREF="http://www.ietf.org/rfc/rfc2359.txt">RFC 2359</A>.
     *
     @since  JavaMail 1.4
     */
    public synchronized AppendUID[] appendUIDMessages(Message[] msgs)
        throws MessagingException {
  checkExists()// verify that self exists

  // XXX - have to verify that messages are in a different
  // store (if any) than target folder, otherwise could
  // deadlock trying to fetch messages on the same connection
  // we're using for the append.

  int maxsize = ((IMAPStore)store).getAppendBufferSize();

  AppendUID[] uids = new AppendUID[msgs.length];
  for (int i = 0; i < msgs.length; i++) {
      final Message m = msgs[i];
      final MessageLiteral mos;

      try {
    // if we know the message is too big, don't buffer any of it
    mos = new MessageLiteral(m,
        m.getSize() > maxsize ? : maxsize);
      catch (IOException ex) {
    throw new MessagingException(
        "IOException while appending messages", ex);
      catch (MessageRemovedException mrex) {
    continue// just skip this expunged message
      }

      Date d = m.getReceivedDate()// retain dates
      if (d == null)
    d = m.getSentDate();
      final Date dd = d;
      final Flags f = m.getFlags();
      AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        return p.appenduid(fullName, f, dd, mos);
    }
      });
      uids[i= auid;
  }
  return uids;
    }

    /**
     * Append the given messages into this folder.
     * Return array of Message objects representing
     * the messages in the destination folder.  Note
     * that the folder must be open.
     * Each element of the returned array corresponds to
     * an element of the <code>msgs</code> array.  A null
     * element means the server didn't return UID information
     * for the appended message. <p>
     *
     * Depends on the APPENDUID response code defined by the
     * UIDPLUS extension -
     * <A HREF="http://www.ietf.org/rfc/rfc2359.txt">RFC 2359</A>.
     *
     @since  JavaMail 1.4
     */
    public synchronized Message[] addMessages(Message[] msgs)
        throws MessagingException {
  checkOpened();
  Message[] rmsgs = new MimeMessage[msgs.length];
  AppendUID[] uids = appendUIDMessages(msgs);
  for (int i = 0; i < uids.length; i++) {
      AppendUID auid = uids[i];
      if (auid != null) {
    if (auid.uidvalidity == uidvalidity) {
        try {
      rmsgs[i= getMessageByUID(auid.uid);
        catch (MessagingException mex) {
      // ignore errors at this stage
        }
    }
      }
  }
  return rmsgs;
    }

    /**
     * Copy the specified messages from this folder, to the
     * specified destination.
     */
    public synchronized void copyMessages(Message[] msgs, Folder folder)
      throws MessagingException {
  checkOpened();

  if (msgs.length == 0// boundary condition
      return;

  // If the destination belongs to our same store, optimize
  if (folder.getStore() == store) {
      synchronized(messageCacheLock) {
    try {
        IMAPProtocol p = getProtocol();
        MessageSet[] ms = Utility.toMessageSet(msgs, null);
        if (ms == null)
      throw new MessageRemovedException(
          "Messages have been removed");
        p.copy(ms, folder.getFullName());
    catch (CommandFailedException cfx) {
        if (cfx.getMessage().indexOf("TRYCREATE"!= -1)
      throw new FolderNotFoundException(
                            folder,
          folder.getFullName() " does not exist"
         );
        else 
      throw new MessagingException(cfx.getMessage(), cfx);
    catch (ConnectionException cex) {
        throw new FolderClosedException(this, cex.getMessage());
    catch (ProtocolException pex) {
        throw new MessagingException(pex.getMessage(), pex);
        }
      }
  else // destination is a different store.
      super.copyMessages(msgs, folder);
    }

    /**
     * Expunge all messages marked as DELETED.
     */
    public synchronized Message[] expunge() throws MessagingException {
  return expunge(null);
    }

    /**
     * Expunge the indicated messages, which must have been marked as DELETED.
     */
    public synchronized Message[] expunge(Message[] msgs)
        throws MessagingException {
  checkOpened();

  if (msgs != null) {
      // call fetch to make sure we have all the UIDs
      FetchProfile fp = new FetchProfile();
      fp.add(UIDFolder.FetchProfileItem.UID);
      fetch(msgs, fp);
  }

  IMAPMessage[] rmsgs;
  synchronized(messageCacheLock) {
      doExpungeNotification = false// We do this ourselves later
      try {
    IMAPProtocol p = getProtocol();
    if (msgs != null)
        p.uidexpunge(Utility.toUIDSet(msgs));
    else
        p.expunge();
      catch (CommandFailedException cfx) {
    // expunge not allowed, perhaps due to a permission problem?
    if (mode != READ_WRITE)
        throw new IllegalStateException(
      "Cannot expunge READ_ONLY folder: " + fullName);
    else
        throw new MessagingException(cfx.getMessage(), cfx);
      catch (ConnectionException cex) {
    throw new FolderClosedException(this, cex.getMessage());
      catch (ProtocolException pex) {
    // Bad bad server ..
    throw new MessagingException(pex.getMessage(), pex);
      finally {
    doExpungeNotification = true;
      }

      // Cleanup expunged messages and sync messageCache with reality.
      if (msgs != null)
    rmsgs = messageCache.removeExpungedMessages(msgs);
      else
    rmsgs = messageCache.removeExpungedMessages();
      if (uidTable != null) {
    for (int i = 0; i < rmsgs.length; i++) {
        IMAPMessage m = rmsgs[i];
        /* remove this message from the UIDTable */
        long uid = m.getUID();
        if (uid != -1)
      uidTable.remove(new Long(uid));
    }
      }

      // Update 'total'
      total = messageCache.size();
  }

  // Notify listeners. This time its for real, guys.
  if (rmsgs.length > 0)
      notifyMessageRemovedListeners(true, rmsgs);
  return rmsgs;
    }

    /**
     * Search whole folder for messages matching the given term.
     */
    public synchronized Message[] search(SearchTerm term)
        throws MessagingException {
  checkOpened();

  try {
      Message[] matchMsgs = null;

      synchronized(messageCacheLock) {
    int[] matches = getProtocol().search(term);
    if (matches != null) {
        matchMsgs = new IMAPMessage[matches.length];
        // Map seq-numbers into actual Messages.
        for (int i = 0; i < matches.length; i++)  
      matchMsgs[i= getMessageBySeqNumber(matches[i]);
    }
      }
      return matchMsgs;

  catch (CommandFailedException cfx) {
      // unsupported charset or search criterion
      return super.search(term);
  catch (SearchException sex) {
      // too complex for IMAP
      return super.search(term);
  catch (ConnectionException cex) {
      throw new FolderClosedException(this, cex.getMessage());
  catch (ProtocolException pex) {
      // bug in our IMAP layer ?
      throw new MessagingException(pex.getMessage(), pex);
  }
    }

    /**
     * Search the folder for messages matching the given term. Returns
     * array of matching messages. Returns an empty array if no matching
     * messages are found.
     */
    public synchronized Message[] search(SearchTerm term, Message[] msgs
      throws MessagingException {
  checkOpened();

  if (msgs.length == 0)
      // need to return an empty array (not null!)
      return msgs;

  try {
      Message[] matchMsgs = null;

      synchronized(messageCacheLock) {
    IMAPProtocol p = getProtocol();
    MessageSet[] ms = Utility.toMessageSet(msgs, null);
    if (ms == null)
        throw new MessageRemovedException(
          "Messages have been removed");
    int[] matches = p.search(ms, term);
    if (matches != null) {
        matchMsgs = new IMAPMessage[matches.length];
        for (int i = 0; i < matches.length; i++)  
      matchMsgs[i= getMessageBySeqNumber(matches[i]);
    }
      }
      return matchMsgs;

  catch (CommandFailedException cfx) {
      // unsupported charset or search criterion
      return super.search(term, msgs);
  catch (SearchException sex) {
      // too complex for IMAP
      return super.search(term, msgs);
  catch (ConnectionException cex) {
      throw new FolderClosedException(this, cex.getMessage());
  catch (ProtocolException pex) {
      // bug in our IMAP layer ?
      throw new MessagingException(pex.getMessage(), pex);
  }
    }

    /*
     * Override Folder method to keep track of whether we have any
     * message count listeners.  Normally we won't have any, so we
     * can avoid creating message objects to pass to the notify
     * method.  It's too hard to keep track of when all listeners
     * are removed, and that's a rare case, so we don't try.
     */
    public synchronized void addMessageCountListener(MessageCountListener l) { 
  super.addMessageCountListener(l);
  hasMessageCountListener = true;
    }

    /***********************************************************
     *    UIDFolder interface methods
     **********************************************************/

    /**
     * Returns the UIDValidity for this folder.
     */
    public synchronized long getUIDValidity() throws MessagingException {
  if (opened// we already have this information
      return uidvalidity;

        IMAPProtocol p = null;
        Status status = null;

  try {
      p = getStoreProtocol();  // XXX
      String[] item = "UIDVALIDITY" };
      status = p.status(fullName, item);
  catch (BadCommandException bex) {
      // Probably a RFC1730 server
      throw new MessagingException("Cannot obtain UIDValidity", bex);
  catch (ConnectionException cex) {
            // Oops, the store or folder died on us.
            throwClosedException(cex);
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  finally {
            releaseStoreProtocol(p);
        }

  return status.uidvalidity;
    }

    /**
     * Returns the predicted UID that will be assigned to the
     * next message that is appended to this folder.
     * If the folder is closed, the STATUS command is used to
     * retrieve this value.  If the folder is open, the value
     * returned from the SELECT or EXAMINE command is returned.
     * Note that messages may have been appended to the folder
     * while it was open and thus this value may be out of
     * date. <p>
     *
     * Servers implementing RFC2060 likely won't return this value
     * when a folder is opened.  Servers implementing RFC3501
     * should return this value when a folder is opened. <p>
     *
     @return  the UIDNEXT value, or -1 if unknown
     @since  JavaMail 1.3.3
     */
    // Not a UIDFolder method, but still useful
    public synchronized long getUIDNext() throws MessagingException {
  if (opened// we already have this information
      return uidnext;

        IMAPProtocol p = null;
        Status status = null;

  try {
      p = getStoreProtocol();  // XXX
      String[] item = "UIDNEXT" };
      status = p.status(fullName, item);
  catch (BadCommandException bex) {
      // Probably a RFC1730 server
      throw new MessagingException("Cannot obtain UIDNext", bex);
  catch (ConnectionException cex) {
            // Oops, the store or folder died on us.
            throwClosedException(cex);
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  finally {
            releaseStoreProtocol(p);
        }

  return status.uidnext;
    }

    /**
     * Get the Message corresponding to the given UID.
     * If no such message exists, <code> null </code> is returned.
     */
    public synchronized Message getMessageByUID(long uid
      throws MessagingException {
  checkOpened()// insure folder is open

  IMAPMessage m = null;

  try {
      synchronized(messageCacheLock) {
    Long l = new Long(uid);

    if (uidTable != null) {
        // Check in uidTable
        m = (IMAPMessage)uidTable.get(l);
        if (m != null// found it
      return m;
    else
        uidTable = new Hashtable();

    // Check with the server
    // Issue UID FETCH command
    UID u = getProtocol().fetchSequenceNumber(uid);

    if (u != null && u.seqnum <= total) { // Valid UID 
        m = getMessageBySeqNumber(u.seqnum);
        m.setUID(u.uid)// set this message's UID ..
        // .. and put this into the hashtable
        uidTable.put(l, m);
    }
      }
  catch(ConnectionException cex) {
      throw new FolderClosedException(this, cex.getMessage());
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  }

  return m;
    }

    /**
     * Get the Messages specified by the given range. <p>
     * Returns Message objects for all valid messages in this range.
     * Returns an empty array if no messages are found.
     */
    public synchronized Message[] getMessagesByUID(long start, long end
      throws MessagingException {
  checkOpened()// insure that folder is open

  Message[] msgs; // array of messages to be returned

  try {
      synchronized(messageCacheLock) {
    if (uidTable == null)
        uidTable = new Hashtable();

    // Issue UID FETCH for given range
    UID[] ua = getProtocol().fetchSequenceNumbers(start, end);

    msgs = new Message[ua.length];
    IMAPMessage m;
    // NOTE: Below must be within messageCacheLock region
    for (int i = 0; i < ua.length; i++) {
        m = getMessageBySeqNumber(ua[i].seqnum);
        m.setUID(ua[i].uid);
        msgs[i= m;
        uidTable.put(new Long(ua[i].uid), m);
    }
      }
  catch(ConnectionException cex) {
      throw new FolderClosedException(this, cex.getMessage());
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  }

  return msgs;
    }

    /**
     * Get the Messages specified by the given array. <p>
     *
     <code>uids.length()</code> elements are returned.
     * If any UID in the array is invalid, a <code>null</code> entry
     * is returned for that element.
     */
    public synchronized Message[] getMessagesByUID(long[] uids)
      throws MessagingException {
  checkOpened()// insure that folder is open

  try {
      synchronized(messageCacheLock) {
    long[] unavailUids = uids;
    if (uidTable != null) {
        Vector v = new Vector()// to collect unavailable UIDs
        Long l;
        for (int i = 0; i < uids.length; i++) {
      if (!uidTable.containsKey(l = new Long(uids[i])))
          // This UID has not been loaded yet.
          v.addElement(l);
        }

        int vsize = v.size();
        unavailUids = new long[vsize];
        for (int i = 0; i < vsize; i++)
      unavailUids[i((Long)v.elementAt(i)).longValue();
    else
        uidTable = new Hashtable();

    if (unavailUids.length > 0) {
        // Issue UID FETCH request for given uids
        UID[] ua = getProtocol().fetchSequenceNumbers(unavailUids);
        IMAPMessage m;
        for (int i = 0; i < ua.length; i++) {
      m = getMessageBySeqNumber(ua[i].seqnum);
      m.setUID(ua[i].uid);
      uidTable.put(new Long(ua[i].uid), m);
        }
    }

    // Return array of size = uids.length
    Message[] msgs = new Message[uids.length];
    for (int i = 0; i < uids.length; i++)
        msgs[i(Message)uidTable.get(new Long(uids[i]));
    return msgs;
      }
  catch(ConnectionException cex) {
      throw new FolderClosedException(this, cex.getMessage());
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  }
    }

    /**
     * Get the UID for the specified message.
     */
    public synchronized long getUID(Message message
      throws MessagingException {
  if (message.getFolder() != this)
      throw new NoSuchElementException(
    "Message does not belong to this folder");

  checkOpened()// insure that folder is open

  IMAPMessage m = (IMAPMessage)message;
  // If the message already knows its UID, great ..
  long uid;
  if ((uid = m.getUID()) != -1)
      return uid;

  synchronized(messageCacheLock) { // Acquire Lock
      try {
    IMAPProtocol p = getProtocol();
    m.checkExpunged()// insure that message is not expunged
    UID u = p.fetchUID(m.getSequenceNumber());

    if (u != null) {
        uid = u.uid;
        m.setUID(uid)// set message's UID

        // insert this message into uidTable
        if (uidTable == null)
      uidTable = new Hashtable();
        uidTable.put(new Long(uid), m);
    }
      catch (ConnectionException cex) {
    throw new FolderClosedException(this, cex.getMessage());
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      }
  }

  return uid;
    }

    /**
     * Get the quotas for the quotaroot associated with this
     * folder.  Note that many folders may have the same quotaroot.
     * Quotas are controlled on the basis of a quotaroot, not
     * (necessarily) a folder.  The relationship between folders
     * and quotaroots depends on the IMAP server.  Some servers
     * might implement a single quotaroot for all folders owned by
     * a user.  Other servers might implement a separate quotaroot
     * for each folder.  A single folder can even have multiple
     * quotaroots, perhaps controlling quotas for different
     * resources.
     *
     @return  array of Quota objects for the quotaroots associated with
     *    this folder
     @exception MessagingException  if the server doesn't support the
     *          QUOTA extension
     */
    public Quota[] getQuota() throws MessagingException {
  return (Quota[])doOptionalCommand("QUOTA not supported",
      new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        return p.getQuotaRoot(fullName);
    }
      });
    }

    /**
     * Set the quotas for the quotaroot specified in the quota argument.
     * Typically this will be one of the quotaroots associated with this
     * folder, as obtained from the <code>getQuota</code> method, but it
     * need not be.
     *
     @param  quota  the quota to set
     @exception MessagingException  if the server doesn't support the
     *          QUOTA extension
     */
    public void setQuota(final Quota quotathrows MessagingException {
  doOptionalCommand("QUOTA not supported",
      new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        p.setQuota(quota);
        return null;
    }
      });
    }

    /**
     * Get the access control list entries for this folder.
     *
     @return  array of access control list entries
     @exception MessagingException  if the server doesn't support the
     *          ACL extension
     */
    public ACL[] getACL() throws MessagingException {
  return (ACL[])doOptionalCommand("ACL not supported",
      new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        return p.getACL(fullName);
    }
      });
    }

    /**
     * Add an access control list entry to the access control list
     * for this folder.
     *
     @param  acl  the access control list entry to add
     @exception MessagingException  if the server doesn't support the
     *          ACL extension
     */
    public void addACL(ACL aclthrows MessagingException {
  setACL(acl, '\0');
    }

    /**
     * Remove any access control list entry for the given identifier
     * from the access control list for this folder.
     *
     @param  name  the identifier for which to remove all ACL entries
     @exception MessagingException  if the server doesn't support the
     *          ACL extension
     */
    public void removeACL(final String namethrows MessagingException {
  doOptionalCommand("ACL not supported",
      new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        p.deleteACL(fullName, name);
        return null;
    }
      });
    }

    /**
     * Add the rights specified in the ACL to the entry for the
     * identifier specified in the ACL.  If an entry for the identifier
     * doesn't already exist, add one.
     *
     @param  acl  the identifer and rights to add
     @exception MessagingException  if the server doesn't support the
     *          ACL extension
     */
    public void addRights(ACL aclthrows MessagingException {
  setACL(acl, '+');
    }

    /**
     * Remove the rights specified in the ACL from the entry for the
     * identifier specified in the ACL.
     *
     @param  acl  the identifer and rights to remove
     @exception MessagingException  if the server doesn't support the
     *          ACL extension
     */
    public void removeRights(ACL aclthrows MessagingException {
  setACL(acl, '-');
    }

    /**
     * Get all the rights that may be allowed to the given identifier.
     * Rights are grouped per RFC 2086 and each group is returned as an
     * element of the array.  The first element of the array is the set
     * of rights that are always granted to the identifier.  Later
     * elements are rights that may be optionally granted to the
     * identifier. <p>
     *
     * Note that this method lists the rights that it is possible to
     * assign to the given identifier, <em>not</em> the rights that are
     * actually granted to the given identifier.  For the latter, see
     * the <code>getACL</code> method.
     *
     @param  name  the identifier to list rights for
     @return    array of Rights objects representing possible
     *      rights for the identifier
     @exception MessagingException  if the server doesn't support the
     *          ACL extension
     */
    public Rights[] listRights(final String namethrows MessagingException {
  return (Rights[])doOptionalCommand("ACL not supported",
      new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        return p.listRights(fullName, name);
    }
      });
    }

    /**
     * Get the rights allowed to the currently authenticated user.
     *
     @return  the rights granted to the current user
     @exception MessagingException  if the server doesn't support the
     *          ACL extension
     */
    public Rights myRights() throws MessagingException {
  return (Rights)doOptionalCommand("ACL not supported",
      new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        return p.myRights(fullName);
    }
      });
    }

    private void setACL(final ACL acl, final char mod)
        throws MessagingException {
  doOptionalCommand("ACL not supported",
      new ProtocolCommand() {
    public Object doCommand(IMAPProtocol p)
      throws ProtocolException {
        p.setACL(fullName, mod, acl);
        return null;
    }
      });
    }

    /**
     * Get the attributes that the IMAP server returns with the
     * LIST response.
     *
     @since  JavaMail 1.3.3
     */
    public synchronized String[] getAttributes() throws MessagingException {
  checkExists();
  if (attributes == null)
      exists();    // do a LIST to set the attributes
  return attributes == null new String[0:
    (String[])(attributes.clone());
    }

    /**
     * Use the IMAP IDLE command (see
     * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>),
     * if supported by the server, to enter idle mode so that the server
     * can send unsolicited notifications of new messages arriving, etc.
     * without the need for the client to constantly poll the server.
     * Use an appropriate listener to be notified of new messages or
     * other events.  When another thread (e.g., the listener thread)
     * needs to issue an IMAP comand for this folder, the idle mode will
     * be terminated and this method will return.  Typically the caller
     * will invoke this method in a loop. <p>
     *
     * The mail.imap.minidletime property enforces a minimum delay
     * before returning from this method, to ensure that other threads
     * have a chance to issue commands before the caller invokes this
     * method again.  The default delay is 10 milliseconds.
     *
     @exception MessagingException  if the server doesn't support the
     *          IDLE extension
     @exception IllegalStateException  if the folder isn't open
     *
     @since  JavaMail 1.4.1
     */
    public void idle() throws MessagingException {
  idle(false);
    }

    /**
     * Like {@link #idle}, but if <code>once</code> is true, abort the
     * IDLE command after the first notification, to allow the caller
     * to process any notification synchronously.
     *
     @exception MessagingException  if the server doesn't support the
     *          IDLE extension
     @exception IllegalStateException  if the folder isn't open
     *
     @since  JavaMail 1.4.3
     */
    public void idle(boolean oncethrows MessagingException {
  // ASSERT: Must NOT be called with this folder's
  // synchronization lock held.
  assert !Thread.holdsLock(this);
  synchronized(this) {
      checkOpened();
      Boolean started = (Boolean)doOptionalCommand("IDLE not supported",
    new ProtocolCommand() {
        public Object doCommand(IMAPProtocol p)
          throws ProtocolException {
      if (idleState == RUNNING) {
          p.idleStart();
          idleState = IDLE;
          return Boolean.TRUE;
      else {
          // some other thread must be running the IDLE
          // command, we'll just wait for it to finish
          // without aborting it ourselves
          try {
        // give up lock and wait to be not idle
        messageCacheLock.wait();
          catch (InterruptedException ex) { }
          return Boolean.FALSE;
      }
        }
    });
      if (!started.booleanValue())
    return;
  }

  /*
   * We gave up the folder lock so that other threads
   * can get into the folder far enough to see that we're
   * in IDLE and abort the IDLE.
   *
   * Now we read responses from the IDLE command, especially
   * including unsolicited notifications from the server.
   * We don't hold the messageCacheLock while reading because
   * it protects the idleState and other threads need to be
   * able to examine the state.
   *
   * We hold the messageCacheLock while processing the
   * responses so that we can update the number of messages
   * in the folder (for example).
   */
  for (;;) {
      Response r = protocol.readIdleResponse();
      try {
    synchronized (messageCacheLock) {
        try {
      if (r == null || protocol == null ||
        !protocol.processIdleResponse(r)) {
          idleState = RUNNING;
          messageCacheLock.notifyAll();
          break;
      }
        catch (ProtocolException pex) {
      idleState = RUNNING;
      messageCacheLock.notifyAll();
      throw pex;
        }
        if (once) {
      if (idleState == IDLE) {
          protocol.idleAbort();
          idleState = ABORTING;
      }
        }
    }
      catch (ConnectionException cex) {
    // Oops, the store or folder died on us.
    throwClosedException(cex);
      catch (ProtocolException pex) {
    throw new MessagingException(pex.getMessage(), pex);
      }
  }

  /*
   * Enforce a minimum delay to give time to threads
   * processing the responses that came in while we
   * were idle.
   */
  int minidle = ((IMAPStore)store).getMinIdleTime();
  if (minidle > 0) {
      try {
    Thread.sleep(minidle);
      catch (InterruptedException ex) { }
  }
    }

    /*
     * If an IDLE command is in progress, abort it if necessary,
     * and wait until it completes.
     * ASSERT: Must be called with the message cache lock held.
     */
    void waitIfIdle() throws ProtocolException {
  assert Thread.holdsLock(messageCacheLock);
  while (idleState != RUNNING) {
      if (idleState == IDLE) {
    protocol.idleAbort();
    idleState = ABORTING;
      }
      try {
    // give up lock and wait to be not idle
    messageCacheLock.wait();
      catch (InterruptedException ex) { }
  }
    }

    /**
     * The response handler. This is the callback routine that is 
     * invoked by the protocol layer.
     */
    /*
     * ASSERT: This method must be called only when holding the
     * messageCacheLock.
     * ASSERT: This method must *not* invoke any other method that
     * might grab the 'folder' lock or 'message' lock (i.e., any 
     * synchronized methods on IMAPFolder or IMAPMessage)
     * since that will result in violating the locking hierarchy.
     */
    public void handleResponse(Response r) {
  assert Thread.holdsLock(messageCacheLock);

  /*
   * First, delegate possible ALERT or notification to the Store.
   */
  if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
      ((IMAPStore)store).handleResponseCode(r);

  /*
   * Now check whether this is a BYE or OK response and
   * handle appropriately.
   */
  if (r.isBYE()) {
      if (opened)    // XXX - accessed without holding folder lock
    cleanup(false)
      return;
  else if (r.isOK()) {
      return;
  else if (!r.isUnTagged()) {
      return;  // XXX - should never happen
  }

  /* Now check whether this is an IMAP specific response */
  if (!(instanceof IMAPResponse)) {
      // Probably a bug in our code !
      // XXX - should be an assert
      out.println("UNEXPECTED RESPONSE : " + r.toString());
      out.println("CONTACT [email protected]");
      return;
  }

  IMAPResponse ir = (IMAPResponse)r;

  if (ir.keyEquals("EXISTS")) { // EXISTS
      int exists = ir.getNumber();
      if (exists <= realTotal
    // Could be the EXISTS following EXPUNGE, ignore 'em
    return;
  
      int count = exists - realTotal; // number of new messages
      Message[] msgs = new Message[count];

      // Add 'count' new IMAPMessage objects into the messageCache
      messageCache.addMessages(count);
      int oldtotal = total;  // used in loop below
      realTotal += count;
      total += count;

      // avoid instantiating Message objects if no listeners.
      if (hasMessageCountListener) {
    for (int i = 0; i < count; i++)
        msgs[i= messageCache.getMessage(++oldtotal);

    // Notify listeners.
    notifyMessageAddedListeners(msgs);
      }

  else if (ir.keyEquals("EXPUNGE")) {
      // EXPUNGE response.

      int seqnum = ir.getNumber();
      Message[] msgs = null;
      if (doExpungeNotification && hasMessageCountListener) {
    // save the Message object first; can't look it
    // up after it's expunged
    msgs = new Message[] { getMessageBySeqNumber(seqnum) };
      }

      messageCache.expungeMessage(seqnum);

      // decrement 'realTotal'; but leave 'total' unchanged
      realTotal--;

      if (msgs != null)  // Do the notification here.
    notifyMessageRemovedListeners(false, msgs);

  else if (ir.keyEquals("FETCH")) {
      // The only unsolicited FETCH response that makes sense
      // to me (for now) is FLAGS updates. Ignore any other junk.
      assert ir instanceof FetchResponse : "!ir instanceof FetchResponse";
      FetchResponse f = (FetchResponse)ir;
      // Get FLAGS response, if present
      Flags flags = (Flags)f.getItem(Flags.class);

      if (flags != null) {
    IMAPMessage msg = getMessageBySeqNumber(f.getNumber());
    if (msg != null) {  // should always be true
        msg._setFlags(flags);
        notifyMessageChangedListeners(
          MessageChangedEvent.FLAGS_CHANGED, msg);
    }
      }

  else if (ir.keyEquals("RECENT")) {
      // update 'recent'
      recent = ir.getNumber();
  }
    }

    /**
     * Handle the given array of Responses.
     *
     * ASSERT: This method must be called only when holding the
     *   messageCacheLock
     */
    void handleResponses(Response[] r) {
  for (int i = 0; i < r.length; i++) {
      if (r[i!= null)
    handleResponse(r[i]);
  }
    }

    /**
     * Get this folder's Store's protocol connection.
     *
     * When acquiring a store protocol object, it is important to
     * use the following steps:
     *
     *     IMAPProtocol p = null;
     *     try {
     *         p = getStoreProtocol();
     *         // perform the command
     *     } catch (WhateverException ex) {
     *         // handle it
     *     } finally {
     *         releaseStoreProtocol(p);
     *     }
     */
    protected synchronized IMAPProtocol getStoreProtocol() 
            throws ProtocolException {
  if (connectionPoolDebug) {
      out.println("DEBUG: getStoreProtocol() - " 
    "borrowing a connection");
  }
  return ((IMAPStore)store).getFolderStoreProtocol();
    }

    /**
     * Throw the appropriate 'closed' exception.
     */
    private synchronized void throwClosedException(ConnectionException cex
            throws FolderClosedException, StoreClosedException {
  // If it's the folder's protocol object, throw a FolderClosedException;
  // otherwise, throw a StoreClosedException.
  // If a command has failed because the connection is closed,
  // the folder will have already been forced closed by the
  // time we get here and our protocol object will have been
  // released, so if we no longer have a protocol object we base
  // this decision on whether we *think* the folder is open.
  if ((protocol != null && cex.getProtocol() == protocol||
    (protocol == null && !reallyClosed))
            throw new FolderClosedException(this, cex.getMessage());
        else
            throw new StoreClosedException(store, cex.getMessage());
    }

    /**
     * Return the IMAPProtocol object for this folder. <p>
     *
     * This method will block if necessary to wait for an IDLE
     * command to finish.
     *
     @return  the IMAPProtocol object used when the folder is open
     */
    private IMAPProtocol getProtocol() throws ProtocolException {
  assert Thread.holdsLock(messageCacheLock);
  waitIfIdle();
        return protocol;
    }

    /**
     * A simple interface for user-defined IMAP protocol commands.
     */
    public static interface ProtocolCommand {
  /**
   * Execute the user-defined command using the supplied IMAPProtocol
   * object.
   */
  public Object doCommand(IMAPProtocol protocolthrows ProtocolException;
    }

    /**
     * Execute a user-supplied IMAP command.  The command is executed
     * in the appropriate context with the necessary locks held and
     * using the appropriate <code>IMAPProtocol</code> object. <p>
     *
     * This method returns whatever the <code>ProtocolCommand</code>
     * object's <code>doCommand</code> method returns.  If the
     <code>doCommand</code> method throws a <code>ConnectionException</code>
     * it is translated into a <code>StoreClosedException</code> or
     <code>FolderClosedException</code> as appropriate.  If the
     <code>doCommand</code> method throws a <code>ProtocolException</code>
     * it is translated into a <code>MessagingException</code><p>
     *
     * The following example shows how to execute the IMAP NOOP command.
     * Executing more complex IMAP commands requires intimate knowledge
     * of the <code>com.sun.mail.iap</code> and
     <code>com.sun.mail.imap.protocol</code> packages, best acquired by
     * reading the source code. <p>
     *
     <blockquote><pre>
     * import com.sun.mail.iap.*;
     * import com.sun.mail.imap.*;
     * import com.sun.mail.imap.protocol.*;
     *
     * ...
     *
     * IMAPFolder f = (IMAPFolder)folder;
     * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
     *  public Object doCommand(IMAPProtocol p)
     *      throws ProtocolException {
     *      p.simpleCommand("NOOP", null);
     *      return null;
     *  }
     * });
     </pre></blockquote>
     <p>
     *
     * Here's a more complex example showing how to use the proposed
     * IMAP SORT extension: <p>
     *
     <pre><blockquote>
     * import com.sun.mail.iap.*;
     * import com.sun.mail.imap.*;
     * import com.sun.mail.imap.protocol.*;
     *
     * ...
     *
     * IMAPFolder f = (IMAPFolder)folder;
     * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
     *  public Object doCommand(IMAPProtocol p)
     *      throws ProtocolException {
     *      // Issue command
     *      Argument args = new Argument();
     *      Argument list = new Argument();
     *      list.writeString("SUBJECT");
     *      args.writeArgument(list);
     *      args.writeString("UTF-8");
     *      args.writeString("ALL");
     *      Response[] r = p.command("SORT", args);
     *      Response response = r[r.length-1];
     *
     *      // Grab response
     *      Vector v = new Vector();
     *      if (response.isOK()) { // command succesful 
     *    for (int i = 0, len = r.length; i < len; i++) {
     *        if (!(r[i] instanceof IMAPResponse))
     *      continue;
     *
     *        IMAPResponse ir = (IMAPResponse)r[i];
     *        if (ir.keyEquals("SORT")) {
     *      String num;
     *      while ((num = ir.readAtomString()) != null)
     *          System.out.println(num);
     *      r[i] = null;
     *        }
     *    }
     *      }
     *
     *      // dispatch remaining untagged responses
     *      p.notifyResponseHandlers(r);
     *      p.handleResult(response);
     *
     *      return null;
     *  }
     * });
     </pre></blockquote>
     */
    public Object doCommand(ProtocolCommand cmdthrows MessagingException {
  try {
      return doProtocolCommand(cmd);
  catch (ConnectionException cex) {
            // Oops, the store or folder died on us.
            throwClosedException(cex);
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  }
  return null;
    }

    public Object doOptionalCommand(String err, ProtocolCommand cmd)
        throws MessagingException {
  try {
      return doProtocolCommand(cmd);
  catch (BadCommandException bex) {
      throw new MessagingException(err, bex);
  catch (ConnectionException cex) {
            // Oops, the store or folder died on us.
            throwClosedException(cex);
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  }
  return null;
    }

    public Object doCommandIgnoreFailure(ProtocolCommand cmd)
        throws MessagingException {
  try {
      return doProtocolCommand(cmd);
  catch (CommandFailedException cfx) {
      return null;
  catch (ConnectionException cex) {
            // Oops, the store or folder died on us.
            throwClosedException(cex);
  catch (ProtocolException pex) {
      throw new MessagingException(pex.getMessage(), pex);
  }
  return null;
    }

    protected Object doProtocolCommand(ProtocolCommand cmd)
        throws ProtocolException {
  synchronized (this) {
      /*
       * Check whether we have a protocol object, not whether we're
       * opened, to allow use of the exsting protocol object in the
       * open method before the state is changed to "opened".
       */
      if (protocol != null) {
    synchronized (messageCacheLock) {
        return cmd.doCommand(getProtocol());
    }
      }
  }

  // only get here if using store's connection
  IMAPProtocol p = null;

  try {
            p = getStoreProtocol();
      return cmd.doCommand(p);
  finally {
      releaseStoreProtocol(p);
  }
    }

    /**
     * Release the store protocol object.  If we borrowed a protocol
     * object from the connection pool, give it back.  If we used our
     * own protocol object, nothing to do.
     */
    protected synchronized void releaseStoreProtocol(IMAPProtocol p) {
        if (p != protocol)
            ((IMAPStore)store).releaseFolderStoreProtocol(p);
  else {
      // XXX - should never happen
      if (debug)
    out.println("DEBUG: releasing our protocol as store protocol?");
  }
    }

    /**
     * Release the protocol object.
     *
     * ASSERT: This method must be called only when holding the
     *  messageCacheLock
     */
    private void releaseProtocol(boolean returnToPool) {
        if (protocol != null) {
            protocol.removeResponseHandler(this);

            if (returnToPool)
                ((IMAPStore)store).releaseProtocol(this, protocol);
            else {
    protocol.disconnect();  // make sure it's disconnected
                ((IMAPStore)store).releaseProtocol(this, null);
      }
      protocol = null;
        }
    }
    
    /**
     * Issue a noop command for the connection if the connection has not been
     * used in more than a second. If <code>keepStoreAlive</code> is true,
     * also issue a noop over the store's connection.
     *
     * ASSERT: This method must be called only when holding the
     *  messageCacheLock
     */
    private void keepConnectionAlive(boolean keepStoreAlive
                    throws ProtocolException {

        if (System.currentTimeMillis() - protocol.getTimestamp() 1000) {
      waitIfIdle();
      if (protocol != null)
    protocol.noop()
  }

        if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection()) {
            IMAPProtocol p = null;
      try {
    p = ((IMAPStore)store).getFolderStoreProtocol();
    if (System.currentTimeMillis() - p.getTimestamp() 1000)
        p.noop();
      finally {
    ((IMAPStore)store).releaseFolderStoreProtocol(p);
      }
        }
    }

    /**
     * Get the message object for the given sequence number. If
     * none found, null is returned.
     *
     * ASSERT: This method must be called only when holding the
     *  messageCacheLock
     */
    IMAPMessage getMessageBySeqNumber(int seqnum) {
  return messageCache.getMessageBySeqnum(seqnum);
    }

    private boolean isDirectory() {
  return ((type & HOLDS_FOLDERS!= 0);
    }
}

/**
 * An object that holds a Message object
 * and reports its size and writes it to another OutputStream
 * on demand.  Used by appendMessages to avoid the need to
 * buffer the entire message in memory in a single byte array
 * before sending it to the server.
 */
class MessageLiteral implements Literal {
    private Message msg;
    private int msgSize = -1;
    private byte[] buf;    // the buffered message, if not null

    public MessageLiteral(Message msg, int maxsize)
        throws MessagingException, IOException {
  this.msg = msg;
  // compute the size here so exceptions can be returned immediately
  LengthCounter lc = new LengthCounter(maxsize);
  OutputStream os = new CRLFOutputStream(lc);
  msg.writeTo(os);
  os.flush();
  msgSize = lc.getSize();
  buf = lc.getBytes();
    }

    public int size() {
  return msgSize;
    }

    public void writeTo(OutputStream osthrows IOException {
  // the message should not change between the constructor and this call
  try {
      if (buf != null)
    os.write(buf, 0, msgSize);
      else {
    os = new CRLFOutputStream(os);
    msg.writeTo(os);
      }
  catch (MessagingException mex) {
      // exceptions here are bad, "should" never happen
      throw new IOException("MessagingException while appending message: "
            + mex);
  }
    }
}

/**
 * Count the number of bytes written to the stream.
 * Also, save a copy of small messages to avoid having to process
 * the data again.
 */
class LengthCounter extends OutputStream {
    private int size = 0;
    private byte[] buf;
    private int maxsize;

    public LengthCounter(int maxsize) {
  buf = new byte[8192];
  this.maxsize = maxsize;
    }

    public void write(int b) {
  int newsize = size + 1;
  if (buf != null) {
      if (newsize > maxsize && maxsize >= 0) {
    buf = null;
      else if (newsize > buf.length) {
    byte newbuf[] new byte[Math.max(buf.length << 1, newsize)];
    System.arraycopy(buf, 0, newbuf, 0, size);
    buf = newbuf;
    buf[size(byte)b;
      else {
    buf[size(byte)b;
      }
  }
  size = newsize;
    }

    public void write(byte b[]int off, int len) {
  if ((off < 0|| (off > b.length|| (len < 0||
            ((off + len> b.length|| ((off + len0)) {
      throw new IndexOutOfBoundsException();
  else if (len == 0) {
      return;
  }
        int newsize = size + len;
  if (buf != null) {
      if (newsize > maxsize && maxsize >= 0) {
    buf = null;
      else if (newsize > buf.length) {
    byte newbuf[] new byte[Math.max(buf.length << 1, newsize)];
    System.arraycopy(buf, 0, newbuf, 0, size);
    buf = newbuf;
    System.arraycopy(b, off, buf, size, len);
      else {
    System.arraycopy(b, off, buf, size, len);
      }
  }
        size = newsize;
    }

    public void write(byte[] bthrows IOException {
  write(b, 0, b.length);
    }

    public int getSize() {
  return size;
    }

    public byte[] getBytes() {
  return buf;
    }
}