Home » glassfish-v2ur2-b04-src » com.sun.mail.imap » [javadoc | source]

    1   /*
    2    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    3    *
    4    * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
    5    *
    6    * The contents of this file are subject to the terms of either the GNU
    7    * General Public License Version 2 only ("GPL") or the Common Development
    8    * and Distribution License("CDDL") (collectively, the "License").  You
    9    * may not use this file except in compliance with the License. You can obtain
   10    * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
   11    * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
   12    * language governing permissions and limitations under the License.
   13    *
   14    * When distributing the software, include this License Header Notice in each
   15    * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
   16    * Sun designates this particular file as subject to the "Classpath" exception
   17    * as provided by Sun in the GPL Version 2 section of the License file that
   18    * accompanied this code.  If applicable, add the following below the License
   19    * Header, with the fields enclosed by brackets [] replaced by your own
   20    * identifying information: "Portions Copyrighted [year]
   21    * [name of copyright owner]"
   22    *
   23    * Contributor(s):
   24    *
   25    * If you wish your version of this file to be governed by only the CDDL or
   26    * only the GPL Version 2, indicate your decision by adding "[Contributor]
   27    * elects to include this software in this distribution under the [CDDL or GPL
   28    * Version 2] license."  If you don't indicate a single choice of license, a
   29    * recipient has the option to distribute your version of this file under
   30    * either the CDDL, the GPL Version 2 or to extend the choice of license to
   31    * its licensees as provided above.  However, if you add GPL Version 2 code
   32    * and therefore, elected the GPL Version 2 license, then the option applies
   33    * only if the new code is made subject to such option by the copyright
   34    * holder.
   35    */
   36   
   37   /*
   38    * @(#)IMAPFolder.java	1.85 07/09/05
   39    */
   40   
   41   package com.sun.mail.imap;
   42   
   43   import java.util.Date;
   44   import java.util.Vector;
   45   import java.util.Hashtable;
   46   import java.util.NoSuchElementException;
   47   import java.io;
   48   
   49   import javax.mail;
   50   import javax.mail.event;
   51   import javax.mail.internet;
   52   import javax.mail.search;
   53   
   54   import com.sun.mail.util;
   55   import com.sun.mail.iap;
   56   import com.sun.mail.imap.protocol;
   57   
   58   /**
   59    * This class implements an IMAP folder. <p>
   60    *
   61    * A closed IMAPFolder object shares a protocol connection with its IMAPStore
   62    * object. When the folder is opened, it gets its own protocol connection. <p>
   63    *
   64    * Applications that need to make use of IMAP-specific features may cast
   65    * a <code>Folder</code> object to an <code>IMAPFolder</code> object and
   66    * use the methods on this class. The {@link #getQuota getQuota} and
   67    * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
   68    * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
   69    * for more information. <p>
   70    *
   71    * The {@link #getACL getACL}, {@link #addACL addACL},
   72    * {@link #removeACL removeACL}, {@link #addRights addRights},
   73    * {@link #removeRights removeRights}, {@link #listRights listRights}, and
   74    * {@link #myRights myRights} methods support the IMAP ACL extension.
   75    * Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A>
   76    * for more information. <p>
   77    *
   78    * The {@link #doCommand doCommand} method and
   79    * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand}
   80    * interface support use of arbitrary IMAP protocol commands. <p>
   81    *
   82    * See the <a href="package-summary.html">com.sun.mail.imap</a> package
   83    * documentation for further information on the IMAP protocol provider. <p>
   84    *
   85    * <strong>WARNING:</strong> The APIs unique to this class should be
   86    * considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
   87    * future in ways that are incompatible with applications using the
   88    * current APIs.
   89    *
   90    * @version 1.85, 07/09/05
   91    * @author  John Mani
   92    * @author  Bill Shannon
   93    * @author  Jim Glennon
   94    */
   95   
   96   /*
   97    * The folder object itself serves as a lock for the folder's state
   98    * EXCEPT for the message cache (see below), typically by using
   99    * synchronized methods.  When checking that a folder is open or
  100    * closed, the folder's lock must be held.  It's important that the
  101    * folder's lock is acquired before the messageCacheLock (see below).
  102    * Thus, the locking hierarchy is that the folder lock, while optional,
  103    * must be acquired before the messageCacheLock, if it's acquired at
  104    * all.  Be especially careful of callbacks that occur while holding
  105    * the messageCacheLock into (e.g.) superclass Folder methods that are
  106    * synchronized.  Note that methods in IMAPMessage will acquire the
  107    * messageCacheLock without acquiring the folder lock. <p>
  108    *
  109    * When a folder is opened, it creates a messageCache (a Vector) of 
  110    * empty IMAPMessage objects. Each Message has a messageNumber - which
  111    * is its index into the messageCache, and a sequenceNumber - which is
  112    * its IMAP sequence-number. All operations on a Message which involve
  113    * communication with the server, use the message's sequenceNumber. <p>
  114    *
  115    * The most important thing to note here is that the server can send
  116    * unsolicited EXPUNGE notifications as part of the responses for "most"
  117    * commands. Refer RFC2060, sections 5.3 &  5.5 for gory details. Also, 
  118    * the server sends these  notifications AFTER the message has been 
  119    * expunged. And once a message is expunged, the sequence-numbers of 
  120    * those messages after the expunged one are renumbered. This essentially
  121    * means that the mapping between *any* Message and its sequence-number 
  122    * can change in the period when a IMAP command is issued and its responses
  123    * are processed. Hence we impose a strict locking model as follows: <p>
  124    *
  125    * We define one mutex per folder - this is just a Java Object (named 
  126    * messageCacheLock). Any time a command is to be issued to the IMAP
  127    * server (i.e., anytime the corresponding IMAPProtocol method is
  128    * invoked), follow the below style:
  129    *		
  130    *	synchronized (messageCacheLock) { // ACQUIRE LOCK
  131    *	    issue command ()
  132    *	    
  133    *	    // The response processing is typically done within
  134    *	    // the handleResponse() callback. A few commands (Fetch,
  135    *	    // Expunge) return *all* responses and hence their
  136    *	    // processing is done here itself. Now, as part of the
  137    *	    // processing unsolicited EXPUNGE responses, we renumber
  138    *	    // the necessary sequence-numbers. Thus the renumbering
  139    *	    // happens within this critical-region, surrounded by
  140    *	    // locks.
  141    *	    process responses ()
  142    *	} // RELEASE LOCK
  143    *
  144    * This technique is used both by methods in IMAPFolder and by methods
  145    * in IMAPMessage and other classes that operate on data in the folder.
  146    * Note that holding the messageCacheLock has the side effect of
  147    * preventing the folder from being closed, and thus ensuring that the
  148    * folder's protocol object is still valid.  The protocol object should
  149    * only be accessed while holding the messageCacheLock (except for calls
  150    * to IMAPProtocol.isREV1(), which don't need to be protected because it
  151    * doesn't access the server).
  152    *	    
  153    * Note that interactions with the Store's protocol connection do
  154    * not have to be protected as above, since the Store's protocol is
  155    * never in a "meaningful" SELECT-ed state.
  156    */
  157   
  158   public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
  159       
  160       protected String fullName;		// full name
  161       protected String name;		// name
  162       protected int type;			// folder type. 
  163       protected char separator;		// separator
  164       protected Flags availableFlags; 	// available flags
  165       protected Flags permanentFlags; 	// permanent flags
  166       protected boolean exists = false; 	// whether this folder really exists ?
  167       protected boolean isNamespace = false; // folder is a namespace name
  168       protected String[] attributes;	// name attributes from LIST response
  169   
  170       protected IMAPProtocol protocol; 	// this folder's own protocol object
  171       protected Vector messageCache;  	// message cache
  172       protected Object messageCacheLock; 	// accessor lock for message cache
  173   
  174       protected Hashtable uidTable;	// UID->Message hashtable
  175   
  176       /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL).
  177        * We use '\uffff' (a non 7bit character) to indicate that we havent
  178        * yet determined what the separator character is.
  179        * We use '\u0000' (NUL) to indicate that no separator character
  180        * exists, i.e., a flat hierarchy
  181        */
  182       static final protected char UNKNOWN_SEPARATOR = '\uffff';
  183   
  184       private boolean opened = false; 	// is this folder opened ?
  185   
  186       /* This field tracks the state of this folder. If the folder is closed
  187        * due to external causes (i.e, not thru the close() method), then
  188        * this field will remain false. If the folder is closed thru the
  189        * close() method, then this field is set to true.
  190        *
  191        * If reallyClosed is false, then a FolderClosedException is
  192        * generated when a method is invoked on any Messaging object
  193        * owned by this folder. If reallyClosed is true, then the
  194        * IllegalStateException runtime exception is thrown.
  195        */
  196       private boolean reallyClosed = true;
  197   
  198       /*
  199        * The idleState field supports the IDLE command.
  200        * Normally when executing an IMAP command we hold the
  201        * messageCacheLock and often the folder lock (see above).
  202        * While executing the IDLE command we can't hold either
  203        * of these locks or it would prevent other threads from
  204        * entering Folder methods even far enough to check whether
  205        * an IDLE command is in progress.  We need to check before
  206        * issuing another command so that we can abort the IDLE
  207        * command.
  208        *
  209        * The idleState field is protected by the messageCacheLock.
  210        * The RUNNING state is the normal state and means no IDLE
  211        * command is in progress.  The IDLE state means we've issued
  212        * an IDLE command and are reading responses.  The ABORTING
  213        * state means we've sent the DONE continuation command and
  214        * are waiting for the thread running the IDLE command to
  215        * break out of its read loop.
  216        *
  217        * When an IDLE command is in progress, the thread calling
  218        * the idle method will be reading from the IMAP connection
  219        * while holding neither the folder lock nor the messageCacheLock.
  220        * It's obviously critical that no other thread try to send a
  221        * command or read from the connection while in this state.
  222        * However, other threads can send the DONE continuation
  223        * command that will cause the server to break out of the IDLE
  224        * loop and send the ending tag response to the IDLE command.
  225        * The thread in the idle method that's reading the responses
  226        * from the IDLE command will see this ending response and
  227        * complete the idle method, setting the idleState field back
  228        * to RUNNING, and notifying any threads waiting to use the
  229        * connection.
  230        *
  231        * All uses of the IMAP connection (IMAPProtocol object) must
  232        * be done while holding the messageCacheLock and must be
  233        * preceeded by a check to make sure an IDLE command is not
  234        * running, and abort the IDLE command if necessary.  While
  235        * waiting for the IDLE command to complete, these other threads
  236        * will give up the messageCacheLock, but might still be holding
  237        * the folder lock.  This check is done by the getProtocol()
  238        * method, resulting in a typical usage pattern of:
  239        *
  240        *	    synchronized (messageCacheLock) {
  241        *		IMAPProtocol p = getProtocol();	// may block waiting for IDLE
  242        *		// ... use protocol
  243        *	    }
  244        */
  245       private static final int RUNNING = 0;	// not doing IDLE command
  246       private static final int IDLE = 1;		// IDLE command in effect
  247       private static final int ABORTING = 2;	// IDLE command aborting
  248       private int idleState = RUNNING;
  249   
  250       private int total = -1;		// total number of messages in the
  251   					// message cache
  252       private int recent = -1;		// number of recent messages
  253       private int realTotal = -1;		// total number of messages on
  254       					// the server
  255       private long uidvalidity = -1;	// UIDValidity
  256       private long uidnext = -1;		// UIDNext
  257       private boolean doExpungeNotification = true; // used in expunge handler
  258   
  259       private Status cachedStatus = null;
  260       private long cachedStatusTime = 0;
  261   
  262       private boolean debug = false;
  263       private PrintStream out;		// debug output stream
  264   
  265       private boolean connectionPoolDebug;
  266   
  267       /**
  268        * A fetch profile item for fetching headers.
  269        * This inner class extends the <code>FetchProfile.Item</code>
  270        * class to add new FetchProfile item types, specific to IMAPFolders.
  271        *
  272        * @see FetchProfile
  273        */
  274       public static class FetchProfileItem extends FetchProfile.Item {
  275   	protected FetchProfileItem(String name) {
  276   	    super(name);
  277   	}
  278   
  279   	/**
  280   	 * HEADERS is a fetch profile item that can be included in a
  281   	 * <code>FetchProfile</code> during a fetch request to a Folder.
  282   	 * This item indicates that the headers for messages in the specified 
  283   	 * range are desired to be prefetched. <p>
  284   	 * 
  285   	 * An example of how a client uses this is below: <p>
  286   	 * <blockquote><pre>
  287   	 *
  288   	 * 	FetchProfile fp = new FetchProfile();
  289   	 *	fp.add(IMAPFolder.FetchProfileItem.HEADERS);
  290   	 *	folder.fetch(msgs, fp);
  291   	 *
  292   	 * </pre></blockquote><p>
  293   	 */ 
  294   	public static final FetchProfileItem HEADERS = 
  295   		new FetchProfileItem("HEADERS");
  296   
  297   	/**
  298   	 * SIZE is a fetch profile item that can be included in a
  299   	 * <code>FetchProfile</code> during a fetch request to a Folder.
  300   	 * This item indicates that the sizes of the messages in the specified 
  301   	 * range are desired to be prefetched. <p>
  302   	 *
  303   	 * SIZE should move to FetchProfile.Item in JavaMail 1.3.
  304   	 */
  305   	public static final FetchProfileItem SIZE = 
  306   		new FetchProfileItem("SIZE");
  307       }
  308   
  309       /**
  310        * Constructor used to create a possibly non-existent folder.
  311        *
  312        * @param fullName	fullname of this folder
  313        * @param separator the default separator character for this 
  314        *			folder's namespace
  315        * @param store	the Store
  316        */
  317       protected IMAPFolder(String fullName, char separator, IMAPStore store) {
  318   	super(store);
  319   	if (fullName == null)
  320   	    throw new NullPointerException("Folder name is null");
  321   	this.fullName = fullName;
  322   	this.separator = separator;
  323   	messageCacheLock = new Object();
  324           debug = store.getSession().getDebug();
  325           connectionPoolDebug = ((IMAPStore)store).getConnectionPoolDebug();
  326   	out = store.getSession().getDebugOut();
  327   	if (out == null)	// should never happen
  328   	    out = System.out;
  329   
  330   	/*
  331   	 * Work around apparent bug in Exchange.  Exchange
  332   	 * will return a name of "Public Folders/" from
  333   	 * LIST "%".
  334   	 *
  335   	 * If name has one separator, and it's at the end,
  336   	 * assume this is a namespace name and treat it
  337   	 * accordingly.  Usually this will happen as a result
  338   	 * of the list method, but this also allows getFolder
  339   	 * to work with namespace names.
  340   	 */
  341   	this.isNamespace = false;
  342   	if (separator != UNKNOWN_SEPARATOR && separator != '\0') {
  343   	    int i = this.fullName.indexOf(separator);
  344   	    if (i > 0 && i == this.fullName.length() - 1) {
  345   		this.fullName = this.fullName.substring(0, i);
  346   		this.isNamespace = true;
  347   	    }
  348   	}
  349       }
  350   
  351       /**
  352        * Constructor used to create a possibly non-existent folder.
  353        *
  354        * @param fullName	fullname of this folder
  355        * @param separator the default separator character for this 
  356        *			folder's namespace
  357        * @param store	the Store
  358        */
  359       protected IMAPFolder(String fullName, char separator, IMAPStore store,
  360   				boolean isNamespace) {
  361   	this(fullName, separator, store);
  362   	this.isNamespace = isNamespace;
  363       }
  364   
  365       /**
  366        * Constructor used to create an existing folder.
  367        */
  368       protected IMAPFolder(ListInfo li, IMAPStore store) {
  369   	this(li.name, li.separator, store);
  370   
  371   	if (li.hasInferiors)
  372   	    type |= HOLDS_FOLDERS;
  373   	if (li.canOpen)
  374   	    type |= HOLDS_MESSAGES;
  375   	exists = true;
  376   	attributes = li.attrs;
  377       }
  378   	
  379       /*
  380        * Ensure that this folder exists. If 'exists' has been set to true,
  381        * we don't attempt to validate it with the server again. Note that
  382        * this can result in a possible loss of sync with the server.
  383        */
  384       private void checkExists() throws MessagingException {
  385   	// If the boolean field 'exists' is false, check with the
  386   	// server by invoking exists() ..
  387   	if (!exists && !exists())
  388   	    throw new FolderNotFoundException(
  389   		this, fullName + " not found");
  390       }
  391   
  392       /*
  393        * Ensure the folder is closed.
  394        * ASSERT: Must be called with this folder's synchronization lock held.
  395        */
  396       private void checkClosed() {
  397   	if (opened)
  398   	    throw new IllegalStateException(
  399   		"This operation is not allowed on an open folder"
  400   		);
  401       }
  402   
  403       /*
  404        * Ensure the folder is open.
  405        * ASSERT: Must be called with this folder's synchronization lock held.
  406        */
  407       private void checkOpened() throws FolderClosedException {
  408   	assert Thread.holdsLock(this);
  409   	if (!opened) {
  410   	    if (reallyClosed)
  411   		throw new IllegalStateException(
  412   		    "This operation is not allowed on a closed folder"
  413   	    	);
  414   	    else // Folder was closed "implicitly"
  415   		throw new FolderClosedException(this, 
  416   		    "Lost folder connection to server"
  417   		);
  418   	}
  419       }
  420   
  421       /*
  422        * Check that the given message number is within the range
  423        * of messages present in this folder. If the message
  424        * number is out of range, we ping the server to obtain any
  425        * pending new message notifications from the server.
  426        */
  427       private void checkRange(int msgno) throws MessagingException {
  428   	if (msgno < 1) // message-numbers start at 1
  429   	    throw new IndexOutOfBoundsException();
  430   
  431   	if (msgno <= total)
  432   	    return;
  433   
  434   	// Out of range, let's ping the server and see if
  435   	// the server has more messages for us.
  436   
  437   	synchronized(messageCacheLock) { // Acquire lock
  438   	    try {
  439   		keepConnectionAlive(false);
  440   	    } catch (ConnectionException cex) {
  441   		// Oops, lost connection
  442   		throw new FolderClosedException(this, cex.getMessage());
  443   	    } catch (ProtocolException pex) { 
  444   		throw new MessagingException(pex.getMessage(), pex);
  445   	    }
  446   	} // Release lock
  447   
  448   	if (msgno > total) // Still out of range ? Throw up ...
  449   	    throw new IndexOutOfBoundsException();
  450       }
  451   
  452       /*
  453        * Check whether the given flags are supported by this server,
  454        * and also verify that the folder allows setting flags.
  455        */
  456       private void checkFlags(Flags flags) throws MessagingException {
  457   	assert Thread.holdsLock(this);
  458   	if (mode != READ_WRITE)
  459   	    throw new IllegalStateException(
  460   		"Cannot change flags on READ_ONLY folder: " + fullName
  461   		);
  462   	/*
  463   	if (!availableFlags.contains(flags))
  464   	    throw new MessagingException(
  465   		"These flags are not supported by this implementation"
  466   		);
  467   	*/
  468       }
  469   
  470       /**
  471        * Get the name of this folder.
  472        */
  473       public synchronized String getName() {
  474   	/* Return the last component of this Folder's full name.
  475   	 * Folder components are delimited by the separator character.
  476   	 */
  477   	if (name == null) {
  478   	    try {
  479   		name = 	fullName.substring(
  480   			    fullName.lastIndexOf(getSeparator()) + 1
  481   			);
  482   	    } catch (MessagingException mex) { }
  483   	}
  484   	return name;
  485       }
  486   
  487       /**
  488        * Get the fullname of this folder.
  489        */
  490       public synchronized String getFullName() {
  491   	return fullName;	
  492       }
  493   
  494       /**
  495        * Get this folder's parent.
  496        */
  497       public synchronized Folder getParent() throws MessagingException {
  498   	char c = getSeparator();
  499   	int index;
  500   	if ((index = fullName.lastIndexOf(c)) != -1)
  501   	    return new IMAPFolder(fullName.substring(0, index), 
  502   				  c, (IMAPStore)store);
  503   	else
  504   	    return new DefaultFolder((IMAPStore)store);
  505       }
  506   
  507       /**
  508        * Check whether this folder really exists on the server.
  509        */
  510       public synchronized boolean exists() throws MessagingException {
  511   	// Check whether this folder exists ..
  512   	ListInfo[] li = null;
  513   	final String lname;
  514   	if (isNamespace && separator != '\0')
  515   	    lname = fullName + separator;
  516   	else
  517   	    lname = fullName;
  518   
  519   	li = (ListInfo[])doCommand(new ProtocolCommand() {
  520   	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
  521   		return p.list("", lname);
  522   	    }
  523   	});
  524   
  525   	if (li != null) {
  526   	    int i = findName(li, lname);
  527   	    fullName = li[i].name;
  528   	    separator = li[i].separator;
  529   	    int len = fullName.length();
  530   	    if (separator != '\0' && len > 0 &&
  531   		    fullName.charAt(len - 1) == separator) {
  532   		fullName = fullName.substring(0, len - 1);
  533   	    }
  534   	    type = 0;
  535   	    if (li[i].hasInferiors)
  536   		type |= HOLDS_FOLDERS;
  537   	    if (li[i].canOpen)
  538   		type |= HOLDS_MESSAGES;
  539   	    exists = true;
  540   	    attributes = li[i].attrs;
  541   	} else {
  542   	    exists = opened;
  543   	    attributes = null;
  544   	}
  545   
  546   	return exists;
  547       }
  548   
  549       /**
  550        * Which entry in <code>li</code> matches <code>lname</code>?
  551        * If the name contains wildcards, more than one entry may be
  552        * returned.
  553        */
  554       private int findName(ListInfo[] li, String lname) {
  555   	int i;
  556   	// if the name contains a wildcard, there might be more than one
  557   	for (i = 0; i < li.length; i++) {
  558   	    if (li[i].name.equals(lname))
  559   		break;
  560   	}
  561   	if (i >= li.length) {	// nothing matched exactly
  562   	    // XXX - possibly should fail?  But what if server
  563   	    // is case insensitive and returns the preferred
  564   	    // case of the name here?
  565   	    i = 0;		// use first one
  566   	}
  567   	return i;
  568       }
  569   
  570       /**
  571        * List all subfolders matching the specified pattern.
  572        */
  573       public Folder[] list(String pattern) throws MessagingException {
  574   	return doList(pattern, false);
  575       }
  576   
  577       /**
  578        * List all subscribed subfolders matching the specified pattern.
  579        */
  580       public Folder[] listSubscribed(String pattern) throws MessagingException {
  581   	return doList(pattern, true);
  582       }
  583   
  584       private synchronized Folder[] doList(final String pattern,
  585   		final boolean subscribed) throws MessagingException {
  586   	checkExists(); // insure that this folder does exist.
  587   	
  588   	if (!isDirectory()) // Why waste a roundtrip to the server ?
  589   	    return new Folder[0];
  590   
  591   	final char c = getSeparator();
  592   
  593   	ListInfo[] li = (ListInfo[])doCommandIgnoreFailure(
  594   	    new ProtocolCommand() {
  595   		public Object doCommand(IMAPProtocol p)
  596   			throws ProtocolException {
  597   		    if (subscribed)
  598   			return p.lsub("", fullName + c + pattern);
  599   		    else 
  600   			return p.list("", fullName + c + pattern);
  601   		}
  602   	    });
  603   
  604   	if (li == null)
  605   	    return new Folder[0];
  606   
  607   	/*
  608   	 * The UW based IMAP4 servers (e.g. SIMS2.0) include
  609   	 * current folder (terminated with the separator), when
  610   	 * the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%> 
  611   	 * returns "mail/" as the first LIST response.
  612   	 *
  613   	 * Doesn't make sense to include the current folder in this
  614   	 * case, so we filter it out. Note that I'm assuming that
  615   	 * the offending response is the *first* one, my experiments
  616   	 * with the UW & SIMS2.0 servers indicate that .. 
  617   	 */
  618   	int start = 0;
  619   	// Check the first LIST response.
  620   	if (li.length > 0 && li[0].name.equals(fullName + c)) 
  621   	    start = 1; // start from index = 1
  622   
  623   	IMAPFolder[] folders = new IMAPFolder[li.length - start];
  624   	for (int i = start; i < li.length; i++)
  625   	    folders[i-start] = new IMAPFolder(li[i], (IMAPStore)store);
  626   	return folders;
  627       }
  628   
  629       /**
  630        * Get the separator character.
  631        */
  632       public synchronized char getSeparator() throws MessagingException {
  633   	if (separator == UNKNOWN_SEPARATOR) {
  634   	    ListInfo[] li = null;
  635   
  636   	    li = (ListInfo[])doCommand(new ProtocolCommand() {
  637   		public Object doCommand(IMAPProtocol p)
  638   			throws ProtocolException {
  639   		    // REV1 allows the following LIST format to obtain
  640   		    // the hierarchy delimiter of non-existent folders
  641   		    if (p.isREV1()) // IMAP4rev1
  642   		        return p.list(fullName, "");
  643   		    else // IMAP4, note that this folder must exist for this
  644   		        // to work :(
  645   		        return p.list("", fullName);
  646   		}
  647   	    });
  648   
  649   	    if (li != null) 
  650   		separator = li[0].separator;
  651   	    else
  652   		separator = '/'; // punt !
  653   	}
  654   	return separator;
  655       }
  656   
  657       /**
  658        * Get the type of this folder.
  659        */
  660       public synchronized int getType() throws MessagingException {
  661   	if (opened) {
  662   	    // never throw FolderNotFoundException if folder is open
  663   	    if (attributes == null)
  664   		exists();	// try to fetch attributes
  665   	} else {
  666   	    checkExists();
  667   	}
  668   	return type;
  669       }
  670       
  671       /**
  672        * Check whether this folder is subscribed. <p>
  673        */
  674       public synchronized boolean isSubscribed() {
  675   	ListInfo[] li = null;
  676   	final String lname;
  677   	if (isNamespace && separator != '\0')
  678   	    lname = fullName + separator;
  679   	else
  680   	    lname = fullName;
  681   
  682   	try {
  683   	    li = (ListInfo[])doProtocolCommand(new ProtocolCommand() {
  684   		public Object doCommand(IMAPProtocol p)
  685   			throws ProtocolException {
  686   		    return p.lsub("", lname);
  687   		}
  688   	    });
  689   	} catch (ProtocolException pex) {
  690           }
  691   
  692   	if (li != null) {
  693   	    int i = findName(li, lname);
  694   	    return li[i].canOpen;
  695   	} else
  696   	    return false;
  697       }
  698   
  699       /**
  700        * Subscribe/Unsubscribe this folder.
  701        */
  702       public synchronized void setSubscribed(final boolean subscribe) 
  703   			throws MessagingException {
  704   	doCommandIgnoreFailure(new ProtocolCommand() {
  705   	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
  706   		if (subscribe)
  707   		    p.subscribe(fullName);
  708   		else
  709   		    p.unsubscribe(fullName);
  710   		return null;
  711   	    }
  712   	});
  713       }
  714   	
  715       /**
  716        * Create this folder, with the specified type.
  717        */
  718       public synchronized boolean create(final int type)
  719   				throws MessagingException {
  720   
  721   	char c = 0;
  722   	if ((type & HOLDS_MESSAGES) == 0)	// only holds folders
  723   	    c = getSeparator();
  724   	final char sep = c;
  725   	Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
  726   		public Object doCommand(IMAPProtocol p)
  727   			throws ProtocolException {
  728   		    if ((type & HOLDS_MESSAGES) == 0)	// only holds folders
  729   			p.create(fullName + sep);
  730   		    else {
  731   			p.create(fullName);
  732   
  733   			// Certain IMAP servers do not allow creation of folders
  734   			// that can contain messages *and* subfolders. So, if we
  735   			// were asked to create such a folder, we should verify
  736   			// that we could indeed do so.
  737   			if ((type & HOLDS_FOLDERS) != 0) {
  738   			    // we want to hold subfolders and messages. Check
  739   			    // whether we could create such a folder.
  740   			    ListInfo[] li = p.list("", fullName);
  741   			    if (li != null && !li[0].hasInferiors) {
  742   				// Hmm ..the new folder 
  743   				// doesn't support Inferiors ? Fail
  744   				p.delete(fullName);
  745   				throw new ProtocolException("Unsupported type");
  746   			    }
  747   			}
  748   		    }
  749   		    return Boolean.TRUE;
  750   		}
  751   	    });
  752   
  753   	if (ret == null)
  754   	    return false; // CREATE failure, maybe this 
  755   			  // folder already exists ?
  756   
  757   	// exists = true;
  758   	// this.type = type;
  759   	boolean retb = exists();	// set exists, type, and attributes
  760   	if (retb)		// Notify listeners on self and our Store
  761   	    notifyFolderListeners(FolderEvent.CREATED);
  762   	return retb;
  763       }
  764   
  765       /**
  766        * Check whether this folder has new messages.
  767        */
  768       public synchronized boolean hasNewMessages() throws MessagingException {
  769   	if (opened) {	// If we are open, we already have this information
  770   	    // Folder is open, make sure information is up to date
  771   	    synchronized(messageCacheLock) {
  772   		// tickle the folder and store connections.
  773   		try {
  774   		    keepConnectionAlive(true);
  775   		} catch (ConnectionException cex) {
  776   		    throw new FolderClosedException(this, cex.getMessage());
  777   		} catch (ProtocolException pex) {
  778   		    throw new MessagingException(pex.getMessage(), pex);
  779   		}
  780   		return recent > 0 ? true : false;
  781   	    }
  782   	}
  783   
  784   	// First, the cheap way - use LIST and look for the \Marked
  785   	// or \Unmarked tag
  786   
  787   	ListInfo[] li = null;
  788   	final String lname;
  789   	if (isNamespace && separator != '\0')
  790   	    lname = fullName + separator;
  791   	else
  792   	    lname = fullName;
  793   	li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() {
  794   	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
  795   		return p.list("", lname);
  796   	    }
  797   	});
  798   
  799   	// if folder doesn't exist, throw exception
  800   	if (li == null)
  801   	    throw new FolderNotFoundException(this, fullName + " not found");
  802   
  803   	int i = findName(li, lname);
  804   	if (li[i].changeState == ListInfo.CHANGED)
  805   	    return true;
  806   	else if (li[i].changeState == ListInfo.UNCHANGED)
  807   	    return false;
  808   
  809   	// LIST didn't work. Try the hard way, using STATUS
  810   	try {
  811   	    Status status = getStatus();
  812   	    if (status.recent > 0)
  813   		return true;
  814   	    else
  815   		return false;
  816   	} catch (BadCommandException bex) {
  817   	    // Probably doesn't support STATUS, tough luck.
  818   	    return false;
  819   	} catch (ConnectionException cex) {
  820   	    throw new StoreClosedException(store, cex.getMessage());
  821   	} catch (ProtocolException pex) {
  822   	    throw new MessagingException(pex.getMessage(), pex);
  823   	}
  824       }
  825   
  826       /**
  827        * Get the named subfolder. <p>
  828        */
  829       public Folder getFolder(String name) throws MessagingException {
  830   	// If we know that this folder is *not* a directory, don't
  831   	// send the request to the server at all ...
  832   	if (attributes != null && !isDirectory())
  833   	    throw new MessagingException("Cannot contain subfolders");
  834   
  835   	char c = getSeparator();
  836   	return new IMAPFolder(fullName + c + name, c, (IMAPStore)store);
  837       }
  838   
  839       /**
  840        * Delete this folder.
  841        */
  842       public synchronized boolean delete(boolean recurse) 
  843   			throws MessagingException {  
  844   	checkClosed(); // insure that this folder is closed.
  845   
  846   	if (recurse) {
  847   	    // Delete all subfolders.
  848   	    Folder[] f = list();
  849   	    for (int i = 0; i < f.length; i++)
  850   		f[i].delete(recurse); // ignore intermediate failures
  851   	}
  852   
  853   	// Attempt to delete this folder
  854   
  855   	Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
  856   	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
  857   		p.delete(fullName);
  858   		return Boolean.TRUE;
  859   	    }
  860   	});
  861   
  862   	if (ret == null)
  863   	    // Non-existent folder/No permission ??
  864   	    return false;
  865   
  866   	// DELETE succeeded.
  867   	exists = false;
  868   	attributes = null;
  869   
  870   	// Notify listeners on self and our Store
  871   	notifyFolderListeners(FolderEvent.DELETED);
  872   	return true;
  873       }
  874   
  875       /**
  876        * Rename this folder. <p>
  877        */
  878       public synchronized boolean renameTo(final Folder f)
  879   				throws MessagingException {
  880   	checkClosed(); // insure that we are closed.
  881   	checkExists();
  882   	if (f.getStore() != store)
  883   	    throw new MessagingException("Can't rename across Stores");
  884   
  885   
  886   	Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
  887   	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
  888   		p.rename(fullName, f.getFullName());
  889   		return Boolean.TRUE;
  890   	    }
  891   	});
  892   
  893   	if (ret == null)
  894   	    return false;
  895   
  896   	exists = false;
  897   	attributes = null;
  898   	notifyFolderRenamedListeners(f);
  899   	return true;
  900       }
  901   
  902       /**
  903        * Open this folder in the given mode.
  904        */
  905       public synchronized void open(int mode) throws MessagingException {
  906   	checkClosed(); // insure that we are not already open
  907   	
  908   	MailboxInfo mi = null;
  909   	// Request store for our own protocol connection.
  910   	protocol = ((IMAPStore)store).getProtocol(this);
  911   
  912   	CommandFailedException exc = null;
  913       lock:
  914   	synchronized(messageCacheLock) { // Acquire messageCacheLock
  915   
  916   	    /*
  917   	     * Add response handler right away so we get any alerts or
  918   	     * notifications that occur during the SELECT or EXAMINE.
  919   	     * Have to be sure to remove it if we fail to open the
  920   	     * folder.
  921   	     */
  922   	    protocol.addResponseHandler(this);
  923   
  924   	    try {
  925   		if (mode == READ_ONLY)
  926   		    mi = protocol.examine(fullName);
  927   		else
  928   		    mi = protocol.select(fullName);
  929   	    } catch (CommandFailedException cex) {
  930   		// got a NO; connection still good, return it
  931   		releaseProtocol(true);
  932   		protocol = null;
  933   		exc = cex;
  934   		break lock;
  935   	    } catch (ProtocolException pex) {
  936   		// got a BAD or a BYE; connection may be bad, close it
  937   		try {
  938   		    protocol.logout();
  939   		} catch (ProtocolException pex2) {
  940   		    // ignore
  941   		} finally {
  942   		    releaseProtocol(false);
  943   		    protocol = null;
  944   		    throw new MessagingException(pex.getMessage(), pex);
  945   		}
  946   	    }
  947   
  948   	    if (mi.mode != mode) {
  949   		if (mode == READ_WRITE && mi.mode == READ_ONLY &&
  950   			((IMAPStore)store).allowReadOnlySelect()) {
  951   		    ;		// all ok, allow it
  952   		} else {	// otherwise, it's an error
  953   		    try {
  954   			// close mailbox and return connection
  955   			protocol.close();
  956   			releaseProtocol(true);
  957   		    } catch (ProtocolException pex) {
  958   			// something went wrong, close connection
  959   			try {
  960   			    protocol.logout();
  961   			} catch (ProtocolException pex2) {
  962   			    // ignore
  963   			} finally {
  964   			    releaseProtocol(false);
  965   			}
  966   		    } finally {
  967   			protocol = null;
  968   			throw new ReadOnlyFolderException(this,
  969   				      "Cannot open in desired mode");
  970   		    }
  971   
  972   		}
  973               }
  974   	
  975   	    // Initialize stuff.
  976   	    opened = true;
  977   	    reallyClosed = false;
  978   	    this.mode = mi.mode;
  979   	    availableFlags = mi.availableFlags;
  980   	    permanentFlags = mi.permanentFlags;
  981   	    total = realTotal = mi.total;
  982   	    recent = mi.recent;
  983   	    uidvalidity = mi.uidvalidity;
  984   	    uidnext = mi.uidnext;
  985   
  986   	    // Create the message cache vector of appropriate size
  987   	    messageCache = new Vector(total);
  988   	    // Fill up the cache with light-weight IMAPMessage objects
  989   	    for (int i = 0; i < total; i++)
  990   		messageCache.addElement(new IMAPMessage(this, i+1, i+1));
  991   
  992   	} // Release lock
  993   
  994   	/*
  995   	 * Handle SELECT or EXAMINE failure after lock is released.
  996   	 * Try to figure out why the operation failed so we can
  997   	 * report a more reasonable exception.
  998   	 */
  999   	if (exc != null) {
 1000   	    checkExists();	// throw exception if folder doesn't exist
 1001   
 1002   	    if ((type & HOLDS_MESSAGES) == 0)
 1003   		throw new MessagingException("folder cannot contain messages");
 1004   	    throw new MessagingException(exc.getMessage(), exc);
 1005   	}
 1006   
 1007   	exists = true;		// if we opened it, it must exist
 1008   	attributes = null;	// but we don't yet know its attributes
 1009   	type = HOLDS_MESSAGES;	// lacking more info, we know at least this much
 1010   
 1011   	// notify listeners
 1012   	notifyConnectionListeners(ConnectionEvent.OPENED);
 1013       }
 1014   
 1015       /**
 1016        * Prefetch attributes, based on the given FetchProfile.
 1017        */
 1018       public synchronized void fetch(Message[] msgs, FetchProfile fp)
 1019   			throws MessagingException {
 1020   	checkOpened();
 1021   	IMAPMessage.fetch(this, msgs, fp);
 1022       }
 1023   
 1024       /**
 1025        * Set the specified flags for the given array of messages.
 1026        */
 1027       public synchronized void setFlags(Message[] msgs, Flags flag, boolean value)
 1028   			throws MessagingException {
 1029   	checkOpened();
 1030   	checkFlags(flag); // validate flags
 1031   
 1032   	if (msgs.length == 0) // boundary condition
 1033   	    return;
 1034   
 1035   	synchronized(messageCacheLock) {
 1036   	    try {
 1037   		IMAPProtocol p = getProtocol();
 1038   		MessageSet[] ms = Utility.toMessageSet(msgs, null);
 1039   		if (ms == null)
 1040   		    throw new MessageRemovedException(
 1041   					"Messages have been removed");
 1042   		p.storeFlags(ms, flag, value);
 1043   	    } catch (ConnectionException cex) {
 1044   		throw new FolderClosedException(this, cex.getMessage());
 1045   	    } catch (ProtocolException pex) {
 1046   		throw new MessagingException(pex.getMessage(), pex);
 1047   	    }
 1048   	}
 1049       }
 1050   
 1051       /**
 1052        * Close this folder.
 1053        */
 1054       public synchronized void close(boolean expunge) throws MessagingException {
 1055   	close(expunge, false);
 1056       }
 1057   
 1058       /**
 1059        * Close this folder without waiting for the server.
 1060        */
 1061       public synchronized void forceClose() throws MessagingException {
 1062   	close(false, true);
 1063       }
 1064   
 1065       /*
 1066        * Common close method.
 1067        */
 1068       private void close(boolean expunge, boolean force)
 1069   				throws MessagingException {
 1070   	assert Thread.holdsLock(this);
 1071   	synchronized(messageCacheLock) {
 1072   	    /*
 1073   	     * If we already know we're closed, this is illegal.
 1074   	     * Can't use checkOpened() because if we were forcibly
 1075   	     * closed asynchronously we just want to complete the
 1076   	     * closing here.
 1077   	     */
 1078   	    if (!opened && reallyClosed)
 1079   		throw new IllegalStateException(
 1080   		    "This operation is not allowed on a closed folder"
 1081   		);
 1082   
 1083   	    reallyClosed = true; // Ok, lets reset
 1084   
 1085   	    // Maybe this folder is already closed, or maybe another
 1086   	    // thread which had the messageCacheLock earlier, found
 1087   	    // that our server connection is dead and cleaned up
 1088   	    // everything ..
 1089   	    if (!opened)
 1090   		return;
 1091   
 1092   	    try {
 1093   		waitIfIdle();
 1094   		if (force) {
 1095                       if (debug)
 1096                           out.println("DEBUG: forcing folder " + fullName +
 1097   					" to close");
 1098   		    if (protocol != null)
 1099   			protocol.disconnect();
 1100                   } else if (((IMAPStore)store).isConnectionPoolFull()) {
 1101   		    // If the connection pool is full, logout the connection
 1102                       if (debug)
 1103                           out.println("DEBUG: pool is full, not adding " +
 1104                               "an Authenticated connection");
 1105   
 1106   		    // If the expunge flag is set, close the folder first.
 1107   		    if (expunge)
 1108   			protocol.close();
 1109   
 1110   		    if (protocol != null)
 1111   			protocol.logout();
 1112                   } else {
 1113   		    // If the expunge flag is set or we're open read-only we
 1114   		    // can just close the folder, otherwise open it read-only
 1115   		    // before closing.
 1116                       if (!expunge && mode == READ_WRITE) {
 1117                           try {
 1118                               MailboxInfo mi = protocol.examine(fullName);
 1119                           } catch (ProtocolException pex2) {
 1120                               if (protocol != null)
 1121   				protocol.disconnect();
 1122                           }
 1123                       }
 1124   		    if (protocol != null)
 1125   			protocol.close();
 1126                   }
 1127   	    } catch (ProtocolException pex) {
 1128   		throw new MessagingException(pex.getMessage(), pex);
 1129   	    } finally {
 1130   		// cleanup if we haven't already
 1131   		if (opened)
 1132   		    cleanup(true);
 1133   	    }
 1134   	}
 1135       }
 1136   
 1137       // NOTE: this method can currently be invoked from close() or
 1138       // from handleResponses(). Both invocations are conditional,
 1139       // based on the "opened" flag, so we are sure that multiple
 1140       // Connection.CLOSED events are not generated. Also both
 1141       // invocations are from within messageCacheLock-ed areas.
 1142       private void cleanup(boolean returnToPool) {
 1143           releaseProtocol(returnToPool);
 1144           protocol = null;
 1145   	messageCache = null;
 1146   	uidTable = null;
 1147   	exists = false; // to force a recheck in exists().
 1148   	attributes = null;
 1149           opened = false;
 1150   	idleState = RUNNING;	// just in case
 1151   	notifyConnectionListeners(ConnectionEvent.CLOSED);
 1152       }
 1153   
 1154       /**
 1155        * Check whether this connection is really open.
 1156        */
 1157       public synchronized boolean isOpen() {
 1158   	synchronized(messageCacheLock) {
 1159   	    // Probe the connection to make sure its really open.
 1160   	    if (opened) {
 1161   		try {
 1162   		    keepConnectionAlive(false);
 1163   		} catch (ProtocolException pex) { }
 1164   	    }
 1165   	}
 1166   
 1167   	return opened;
 1168       }
 1169   
 1170       /**
 1171        * Return the permanent flags supported by the server.
 1172        */
 1173       public synchronized Flags getPermanentFlags() {
 1174   	return (Flags)(permanentFlags.clone());
 1175       }
 1176   
 1177       /**
 1178        * Get the total message count.
 1179        */
 1180       public synchronized int getMessageCount() throws MessagingException {
 1181   	if (!opened) {
 1182   	    checkExists();
 1183   	    // If this folder is not yet open, we use STATUS to
 1184   	    // get the total message count
 1185   	    try {
 1186   		Status status = getStatus();
 1187   		return status.total;
 1188   	    } catch (BadCommandException bex) {
 1189   		// doesn't support STATUS, probably vanilla IMAP4 ..
 1190   		// lets try EXAMINE
 1191                   IMAPProtocol p = null;
 1192   
 1193   	        try {
 1194   	            p = getStoreProtocol();	// XXX
 1195   		    MailboxInfo minfo = p.examine(fullName);
 1196   		    p.close();
 1197   		    return minfo.total;
 1198   		} catch (ProtocolException pex) {
 1199   		    // Give up.
 1200   		    throw new MessagingException(pex.getMessage(), pex);
 1201   		} finally {
 1202                       releaseStoreProtocol(p);
 1203                   }
 1204   	    } catch (ConnectionException cex) {
 1205                   throw new StoreClosedException(store, cex.getMessage());
 1206   	    } catch (ProtocolException pex) {
 1207   		throw new MessagingException(pex.getMessage(), pex);
 1208   	    }
 1209   	}
 1210   
 1211   	// Folder is open, we know what the total message count is ..
 1212   	synchronized(messageCacheLock) {
 1213   	    // tickle the folder and store connections.
 1214   	    try {
 1215   		keepConnectionAlive(true);
 1216   		return total;
 1217   	    } catch (ConnectionException cex) {
 1218   		throw new FolderClosedException(this, cex.getMessage());
 1219   	    } catch (ProtocolException pex) {
 1220   		throw new MessagingException(pex.getMessage(), pex);
 1221   	    }
 1222   	}
 1223       }
 1224   
 1225       /**
 1226        * Get the new message count.
 1227        */
 1228       public synchronized int getNewMessageCount()
 1229   			throws MessagingException {
 1230   	if (!opened) {
 1231   	    checkExists();
 1232   	    // If this folder is not yet open, we use STATUS to
 1233   	    // get the new message count
 1234   	    try {
 1235   		Status status = getStatus();
 1236   		return status.recent;
 1237   	    } catch (BadCommandException bex) {
 1238   		// doesn't support STATUS, probably vanilla IMAP4 ..
 1239   		// lets try EXAMINE
 1240                   IMAPProtocol p = null;
 1241   
 1242   	        try {
 1243   	            p = getStoreProtocol();	// XXX
 1244   		    MailboxInfo minfo = p.examine(fullName);
 1245   		    p.close();
 1246   		    return minfo.recent;
 1247   		} catch (ProtocolException pex) {
 1248   		    // Give up.
 1249   		    throw new MessagingException(pex.getMessage(), pex);
 1250   		} finally {
 1251                       releaseStoreProtocol(p);
 1252                   }
 1253   	    } catch (ConnectionException cex) {
 1254   		throw new StoreClosedException(store, cex.getMessage());
 1255   	    } catch (ProtocolException pex) {
 1256   		throw new MessagingException(pex.getMessage(), pex);
 1257   	    }
 1258   	}
 1259   
 1260   	// Folder is open, we know what the new message count is ..
 1261   	synchronized(messageCacheLock) {
 1262   	    // tickle the folder and store connections.
 1263   	    try {
 1264   		keepConnectionAlive(true);
 1265   		return recent;
 1266   	    } catch (ConnectionException cex) {
 1267   		throw new FolderClosedException(this, cex.getMessage());
 1268   	    } catch (ProtocolException pex) {
 1269   		throw new MessagingException(pex.getMessage(), pex);
 1270   	    }
 1271   	}
 1272       }
 1273   
 1274       /**
 1275        * Get the unread message count.
 1276        */
 1277       public synchronized int getUnreadMessageCount()
 1278   			throws MessagingException {
 1279   	if (!opened) {
 1280   	    checkExists();
 1281   	    // If this folder is not yet open, we use STATUS to
 1282   	    // get the unseen message count
 1283   	    try {
 1284   		Status status = getStatus();
 1285   		return status.unseen;
 1286   	    } catch (BadCommandException bex) {
 1287   		// doesn't support STATUS, probably vanilla IMAP4 ..
 1288   		// Could EXAMINE, SEARCH for UNREAD messages and
 1289   		// return the count .. bah, not worth it.
 1290   		return -1;
 1291   	    } catch (ConnectionException cex) {
 1292   		throw new StoreClosedException(store, cex.getMessage());
 1293   	    } catch (ProtocolException pex) {
 1294   		throw new MessagingException(pex.getMessage(), pex);
 1295   	    }
 1296   	}
 1297   
 1298   	// if opened, issue server-side search for messages that do
 1299   	// *not* have the SEEN flag.
 1300   	Flags f = new Flags();
 1301   	f.add(Flags.Flag.SEEN);
 1302   	try {
 1303   	    synchronized(messageCacheLock) {
 1304   		int[] matches = getProtocol().search(new FlagTerm(f, false));
 1305   		return matches.length; // NOTE: 'matches' is never null
 1306   	    }
 1307   	} catch (ConnectionException cex) {
 1308   	    throw new FolderClosedException(this, cex.getMessage());
 1309   	} catch (ProtocolException pex) {
 1310   	    // Shouldn't happen
 1311   	    throw new MessagingException(pex.getMessage(), pex);
 1312   	}
 1313       }
 1314   
 1315       /**
 1316        * Get the deleted message count.
 1317        */
 1318       public synchronized int getDeletedMessageCount()
 1319   			throws MessagingException {
 1320   	if (!opened) {
 1321   	    checkExists();
 1322   	    // no way to do this on closed folders
 1323   	    return -1;
 1324   	}
 1325   
 1326   	// if opened, issue server-side search for messages that do
 1327   	// have the DELETED flag.
 1328   	Flags f = new Flags();
 1329   	f.add(Flags.Flag.DELETED);
 1330   	try {
 1331   	    synchronized(messageCacheLock) {
 1332   		int[] matches = getProtocol().search(new FlagTerm(f, true));
 1333   		return matches.length; // NOTE: 'matches' is never null
 1334   	    }
 1335   	} catch (ConnectionException cex) {
 1336   	    throw new FolderClosedException(this, cex.getMessage());
 1337   	} catch (ProtocolException pex) {
 1338   	    // Shouldn't happen
 1339   	    throw new MessagingException(pex.getMessage(), pex);
 1340   	}
 1341       }
 1342   
 1343       /*
 1344        * Get results of STATUS command for this folder, checking cache first.
 1345        * ASSERT: Must be called with this folder's synchronization lock held.
 1346        * ASSERT: The folder must be closed.
 1347        */
 1348       private Status getStatus() throws ProtocolException {
 1349   	int statusCacheTimeout = ((IMAPStore)store).getStatusCacheTimeout();
 1350   
 1351   	// if allowed to cache and our cache is still valid, return it
 1352   	if (statusCacheTimeout > 0 && cachedStatus != null &&
 1353   	    System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout)
 1354   	    return cachedStatus;
 1355   
 1356           IMAPProtocol p = null;
 1357   
 1358   	try {
 1359   	    p = getStoreProtocol();	// XXX
 1360   	    Status s = p.status(fullName, null); 
 1361   	    // if allowed to cache, do so
 1362   	    if (statusCacheTimeout > 0) {
 1363   		cachedStatus = s;
 1364   		cachedStatusTime = System.currentTimeMillis();
 1365   	    }
 1366   	    return s;
 1367           } finally {
 1368               releaseStoreProtocol(p);
 1369           }
 1370       }
 1371   
 1372       /**
 1373        * Get the specified message.
 1374        */
 1375       public synchronized Message getMessage(int msgnum) 
 1376   		throws MessagingException {
 1377   	checkOpened();
 1378   	checkRange(msgnum);
 1379   
 1380   	return (Message)messageCache.elementAt(msgnum-1);
 1381       }
 1382   
 1383       /**
 1384        * Append the given messages into this folder.
 1385        */
 1386       public synchronized void appendMessages(Message[] msgs)
 1387   				throws MessagingException {
 1388   	checkExists(); // verify that self exists
 1389   
 1390   	// XXX - have to verify that messages are in a different
 1391   	// store (if any) than target folder, otherwise could
 1392   	// deadlock trying to fetch messages on the same connection
 1393   	// we're using for the append.
 1394   
 1395   	int maxsize = ((IMAPStore)store).getAppendBufferSize();
 1396   
 1397   	for (int i = 0; i < msgs.length; i++) {
 1398   	    final Message m = msgs[i];
 1399   	    final MessageLiteral mos;
 1400   
 1401   	    try {
 1402   		// if we know the message is too big, don't buffer any of it
 1403   		mos = new MessageLiteral(m,
 1404   				m.getSize() > maxsize ? 0 : maxsize);
 1405   	    } catch (IOException ex) {
 1406   		throw new MessagingException(
 1407   				"IOException while appending messages", ex);
 1408   	    } catch (MessageRemovedException mrex) {
 1409   		continue; // just skip this expunged message
 1410   	    }
 1411   
 1412   	    Date d = m.getReceivedDate(); // retain dates
 1413   	    if (d == null)
 1414   		d = m.getSentDate();
 1415   	    final Date dd = d;
 1416   	    final Flags f = m.getFlags();
 1417   	    doCommand(new ProtocolCommand() {
 1418   		public Object doCommand(IMAPProtocol p)
 1419   			throws ProtocolException {
 1420   		    p.append(fullName, f, dd, mos);
 1421   		    return null;
 1422   		}
 1423   	    });
 1424   	}
 1425       }
 1426   
 1427       /**
 1428        * Append the given messages into this folder.
 1429        * Return array of AppendUID objects containing
 1430        * UIDs of these messages in the destination folder.
 1431        * Each element of the returned array corresponds to
 1432        * an element of the <code>msgs</code> array.  A null
 1433        * element means the server didn't return UID information
 1434        * for the appended message.  <p>
 1435        *
 1436        * Depends on the APPENDUID response code defined by the
 1437        * UIDPLUS extension -
 1438        * <A HREF="http://www.ietf.org/rfc/rfc2359.txt">RFC 2359</A>.
 1439        *
 1440        * @since	JavaMail 1.4
 1441        */
 1442       public synchronized AppendUID[] appendUIDMessages(Message[] msgs)
 1443   				throws MessagingException {
 1444   	checkExists(); // verify that self exists
 1445   
 1446   	// XXX - have to verify that messages are in a different
 1447   	// store (if any) than target folder, otherwise could
 1448   	// deadlock trying to fetch messages on the same connection
 1449   	// we're using for the append.
 1450   
 1451   	int maxsize = ((IMAPStore)store).getAppendBufferSize();
 1452   
 1453   	AppendUID[] uids = new AppendUID[msgs.length];
 1454   	for (int i = 0; i < msgs.length; i++) {
 1455   	    final Message m = msgs[i];
 1456   	    final MessageLiteral mos;
 1457   
 1458   	    try {
 1459   		// if we know the message is too big, don't buffer any of it
 1460   		mos = new MessageLiteral(m,
 1461   				m.getSize() > maxsize ? 0 : maxsize);
 1462   	    } catch (IOException ex) {
 1463   		throw new MessagingException(
 1464   				"IOException while appending messages", ex);
 1465   	    } catch (MessageRemovedException mrex) {
 1466   		continue; // just skip this expunged message
 1467   	    }
 1468   
 1469   	    Date d = m.getReceivedDate(); // retain dates
 1470   	    if (d == null)
 1471   		d = m.getSentDate();
 1472   	    final Date dd = d;
 1473   	    final Flags f = m.getFlags();
 1474   	    AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() {
 1475   		public Object doCommand(IMAPProtocol p)
 1476   			throws ProtocolException {
 1477   		    return p.appenduid(fullName, f, dd, mos);
 1478   		}
 1479   	    });
 1480   	    uids[i] = auid;
 1481   	}
 1482   	return uids;
 1483       }
 1484   
 1485       /**
 1486        * Append the given messages into this folder.
 1487        * Return array of Message objects representing
 1488        * the messages in the destination folder.  Note
 1489        * that the folder must be open.
 1490        * Each element of the returned array corresponds to
 1491        * an element of the <code>msgs</code> array.  A null
 1492        * element means the server didn't return UID information
 1493        * for the appended message. <p>
 1494        *
 1495        * Depends on the APPENDUID response code defined by the
 1496        * UIDPLUS extension -
 1497        * <A HREF="http://www.ietf.org/rfc/rfc2359.txt">RFC 2359</A>.
 1498        *
 1499        * @since	JavaMail 1.4
 1500        */
 1501       public synchronized Message[] addMessages(Message[] msgs)
 1502   				throws MessagingException {
 1503   	checkOpened();
 1504   	Message[] rmsgs = new MimeMessage[msgs.length];
 1505   	AppendUID[] uids = appendUIDMessages(msgs);
 1506   	for (int i = 0; i < uids.length; i++) {
 1507   	    AppendUID auid = uids[i];
 1508   	    if (auid != null) {
 1509   		if (auid.uidvalidity == uidvalidity) {
 1510   		    try {
 1511   			rmsgs[i] = getMessageByUID(auid.uid);
 1512   		    } catch (MessagingException mex) {
 1513   			// ignore errors at this stage
 1514   		    }
 1515   		}
 1516   	    }
 1517   	}
 1518   	return rmsgs;
 1519       }
 1520   
 1521       /**
 1522        * Copy the specified messages from this folder, to the
 1523        * specified destination.
 1524        */
 1525       public synchronized void copyMessages(Message[] msgs, Folder folder)
 1526   			throws MessagingException {
 1527   	checkOpened();
 1528   
 1529   	if (msgs.length == 0) // boundary condition
 1530   	    return;
 1531   
 1532   	// If the destination belongs to our same store, optimize
 1533   	if (folder.getStore() == store) {
 1534   	    synchronized(messageCacheLock) {
 1535   		try {
 1536   		    IMAPProtocol p = getProtocol();
 1537   		    MessageSet[] ms = Utility.toMessageSet(msgs, null);
 1538   		    if (ms == null)
 1539   			throw new MessageRemovedException(
 1540   					"Messages have been removed");
 1541   		    p.copy(ms, folder.getFullName());
 1542   		} catch (CommandFailedException cfx) {
 1543   		    if (cfx.getMessage().indexOf("TRYCREATE") != -1)
 1544   			throw new FolderNotFoundException(
 1545                               folder,
 1546   			    folder.getFullName() + " does not exist"
 1547   			   );
 1548   		    else 
 1549   			throw new MessagingException(cfx.getMessage(), cfx);
 1550   		} catch (ConnectionException cex) {
 1551   		    throw new FolderClosedException(this, cex.getMessage());
 1552   		} catch (ProtocolException pex) {
 1553   		    throw new MessagingException(pex.getMessage(), pex);
 1554   	    	}
 1555   	    }
 1556   	} else // destination is a different store.
 1557   	    super.copyMessages(msgs, folder);
 1558       }
 1559   
 1560       /**
 1561        * Expunge all messages marked as DELETED.
 1562        */
 1563       public synchronized Message[] expunge() throws MessagingException {
 1564   	return expunge(null);
 1565       }
 1566   
 1567       /**
 1568        * Expunge the indicated messages, which must have been marked as DELETED.
 1569        */
 1570       public synchronized Message[] expunge(Message[] msgs)
 1571   				throws MessagingException {
 1572   	checkOpened();
 1573   
 1574   	Vector v = new Vector(); // to collect expunged messages
 1575   
 1576   	if (msgs != null) {
 1577   	    // call fetch to make sure we have all the UIDs
 1578   	    FetchProfile fp = new FetchProfile();
 1579   	    fp.add(UIDFolder.FetchProfileItem.UID);
 1580   	    fetch(msgs, fp);
 1581   	}
 1582   
 1583   	synchronized(messageCacheLock) {
 1584   	    doExpungeNotification = false; // We do this ourselves later
 1585   	    try {
 1586   		IMAPProtocol p = getProtocol();
 1587   		if (msgs != null)
 1588   		    p.uidexpunge(Utility.toUIDSet(msgs));
 1589   		else
 1590   		    p.expunge();
 1591   	    } catch (CommandFailedException cfx) {
 1592   		// expunge not allowed, perhaps due to a permission problem?
 1593   		if (mode != READ_WRITE)
 1594   		    throw new IllegalStateException(
 1595   			"Cannot expunge READ_ONLY folder: " + fullName);
 1596   		else
 1597   		    throw new MessagingException(cfx.getMessage(), cfx);
 1598   	    } catch (ConnectionException cex) {
 1599   		throw new FolderClosedException(this, cex.getMessage());
 1600   	    } catch (ProtocolException pex) {
 1601   		// Bad bad server ..
 1602   		throw new MessagingException(pex.getMessage(), pex);
 1603   	    } finally {
 1604   		doExpungeNotification = true;
 1605   	    }
 1606   
 1607   	    // Cleanup expunged messages and sync messageCache with 
 1608   	    // reality.
 1609   	    for (int i = 0; i < messageCache.size(); ) {
 1610   		IMAPMessage m = (IMAPMessage)messageCache.elementAt(i);
 1611   		if (m.isExpunged()) {
 1612   		    v.addElement(m); // add into vector of expunged messages
 1613   
 1614   		    /* remove this message from the messageCache.
 1615   		     *
 1616   		     * Note that this also causes all succeeding messages
 1617   		     * in the cache to shifted downward in the vector,
 1618   		     * therby decrementing the vector's size. (and hence
 1619   		     * we need to do messageCache.size() at the top of
 1620   		     * this loop.
 1621   		     */
 1622   		    messageCache.removeElementAt(i);
 1623   
 1624   		    /* remove this message from the UIDTable */
 1625   		    if (uidTable != null) {
 1626   			long uid = m.getUID();
 1627   			if (uid != -1)
 1628   			    uidTable.remove(new Long(uid));
 1629   		    }
 1630   		} else {
 1631   		    /* Valid message, sync its message number with 
 1632   		     * its sequence number.
 1633   		     */
 1634   		    m.setMessageNumber(m.getSequenceNumber());
 1635   		    i++; // done; increment index, go check next message
 1636   		}
 1637   	    }
 1638   	}
 1639   
 1640   	// Update 'total'
 1641   	total = messageCache.size();
 1642   
 1643   	// Notify listeners. This time its for real, guys.
 1644   	Message[] rmsgs = new Message[v.size()];
 1645   	v.copyInto(rmsgs);
 1646   	if (rmsgs.length > 0)
 1647   	    notifyMessageRemovedListeners(true, rmsgs);
 1648   	return rmsgs;
 1649       }
 1650   
 1651       /**
 1652        * Search whole folder for messages matching the given term.
 1653        */
 1654       public synchronized Message[] search(SearchTerm term)
 1655   				throws MessagingException {
 1656   	checkOpened();
 1657   
 1658   	try {
 1659   	    Message[] matchMsgs = null;
 1660   
 1661   	    synchronized(messageCacheLock) {
 1662   		int[] matches = getProtocol().search(term);
 1663   		if (matches != null) {
 1664   		    matchMsgs = new IMAPMessage[matches.length];
 1665   		    // Map seq-numbers into actual Messages.
 1666   		    for (int i = 0; i < matches.length; i++)	
 1667   			matchMsgs[i] = getMessageBySeqNumber(matches[i]);
 1668   		}
 1669   	    }
 1670   	    return matchMsgs;
 1671   
 1672   	} catch (CommandFailedException cfx) {
 1673   	    // unsupported charset or search criterion
 1674   	    return super.search(term);
 1675   	} catch (SearchException sex) {
 1676   	    // too complex for IMAP
 1677   	    return super.search(term);
 1678   	} catch (ConnectionException cex) {
 1679   	    throw new FolderClosedException(this, cex.getMessage());
 1680   	} catch (ProtocolException pex) {
 1681   	    // bug in our IMAP layer ?
 1682   	    throw new MessagingException(pex.getMessage(), pex);
 1683   	}
 1684       }
 1685   
 1686       /**
 1687        * Search the folder for messages matching the given term. Returns
 1688        * array of matching messages. Returns an empty array if no matching
 1689        * messages are found.
 1690        */
 1691       public synchronized Message[] search(SearchTerm term, Message[] msgs) 
 1692   			throws MessagingException {
 1693   	checkOpened();
 1694   
 1695   	if (msgs.length == 0)
 1696   	    // need to return an empty array (not null!)
 1697   	    return msgs;
 1698   
 1699   	try {
 1700   	    Message[] matchMsgs = null;
 1701   
 1702   	    synchronized(messageCacheLock) {
 1703   		IMAPProtocol p = getProtocol();
 1704   		MessageSet[] ms = Utility.toMessageSet(msgs, null);
 1705   		if (ms == null)
 1706   		    throw new MessageRemovedException(
 1707   					"Messages have been removed");
 1708   		int[] matches = p.search(ms, term);
 1709   		if (matches != null) {
 1710   		    matchMsgs = new IMAPMessage[matches.length];
 1711   		    for (int i = 0; i < matches.length; i++)	
 1712   			matchMsgs[i] = getMessageBySeqNumber(matches[i]);
 1713   		}
 1714   	    }
 1715   	    return matchMsgs;
 1716   
 1717   	} catch (CommandFailedException cfx) {
 1718   	    // unsupported charset or search criterion
 1719   	    return super.search(term, msgs);
 1720   	} catch (SearchException sex) {
 1721   	    // too complex for IMAP
 1722   	    return super.search(term, msgs);
 1723   	} catch (ConnectionException cex) {
 1724   	    throw new FolderClosedException(this, cex.getMessage());
 1725   	} catch (ProtocolException pex) {
 1726   	    // bug in our IMAP layer ?
 1727   	    throw new MessagingException(pex.getMessage(), pex);
 1728   	}
 1729       }
 1730   
 1731       /***********************************************************
 1732        *		UIDFolder interface methods
 1733        **********************************************************/
 1734   
 1735       /**
 1736        * Returns the UIDValidity for this folder.
 1737        */
 1738       public synchronized long getUIDValidity() throws MessagingException {
 1739   	if (opened) // we already have this information
 1740   	    return uidvalidity;
 1741   
 1742           IMAPProtocol p = null;
 1743           Status status = null;
 1744   
 1745   	try {
 1746   	    p = getStoreProtocol();	// XXX
 1747   	    String[] item = { "UIDVALIDITY" };
 1748   	    status = p.status(fullName, item);
 1749   	} catch (BadCommandException bex) {
 1750   	    // Probably a RFC1730 server
 1751   	    throw new MessagingException("Cannot obtain UIDValidity", bex);
 1752   	} catch (ConnectionException cex) {
 1753               // Oops, the store or folder died on us.
 1754               throwClosedException(cex);
 1755   	} catch (ProtocolException pex) {
 1756   	    throw new MessagingException(pex.getMessage(), pex);
 1757   	} finally {
 1758               releaseStoreProtocol(p);
 1759           }
 1760   
 1761   	return status.uidvalidity;
 1762       }
 1763   
 1764       /**
 1765        * Returns the predicted UID that will be assigned to the
 1766        * next message that is appended to this folder.
 1767        * If the folder is closed, the STATUS command is used to
 1768        * retrieve this value.  If the folder is open, the value
 1769        * returned from the SELECT or EXAMINE command is returned.
 1770        * Note that messages may have been appended to the folder
 1771        * while it was open and thus this value may be out of
 1772        * date. <p>
 1773        *
 1774        * Servers implementing RFC2060 likely won't return this value
 1775        * when a folder is opened.  Servers implementing RFC3501
 1776        * should return this value when a folder is opened. <p>
 1777        *
 1778        * @return	the UIDNEXT value, or -1 if unknown
 1779        * @since	JavaMail 1.3.3
 1780        */
 1781       // Not a UIDFolder method, but still useful
 1782       public synchronized long getUIDNext() throws MessagingException {
 1783   	if (opened) // we already have this information
 1784   	    return uidnext;
 1785   
 1786           IMAPProtocol p = null;
 1787           Status status = null;
 1788   
 1789   	try {
 1790   	    p = getStoreProtocol();	// XXX
 1791   	    String[] item = { "UIDNEXT" };
 1792   	    status = p.status(fullName, item);
 1793   	} catch (BadCommandException bex) {
 1794   	    // Probably a RFC1730 server
 1795   	    throw new MessagingException("Cannot obtain UIDNext", bex);
 1796   	} catch (ConnectionException cex) {
 1797               // Oops, the store or folder died on us.
 1798               throwClosedException(cex);
 1799   	} catch (ProtocolException pex) {
 1800   	    throw new MessagingException(pex.getMessage(), pex);
 1801   	} finally {
 1802               releaseStoreProtocol(p);
 1803           }
 1804   
 1805   	return status.uidnext;
 1806       }
 1807   
 1808       /**
 1809        * Get the Message corresponding to the given UID.
 1810        * If no such message exists, <code> null </code> is returned.
 1811        */
 1812       public synchronized Message getMessageByUID(long uid) 
 1813   			throws MessagingException {
 1814   	checkOpened(); // insure folder is open
 1815   
 1816   	IMAPMessage m = null;
 1817   
 1818   	try {
 1819   	    synchronized(messageCacheLock) {
 1820   		Long l = new Long(uid);
 1821   
 1822   		if (uidTable != null) {
 1823   		    // Check in uidTable
 1824   		    m = (IMAPMessage)uidTable.get(l);
 1825   		    if (m != null) // found it
 1826   			return m;
 1827   		} else
 1828   		    uidTable = new Hashtable();
 1829   
 1830   		// Check with the server
 1831   		// Issue UID FETCH command
 1832   		UID u = getProtocol().fetchSequenceNumber(uid);
 1833   
 1834   		if (u != null && u.seqnum <= total) { // Valid UID 
 1835   		    m = getMessageBySeqNumber(u.seqnum);
 1836   		    m.setUID(u.uid); // set this message's UID ..
 1837   		    // .. and put this into the hashtable
 1838   		    uidTable.put(l, m);
 1839   		}
 1840   	    }
 1841   	} catch(ConnectionException cex) {
 1842   	    throw new FolderClosedException(this, cex.getMessage());
 1843   	} catch (ProtocolException pex) {
 1844   	    throw new MessagingException(pex.getMessage(), pex);
 1845   	}
 1846   
 1847   	return m;
 1848       }
 1849   
 1850       /**
 1851        * Get the Messages specified by the given range. <p>
 1852        * Returns Message objects for all valid messages in this range.
 1853        * Returns an empty array if no messages are found.
 1854        */
 1855       public synchronized Message[] getMessagesByUID(long start, long end) 
 1856   			throws MessagingException {
 1857   	checkOpened(); // insure that folder is open
 1858   
 1859   	Message[] msgs; // array of messages to be returned
 1860   
 1861   	try {
 1862   	    synchronized(messageCacheLock) {
 1863   		if (uidTable == null)
 1864   		    uidTable = new Hashtable();
 1865   
 1866   		// Issue UID FETCH for given range
 1867   		UID[] ua = getProtocol().fetchSequenceNumbers(start, end);
 1868   
 1869   		msgs = new Message[ua.length];
 1870   		IMAPMessage m;
 1871   		// NOTE: Below must be within messageCacheLock region
 1872   		for (int i = 0; i < ua.length; i++) {
 1873   		    m = getMessageBySeqNumber(ua[i].seqnum);
 1874   		    m.setUID(ua[i].uid);
 1875   		    msgs[i] = m;
 1876   		    uidTable.put(new Long(ua[i].uid), m);
 1877   		}
 1878   	    }
 1879   	} catch(ConnectionException cex) {
 1880   	    throw new FolderClosedException(this, cex.getMessage());
 1881   	} catch (ProtocolException pex) {
 1882   	    throw new MessagingException(pex.getMessage(), pex);
 1883   	}
 1884   
 1885   	return msgs;
 1886       }
 1887   
 1888       /**
 1889        * Get the Messages specified by the given array. <p>
 1890        *
 1891        * <code>uids.length()</code> elements are returned.
 1892        * If any UID in the array is invalid, a <code>null</code> entry
 1893        * is returned for that element.
 1894        */
 1895       public synchronized Message[] getMessagesByUID(long[] uids)
 1896   			throws MessagingException {
 1897   	checkOpened(); // insure that folder is open
 1898   
 1899   	try {
 1900   	    synchronized(messageCacheLock) {
 1901   		long[] unavailUids = uids;
 1902   		if (uidTable != null) {
 1903   		    Vector v = new Vector(); // to collect unavailable UIDs
 1904   		    Long l;
 1905   		    for (int i = 0; i < uids.length; i++) {
 1906   			if (!uidTable.containsKey(l = new Long(uids[i])))
 1907   			    // This UID has not been loaded yet.
 1908   			    v.addElement(l);
 1909   		    }
 1910   
 1911   		    int vsize = v.size();
 1912   		    unavailUids = new long[vsize];
 1913   		    for (int i = 0; i < vsize; i++)
 1914   			unavailUids[i] = ((Long)v.elementAt(i)).longValue();
 1915   		} else
 1916   		    uidTable = new Hashtable();
 1917   
 1918   		if (unavailUids.length > 0) {
 1919   		    // Issue UID FETCH request for given uids
 1920   		    UID[] ua = getProtocol().fetchSequenceNumbers(unavailUids);
 1921   		    IMAPMessage m;
 1922   		    for (int i = 0; i < ua.length; i++) {
 1923   			m = getMessageBySeqNumber(ua[i].seqnum);
 1924   			m.setUID(ua[i].uid);
 1925   			uidTable.put(new Long(ua[i].uid), m);
 1926   		    }
 1927   		}
 1928   
 1929   		// Return array of size = uids.length
 1930   		Message[] msgs = new Message[uids.length];
 1931   		for (int i = 0; i < uids.length; i++)
 1932   		    msgs[i] = (Message)uidTable.get(new Long(uids[i]));
 1933   		return msgs;
 1934   	    }
 1935   	} catch(ConnectionException cex) {
 1936   	    throw new FolderClosedException(this, cex.getMessage());
 1937   	} catch (ProtocolException pex) {
 1938   	    throw new MessagingException(pex.getMessage(), pex);
 1939   	}
 1940       }
 1941   
 1942       /**
 1943        * Get the UID for the specified message.
 1944        */
 1945       public synchronized long getUID(Message message) 
 1946   			throws MessagingException {
 1947   	if (message.getFolder() != this)
 1948   	    throw new NoSuchElementException(
 1949   		"Message does not belong to this folder");
 1950   
 1951   	checkOpened(); // insure that folder is open
 1952   
 1953   	IMAPMessage m = (IMAPMessage)message;
 1954   	// If the message already knows its UID, great ..
 1955   	long uid;
 1956   	if ((uid = m.getUID()) != -1)
 1957   	    return uid;
 1958   
 1959   	synchronized(messageCacheLock) { // Acquire Lock
 1960   	    try {
 1961   		IMAPProtocol p = getProtocol();
 1962   		m.checkExpunged(); // insure that message is not expunged
 1963   		UID u = p.fetchUID(m.getSequenceNumber());
 1964   
 1965   		if (u != null) {
 1966   		    uid = u.uid;
 1967   		    m.setUID(uid); // set message's UID
 1968   
 1969   		    // insert this message into uidTable
 1970   		    if (uidTable == null)
 1971   			uidTable = new Hashtable();
 1972   		    uidTable.put(new Long(uid), m);
 1973   		}
 1974   	    } catch (ConnectionException cex) {
 1975   		throw new FolderClosedException(this, cex.getMessage());
 1976   	    } catch (ProtocolException pex) {
 1977   		throw new MessagingException(pex.getMessage(), pex);
 1978   	    }
 1979   	}
 1980   
 1981   	return uid;
 1982       }
 1983   
 1984       /**
 1985        * Get the quotas for the quotaroot associated with this
 1986        * folder.  Note that many folders may have the same quotaroot.
 1987        * Quotas are controlled on the basis of a quotaroot, not
 1988        * (necessarily) a folder.  The relationship between folders
 1989        * and quotaroots depends on the IMAP server.  Some servers
 1990        * might implement a single quotaroot for all folders owned by
 1991        * a user.  Other servers might implement a separate quotaroot
 1992        * for each folder.  A single folder can even have multiple
 1993        * quotaroots, perhaps controlling quotas for different
 1994        * resources.
 1995        *
 1996        * @return	array of Quota objects for the quotaroots associated with
 1997        *		this folder
 1998        * @exception MessagingException	if the server doesn't support the
 1999        *					QUOTA extension
 2000        */
 2001       public Quota[] getQuota() throws MessagingException {
 2002   	return (Quota[])doOptionalCommand("QUOTA not supported",
 2003   	    new ProtocolCommand() {
 2004   		public Object doCommand(IMAPProtocol p)
 2005   			throws ProtocolException {
 2006   		    return p.getQuotaRoot(fullName);
 2007   		}
 2008   	    });
 2009       }
 2010   
 2011       /**
 2012        * Set the quotas for the quotaroot specified in the quota argument.
 2013        * Typically this will be one of the quotaroots associated with this
 2014        * folder, as obtained from the <code>getQuota</code> method, but it
 2015        * need not be.
 2016        *
 2017        * @param	quota	the quota to set
 2018        * @exception MessagingException	if the server doesn't support the
 2019        *					QUOTA extension
 2020        */
 2021       public void setQuota(final Quota quota) throws MessagingException {
 2022   	doOptionalCommand("QUOTA not supported",
 2023   	    new ProtocolCommand() {
 2024   		public Object doCommand(IMAPProtocol p)
 2025   			throws ProtocolException {
 2026   		    p.setQuota(quota);
 2027   		    return null;
 2028   		}
 2029   	    });
 2030       }
 2031   
 2032       /**
 2033        * Get the access control list entries for this folder.
 2034        *
 2035        * @return	array of access control list entries
 2036        * @exception MessagingException	if the server doesn't support the
 2037        *					ACL extension
 2038        */
 2039       public ACL[] getACL() throws MessagingException {
 2040   	return (ACL[])doOptionalCommand("ACL not supported",
 2041   	    new ProtocolCommand() {
 2042   		public Object doCommand(IMAPProtocol p)
 2043   			throws ProtocolException {
 2044   		    return p.getACL(fullName);
 2045   		}
 2046   	    });
 2047       }
 2048   
 2049       /**
 2050        * Add an access control list entry to the access control list
 2051        * for this folder.
 2052        *
 2053        * @param	acl	the access control list entry to add
 2054        * @exception MessagingException	if the server doesn't support the
 2055        *					ACL extension
 2056        */
 2057       public void addACL(ACL acl) throws MessagingException {
 2058   	setACL(acl, '\0');
 2059       }
 2060   
 2061       /**
 2062        * Remove any access control list entry for the given identifier
 2063        * from the access control list for this folder.
 2064        *
 2065        * @param	name	the identifier for which to remove all ACL entries
 2066        * @exception MessagingException	if the server doesn't support the
 2067        *					ACL extension
 2068        */
 2069       public void removeACL(final String name) throws MessagingException {
 2070   	doOptionalCommand("ACL not supported",
 2071   	    new ProtocolCommand() {
 2072   		public Object doCommand(IMAPProtocol p)
 2073   			throws ProtocolException {
 2074   		    p.deleteACL(fullName, name);
 2075   		    return null;
 2076   		}
 2077   	    });
 2078       }
 2079   
 2080       /**
 2081        * Add the rights specified in the ACL to the entry for the
 2082        * identifier specified in the ACL.  If an entry for the identifier
 2083        * doesn't already exist, add one.
 2084        *
 2085        * @param	acl	the identifer and rights to add
 2086        * @exception MessagingException	if the server doesn't support the
 2087        *					ACL extension
 2088        */
 2089       public void addRights(ACL acl) throws MessagingException {
 2090   	setACL(acl, '+');
 2091       }
 2092   
 2093       /**
 2094        * Remove the rights specified in the ACL from the entry for the
 2095        * identifier specified in the ACL.
 2096        *
 2097        * @param	acl	the identifer and rights to remove
 2098        * @exception MessagingException	if the server doesn't support the
 2099        *					ACL extension
 2100        */
 2101       public void removeRights(ACL acl) throws MessagingException {
 2102   	setACL(acl, '-');
 2103       }
 2104   
 2105       /**
 2106        * Get all the rights that may be allowed to the given identifier.
 2107        * Rights are grouped per RFC 2086 and each group is returned as an
 2108        * element of the array.  The first element of the array is the set
 2109        * of rights that are always granted to the identifier.  Later
 2110        * elements are rights that may be optionally granted to the
 2111        * identifier. <p>
 2112        *
 2113        * Note that this method lists the rights that it is possible to
 2114        * assign to the given identifier, <em>not</em> the rights that are
 2115        * actually granted to the given identifier.  For the latter, see
 2116        * the <code>getACL</code> method.
 2117        *
 2118        * @param	name	the identifier to list rights for
 2119        * @return		array of Rights objects representing possible
 2120        *			rights for the identifier
 2121        * @exception MessagingException	if the server doesn't support the
 2122        *					ACL extension
 2123        */
 2124       public Rights[] listRights(final String name) throws MessagingException {
 2125   	return (Rights[])doOptionalCommand("ACL not supported",
 2126   	    new ProtocolCommand() {
 2127   		public Object doCommand(IMAPProtocol p)
 2128   			throws ProtocolException {
 2129   		    return p.listRights(fullName, name);
 2130   		}
 2131   	    });
 2132       }
 2133   
 2134       /**
 2135        * Get the rights allowed to the currently authenticated user.
 2136        *
 2137        * @return	the rights granted to the current user
 2138        * @exception MessagingException	if the server doesn't support the
 2139        *					ACL extension
 2140        */
 2141       public Rights myRights() throws MessagingException {
 2142   	return (Rights)doOptionalCommand("ACL not supported",
 2143   	    new ProtocolCommand() {
 2144   		public Object doCommand(IMAPProtocol p)
 2145   			throws ProtocolException {
 2146   		    return p.myRights(fullName);
 2147   		}
 2148   	    });
 2149       }
 2150   
 2151       private void setACL(final ACL acl, final char mod)
 2152   				throws MessagingException {
 2153   	doOptionalCommand("ACL not supported",
 2154   	    new ProtocolCommand() {
 2155   		public Object doCommand(IMAPProtocol p)
 2156   			throws ProtocolException {
 2157   		    p.setACL(fullName, mod, acl);
 2158   		    return null;
 2159   		}
 2160   	    });
 2161       }
 2162   
 2163       /**
 2164        * Get the attributes that the IMAP server returns with the
 2165        * LIST response.
 2166        *
 2167        * @since	JavaMail 1.3.3
 2168        */
 2169       public String[] getAttributes() throws MessagingException {
 2170   	if (attributes == null)
 2171   	    exists();		// do a LIST to set the attributes
 2172   	return (String[])(attributes.clone());
 2173       }
 2174   
 2175       /**
 2176        * Use the IMAP IDLE command (see
 2177        * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>),
 2178        * if supported by the server, to enter idle mode so that the server
 2179        * can send unsolicited notifications of new messages arriving, etc.
 2180        * without the need for the client to constantly poll the server.
 2181        * Use an appropriate listener to be notified of new messages or
 2182        * other events.  When another thread (e.g., the listener thread)
 2183        * needs to issue an IMAP comand for this folder, the idle mode will
 2184        * be terminated and this method will return.  Typically the caller
 2185        * will invoke this method in a loop. <p>
 2186        *
 2187        * The mail.imap.minidletime property enforces a minimum delay
 2188        * before returning from this method, to ensure that other threads
 2189        * have a chance to issue commands before the caller invokes this
 2190        * method again.  The default delay is 10 milliseconds.
 2191        *
 2192        * @exception MessagingException	if the server doesn't support the
 2193        *					IDLE extension
 2194        * @exception IllegalStateException	if the folder isn't open
 2195        *
 2196        * @since	JavaMail 1.4.1
 2197        */
 2198       public void idle() throws MessagingException {
 2199   	// ASSERT: Must NOT be called with this folder's
 2200   	// synchronization lock held.
 2201   	assert !Thread.holdsLock(this);
 2202   	synchronized(this) {
 2203   	    checkOpened();
 2204   	    Boolean started = (Boolean)doOptionalCommand("IDLE not supported",
 2205   		new ProtocolCommand() {
 2206   		    public Object doCommand(IMAPProtocol p)
 2207   			    throws ProtocolException {
 2208   			if (idleState == RUNNING) {
 2209   			    p.idleStart();
 2210   			    idleState = IDLE;
 2211   			    return Boolean.TRUE;
 2212   			} else {
 2213   			    // some other thread must be running the IDLE
 2214   			    // command, we'll just wait for it to finish
 2215   			    // without aborting it ourselves
 2216   			    try {
 2217   				// give up lock and wait to be not idle
 2218   				messageCacheLock.wait();
 2219   			    } catch (InterruptedException ex) { }
 2220   			    return Boolean.FALSE;
 2221   			}
 2222   		    }
 2223   		});
 2224   	    if (!started.booleanValue())
 2225   		return;
 2226   	}
 2227   
 2228   	/*
 2229   	 * We gave up the folder lock so that other threads
 2230   	 * can get into the folder far enough to see that we're
 2231   	 * in IDLE and abort the IDLE.
 2232   	 *
 2233   	 * Now we read responses from the IDLE command, especially
 2234   	 * including unsolicited notifications from the server.
 2235   	 * We don't hold the messageCacheLock while reading because
 2236   	 * it protects the idleState and other threads need to be
 2237   	 * able to examine the state.
 2238   	 *
 2239   	 * We hold the messageCacheLock while processing the
 2240   	 * responses so that we can update the number of messages
 2241   	 * in the folder (for example).
 2242   	 */
 2243   	for (;;) {
 2244   	    Response r = protocol.readIdleResponse();
 2245   	    try {
 2246   		synchronized (messageCacheLock) {
 2247   		    if (r == null || protocol == null ||
 2248   			    !protocol.processIdleResponse(r)) {
 2249   			idleState = RUNNING;
 2250   			messageCacheLock.notifyAll();
 2251   			break;
 2252   		    }
 2253   		}
 2254   	    } catch (ConnectionException cex) {
 2255   		// Oops, the store or folder died on us.
 2256   		throwClosedException(cex);
 2257   	    } catch (ProtocolException pex) {
 2258   		throw new MessagingException(pex.getMessage(), pex);
 2259   	    }
 2260   	}
 2261   
 2262   	/*
 2263   	 * Enforce a minimum delay to give time to threads
 2264   	 * processing the responses that came in while we
 2265   	 * were idle.
 2266   	 */
 2267   	int minidle = ((IMAPStore)store).getMinIdleTime();
 2268   	if (minidle > 0) {
 2269   	    try {
 2270   		Thread.sleep(minidle);
 2271   	    } catch (InterruptedException ex) { }
 2272   	}
 2273       }
 2274   
 2275       /*
 2276        * If an IDLE command is in progress, abort it if necessary,
 2277        * and wait until it completes.
 2278        * ASSERT: Must be called with the message cache lock held.
 2279        */
 2280       void waitIfIdle() throws ProtocolException {
 2281   	assert Thread.holdsLock(messageCacheLock);
 2282   	while (idleState != RUNNING) {
 2283   	    if (idleState == IDLE) {
 2284   		protocol.idleAbort();
 2285   		idleState = ABORTING;
 2286   	    }
 2287   	    try {
 2288   		// give up lock and wait to be not idle
 2289   		messageCacheLock.wait();
 2290   	    } catch (InterruptedException ex) { }
 2291   	}
 2292       }
 2293   
 2294       /**
 2295        * The response handler. This is the callback routine that is 
 2296        * invoked by the protocol layer.
 2297        */
 2298       /*
 2299        * ASSERT: This method must be called only when holding the
 2300        * messageCacheLock.
 2301        * ASSERT: This method must *not* invoke any other method that
 2302        * might grab the 'folder' lock or 'message' lock (i.e., any 
 2303        * synchronized methods on IMAPFolder or IMAPMessage)
 2304        * since that will result in violating the locking hierarchy.
 2305        */
 2306       public void handleResponse(Response r) {
 2307   	assert Thread.holdsLock(messageCacheLock);
 2308   
 2309   	/*
 2310   	 * First, delegate possible ALERT or notification to the Store.
 2311   	 */
 2312   	if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
 2313   	    ((IMAPStore)store).handleResponseCode(r);
 2314   
 2315   	/*
 2316   	 * Now check whether this is a BYE or OK response and
 2317   	 * handle appropriately.
 2318   	 */
 2319   	if (r.isBYE()) {
 2320   	    if (opened)		// XXX - accessed without holding folder lock
 2321   		cleanup(false); 
 2322   	    return;
 2323   	} else if (r.isOK()) {
 2324   	    return;
 2325   	} else if (!r.isUnTagged()) {
 2326   	    return;	// XXX - should never happen
 2327   	}
 2328   
 2329   	/* Now check whether this is an IMAP specific response */
 2330   	if (!(r instanceof IMAPResponse)) {
 2331   	    // Probably a bug in our code !
 2332   	    // XXX - should be an assert
 2333   	    out.println("UNEXPECTED RESPONSE : " + r.toString());
 2334   	    out.println("CONTACT javamail@sun.com");
 2335   	    return;
 2336   	}
 2337   
 2338   	IMAPResponse ir = (IMAPResponse)r;
 2339   
 2340   	if (ir.keyEquals("EXISTS")) { // EXISTS
 2341   	    int exists = ir.getNumber();
 2342   	    if (exists <= realTotal) 
 2343   		// Could be the EXISTS following EXPUNGE, ignore 'em
 2344   		return;
 2345   	
 2346   	    int count = exists - realTotal; // number of new messages
 2347   	    Message[] msgs = new Message[count];
 2348   
 2349   	    // Add 'count' new IMAPMessage objects into the messageCache
 2350   	    for (int i = 0; i < count; i++) {
 2351   		// Note that as a side-effect, we also increment
 2352   		// total & realTotal
 2353   		IMAPMessage msg = new IMAPMessage(this, ++total, ++realTotal);
 2354   		msgs[i] = msg;
 2355   		messageCache.addElement(msg);
 2356   	    }
 2357   
 2358   	    // Notify listeners.
 2359   	    notifyMessageAddedListeners(msgs);
 2360   
 2361   	} else if (ir.keyEquals("EXPUNGE")) {
 2362   	    // EXPUNGE response.
 2363   
 2364   	    IMAPMessage msg = getMessageBySeqNumber(ir.getNumber());
 2365   	    msg.setExpunged(true); // mark this message expunged.
 2366   
 2367   	    // Renumber the cache, starting from just beyond 
 2368   	    // the expunged message.
 2369   	    for (int i = msg.getMessageNumber(); i < total; i++) {
 2370   		// Note that 'i' actually indexes the message
 2371   		// beyond the expunged message.
 2372   		IMAPMessage m = (IMAPMessage)messageCache.elementAt(i);
 2373   		if (m.isExpunged()) // an expunged message, skip
 2374   		    continue;
 2375   
 2376   		// Decrement this message's seqnum
 2377   		m.setSequenceNumber(m.getSequenceNumber() - 1);
 2378   	    } // Whew, done.
 2379   
 2380   	    // decrement 'realTotal'; but leave 'total' unchanged
 2381   	    realTotal--;
 2382   
 2383   	    if (doExpungeNotification) {
 2384   		// Do the notification here.
 2385   		Message[] msgs = {msg};
 2386   		notifyMessageRemovedListeners(false, msgs);
 2387   	    }
 2388   
 2389   	} else if (ir.keyEquals("FETCH")) {
 2390   	    // The only unsolicited FETCH response that makes sense
 2391   	    // to me (for now) is FLAGS updates. Ignore any other junk.
 2392   	    assert ir instanceof FetchResponse : "!ir instanceof FetchResponse";
 2393   	    FetchResponse f = (FetchResponse)ir;
 2394   	    // Get FLAGS response, if present
 2395   	    Flags flags = (Flags)f.getItem(Flags.class);
 2396   
 2397   	    if (flags != null) {
 2398   		IMAPMessage msg = getMessageBySeqNumber(f.getNumber());
 2399   		if (msg != null) {	// should always be true
 2400   		    msg._setFlags(flags);
 2401   		    notifyMessageChangedListeners(
 2402   			    MessageChangedEvent.FLAGS_CHANGED, msg);
 2403   		}
 2404   	    }
 2405   
 2406   	} else if (ir.keyEquals("RECENT")) {
 2407   	    // update 'recent'
 2408   	    recent = ir.getNumber();
 2409   	}
 2410       }
 2411   
 2412       /**
 2413        * Handle the given array of Responses.
 2414        *
 2415        * ASSERT: This method must be called only when holding the
 2416        * 	messageCacheLock
 2417        */
 2418       void handleResponses(Response[] r) {
 2419   	for (int i = 0; i < r.length; i++) {
 2420   	    if (r[i] != null)
 2421   		handleResponse(r[i]);
 2422   	}
 2423       }
 2424   
 2425       /**
 2426        * Get this folder's Store's protocol connection.
 2427        *
 2428        * When acquiring a store protocol object, it is important to
 2429        * use the following steps:
 2430        *
 2431        *     IMAPProtocol p = null;
 2432        *     try {
 2433        *         p = getStoreProtocol();
 2434        *         // perform the command
 2435        *     } catch (WhateverException ex) {
 2436        *         // handle it
 2437        *     } finally {
 2438        *         releaseStoreProtocol(p);
 2439        *     }
 2440        */
 2441       protected synchronized IMAPProtocol getStoreProtocol() 
 2442               throws ProtocolException {
 2443   	if (connectionPoolDebug) {
 2444   	    out.println("DEBUG: getStoreProtocol() - " + 
 2445   		"borrowing a connection");
 2446   	}
 2447   	return ((IMAPStore)store).getStoreProtocol();
 2448       }
 2449   
 2450       /**
 2451        * Throw the appropriate 'closed' exception.
 2452        */
 2453       private synchronized void throwClosedException(ConnectionException cex) 
 2454               throws FolderClosedException, StoreClosedException {
 2455   	// If it's the folder's protocol object, throw a FolderClosedException;
 2456   	// otherwise, throw a StoreClosedException.
 2457   	// If a command has failed because the connection is closed,
 2458   	// the folder will have already been forced closed by the
 2459   	// time we get here and our protocol object will have been
 2460   	// released, so if we no longer have a protocol object we base
 2461   	// this decision on whether we *think* the folder is open.
 2462   	if ((protocol != null && cex.getProtocol() == protocol) ||
 2463   		(protocol == null && !reallyClosed))
 2464               throw new FolderClosedException(this, cex.getMessage());
 2465           else
 2466               throw new StoreClosedException(store, cex.getMessage());
 2467       }
 2468   
 2469       /**
 2470        * Return the IMAPProtocol object for this folder. <p>
 2471        *
 2472        * This method will block if necessary to wait for an IDLE
 2473        * command to finish.
 2474        *
 2475        * @return	the IMAPProtocol object used when the folder is open
 2476        */
 2477       private IMAPProtocol getProtocol() throws ProtocolException {
 2478   	assert Thread.holdsLock(messageCacheLock);
 2479   	waitIfIdle();
 2480           return protocol;
 2481       }
 2482   
 2483       /**
 2484        * A simple interface for user-defined IMAP protocol commands.
 2485        */
 2486       public static interface ProtocolCommand {
 2487   	/**
 2488   	 * Execute the user-defined command using the supplied IMAPProtocol
 2489   	 * object.
 2490   	 */
 2491   	public Object doCommand(IMAPProtocol protocol) throws ProtocolException;
 2492       }
 2493   
 2494       /**
 2495        * Execute a user-supplied IMAP command.  The command is executed
 2496        * in the appropriate context with the necessary locks held and
 2497        * using the appropriate <code>IMAPProtocol</code> object. <p>
 2498        *
 2499        * This method returns whatever the <code>ProtocolCommand</code>
 2500        * object's <code>doCommand</code> method returns.  If the
 2501        * <code>doCommand</code> method throws a <code>ConnectionException</code>
 2502        * it is translated into a <code>StoreClosedException</code> or
 2503        * <code>FolderClosedException</code> as appropriate.  If the
 2504        * <code>doCommand</code> method throws a <code>ProtocolException</code>
 2505        * it is translated into a <code>MessagingException</code>. <p>
 2506        *
 2507        * The following example shows how to execute the IMAP NOOP command.
 2508        * Executing more complex IMAP commands requires intimate knowledge
 2509        * of the <code>com.sun.mail.iap</code> and
 2510        * <code>com.sun.mail.imap.protocol</code> packages, best acquired by
 2511        * reading the source code. <p>
 2512        *
 2513        * <blockquote><pre>
 2514        * import com.sun.mail.iap.*;
 2515        * import com.sun.mail.imap.*;
 2516        * import com.sun.mail.imap.protocol.*;
 2517        *
 2518        * ...
 2519        *
 2520        * IMAPFolder f = (IMAPFolder)folder;
 2521        * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
 2522        *	public Object doCommand(IMAPProtocol p)
 2523        *			throws ProtocolException {
 2524        *	    p.simpleCommand("NOOP", null);
 2525        *	    return null;
 2526        *	}
 2527        * });
 2528        * </pre></blockquote>
 2529        * <p>
 2530        *
 2531        * Here's a more complex example showing how to use the proposed
 2532        * IMAP SORT extension: <p>
 2533        *
 2534        * <pre><blockquote>
 2535        * import com.sun.mail.iap.*;
 2536        * import com.sun.mail.imap.*;
 2537        * import com.sun.mail.imap.protocol.*;
 2538        *
 2539        * ...
 2540        *
 2541        * IMAPFolder f = (IMAPFolder)folder;
 2542        * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
 2543        *	public Object doCommand(IMAPProtocol p)
 2544        *			throws ProtocolException {
 2545        *	    // Issue command
 2546        *	    Argument args = new Argument();
 2547        *	    Argument list = new Argument();
 2548        *	    list.writeString("SUBJECT");
 2549        *	    args.writeArgument(list);
 2550        *	    args.writeString("UTF-8");
 2551        *	    args.writeString("ALL");
 2552        *	    Response[] r = p.command("SORT", args);
 2553        *	    Response response = r[r.length-1];
 2554        *
 2555        *	    // Grab response
 2556        *	    Vector v = new Vector();
 2557        *	    if (response.isOK()) { // command succesful 
 2558        *		for (int i = 0, len = r.length; i < len; i++) {
 2559        *		    if (!(r[i] instanceof IMAPResponse))
 2560        *			continue;
 2561        *
 2562        *		    IMAPResponse ir = (IMAPResponse)r[i];
 2563        *		    if (ir.keyEquals("SORT")) {
 2564        *			String num;
 2565        *			while ((num = ir.readAtomString()) != null)
 2566        *			    System.out.println(num);
 2567        *			r[i] = null;
 2568        *		    }
 2569        *		}
 2570        *	    }
 2571        *
 2572        *	    // dispatch remaining untagged responses
 2573        *	    p.notifyResponseHandlers(r);
 2574        *	    p.handleResult(response);
 2575        *
 2576        *	    return null;
 2577        *	}
 2578        * });
 2579        * </pre></blockquote>
 2580        */
 2581       public Object doCommand(ProtocolCommand cmd) throws MessagingException {
 2582   	try {
 2583   	    return doProtocolCommand(cmd);
 2584   	} catch (ConnectionException cex) {
 2585               // Oops, the store or folder died on us.
 2586               throwClosedException(cex);
 2587   	} catch (ProtocolException pex) {
 2588   	    throw new MessagingException(pex.getMessage(), pex);
 2589   	}
 2590   	return null;
 2591       }
 2592   
 2593       public Object doOptionalCommand(String err, ProtocolCommand cmd)
 2594   				throws MessagingException {
 2595   	try {
 2596   	    return doProtocolCommand(cmd);
 2597   	} catch (BadCommandException bex) {
 2598   	    throw new MessagingException(err, bex);
 2599   	} catch (ConnectionException cex) {
 2600               // Oops, the store or folder died on us.
 2601               throwClosedException(cex);
 2602   	} catch (ProtocolException pex) {
 2603   	    throw new MessagingException(pex.getMessage(), pex);
 2604   	}
 2605   	return null;
 2606       }
 2607   
 2608       public Object doCommandIgnoreFailure(ProtocolCommand cmd)
 2609   				throws MessagingException {
 2610   	try {
 2611   	    return doProtocolCommand(cmd);
 2612   	} catch (CommandFailedException cfx) {
 2613   	    return null;
 2614   	} catch (ConnectionException cex) {
 2615               // Oops, the store or folder died on us.
 2616               throwClosedException(cex);
 2617   	} catch (ProtocolException pex) {
 2618   	    throw new MessagingException(pex.getMessage(), pex);
 2619   	}
 2620   	return null;
 2621       }
 2622   
 2623       protected Object doProtocolCommand(ProtocolCommand cmd)
 2624   				throws ProtocolException {
 2625   	synchronized (this) {
 2626   	    // XXX - why does "&& !hasSeparateStoreConnection" make sense?
 2627   	    if (opened && !((IMAPStore)store).hasSeparateStoreConnection()) {
 2628   		synchronized (messageCacheLock) {
 2629   		    return cmd.doCommand(getProtocol());
 2630   		}
 2631   	    }
 2632   	}
 2633   
 2634   	// only get here if using store's connection
 2635   	IMAPProtocol p = null;
 2636   
 2637   	try {
 2638               p = getStoreProtocol();
 2639   	    return cmd.doCommand(p);
 2640   	} finally {
 2641   	    releaseStoreProtocol(p);
 2642   	}
 2643       }
 2644   
 2645       /**
 2646        * Release the store protocol object.  If we borrowed a protocol
 2647        * object from the connection pool, give it back.  If we used our
 2648        * own protocol object, nothing to do.
 2649        */
 2650       protected synchronized void releaseStoreProtocol(IMAPProtocol p) {
 2651           if (p != protocol)
 2652               ((IMAPStore)store).releaseStoreProtocol(p);
 2653       }
 2654   
 2655       /**
 2656        * Release the protocol object.
 2657        *
 2658        * ASSERT: This method must be called only when holding the
 2659        *  messageCacheLock
 2660        */
 2661       private void releaseProtocol(boolean returnToPool) {
 2662           if (protocol != null) {
 2663               protocol.removeResponseHandler(this);
 2664   
 2665               if (returnToPool)
 2666                   ((IMAPStore)store).releaseProtocol(this, protocol);
 2667               else
 2668                   ((IMAPStore)store).releaseProtocol(this, null);
 2669           }
 2670       }
 2671       
 2672       /**
 2673        * Issue a noop command for the connection if the connection has not been
 2674        * used in more than a second. If <code>keepStoreAlive</code> is true,
 2675        * also issue a noop over the store's connection.
 2676        *
 2677        * ASSERT: This method must be called only when holding the
 2678        *  messageCacheLock
 2679        */
 2680       private void keepConnectionAlive(boolean keepStoreAlive) 
 2681                       throws ProtocolException {
 2682   
 2683           if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) {
 2684   	    waitIfIdle();
 2685               protocol.noop(); 
 2686   	}
 2687   
 2688           if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection()) {
 2689               IMAPProtocol p = null;
 2690   	    try {
 2691   		p = ((IMAPStore)store).getStoreProtocol();
 2692   		if (System.currentTimeMillis() - p.getTimestamp() > 1000)
 2693   		    p.noop();
 2694   	    } finally {
 2695   		((IMAPStore)store).releaseStoreProtocol(p);
 2696   	    }
 2697           }
 2698       }
 2699   
 2700       /**
 2701        * Get the message object for the given sequence number. If
 2702        * none found, null is returned.
 2703        *
 2704        * ASSERT: This method must be called only when holding the
 2705        *  messageCacheLock
 2706        */
 2707       IMAPMessage getMessageBySeqNumber(int seqnum) {
 2708   	/* Check messageCache for message matching the given
 2709   	 * sequence number. We start the search from position (seqnum-1)
 2710   	 * and continue down the vector till we get a match.
 2711   	 */
 2712   	for (int i = seqnum-1; i < total; i++) {
 2713   	    IMAPMessage msg = (IMAPMessage)messageCache.elementAt(i);
 2714   	    if (msg.getSequenceNumber() == seqnum)
 2715   		return msg;
 2716   	}
 2717   	return null;
 2718       }
 2719   
 2720       private boolean isDirectory() {
 2721   	return ((type & HOLDS_FOLDERS) != 0);
 2722       }
 2723   }
 2724   
 2725   /**
 2726    * An object that holds a Message object
 2727    * and reports its size and writes it to another OutputStream
 2728    * on demand.  Used by appendMessages to avoid the need to
 2729    * buffer the entire message in memory in a single byte array
 2730    * before sending it to the server.
 2731    */
 2732   class MessageLiteral implements Literal {
 2733       private Message msg;
 2734       private int msgSize = -1;
 2735       private byte[] buf;		// the buffered message, if not null
 2736   
 2737       public MessageLiteral(Message msg, int maxsize)
 2738   				throws MessagingException, IOException {
 2739   	this.msg = msg;
 2740   	// compute the size here so exceptions can be returned immediately
 2741   	LengthCounter lc = new LengthCounter(maxsize);
 2742   	OutputStream os = new CRLFOutputStream(lc);
 2743   	msg.writeTo(os);
 2744   	os.flush();
 2745   	msgSize = lc.getSize();
 2746   	buf = lc.getBytes();
 2747       }
 2748   
 2749       public int size() {
 2750   	return msgSize;
 2751       }
 2752   
 2753       public void writeTo(OutputStream os) throws IOException {
 2754   	// the message should not change between the constructor and this call
 2755   	try {
 2756   	    if (buf != null)
 2757   		os.write(buf, 0, msgSize);
 2758   	    else {
 2759   		os = new CRLFOutputStream(os);
 2760   		msg.writeTo(os);
 2761   	    }
 2762   	} catch (MessagingException mex) {
 2763   	    // exceptions here are bad, "should" never happen
 2764   	    throw new IOException("MessagingException while appending message: "
 2765   				    + mex);
 2766   	}
 2767       }
 2768   }
 2769   
 2770   /**
 2771    * Count the number of bytes written to the stream.
 2772    * Also, save a copy of small messages to avoid having to process
 2773    * the data again.
 2774    */
 2775   class LengthCounter extends OutputStream {
 2776       private int size = 0;
 2777       private byte[] buf;
 2778       private int maxsize;
 2779   
 2780       public LengthCounter(int maxsize) {
 2781   	buf = new byte[8192];
 2782   	this.maxsize = maxsize;
 2783       }
 2784   
 2785       public void write(int b) {
 2786   	int newsize = size + 1;
 2787   	if (buf != null) {
 2788   	    if (newsize > maxsize && maxsize >= 0) {
 2789   		buf = null;
 2790   	    } else if (newsize > buf.length) {
 2791   		byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
 2792   		System.arraycopy(buf, 0, newbuf, 0, size);
 2793   		buf = newbuf;
 2794   		buf[size] = (byte)b;
 2795   	    } else {
 2796   		buf[size] = (byte)b;
 2797   	    }
 2798   	}
 2799   	size = newsize;
 2800       }
 2801   
 2802       public void write(byte b[], int off, int len) {
 2803   	if ((off < 0) || (off > b.length) || (len < 0) ||
 2804               ((off + len) > b.length) || ((off + len) < 0)) {
 2805   	    throw new IndexOutOfBoundsException();
 2806   	} else if (len == 0) {
 2807   	    return;
 2808   	}
 2809           int newsize = size + len;
 2810   	if (buf != null) {
 2811   	    if (newsize > maxsize && maxsize >= 0) {
 2812   		buf = null;
 2813   	    } else if (newsize > buf.length) {
 2814   		byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
 2815   		System.arraycopy(buf, 0, newbuf, 0, size);
 2816   		buf = newbuf;
 2817   		System.arraycopy(b, off, buf, size, len);
 2818   	    } else {
 2819   		System.arraycopy(b, off, buf, size, len);
 2820   	    }
 2821   	}
 2822           size = newsize;
 2823       }
 2824   
 2825       public void write(byte[] b) throws IOException {
 2826   	write(b, 0, b.length);
 2827       }
 2828   
 2829       public int getSize() {
 2830   	return size;
 2831       }
 2832   
 2833       public byte[] getBytes() {
 2834   	return buf;
 2835       }
 2836   }

Home » glassfish-v2ur2-b04-src » com.sun.mail.imap » [javadoc | source]