geronimo-javamail_1.5/geronimo-javamail_1.5_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java [57:1490]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public class IMAPFolder extends Folder implements UIDFolder, IMAPUntaggedResponseHandler {

    /**
     * Special profile item used for fetching SIZE and HEADER information.
     * These items are extensions that Sun has added to their IMAPFolder immplementation.
     * We're supporting the same set.
     */
    public static class FetchProfileItem extends FetchProfile.Item {
        public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS");
        public static final FetchProfileItem SIZE = new FetchProfileItem("SIZE");

        protected FetchProfileItem(String name) {
            super(name);
        }
    }

    // marker that we don't know the separator yet for this folder.
    // This occurs when we obtain a folder reference from the
    // default folder.  At that point, we've not queried the
    // server for specifics yet.
    static final protected char UNDETERMINED = 0;

    // our attached session
    protected Session session;
    // retrieved messages, mapped by sequence number.
    protected Map messageCache;
    // mappings of UIDs to retrieved messages.
    protected Map uidCache;

    // the separator the server indicates is used as the hierarchy separator
    protected char separator;
    // the "full" name of the folder.  This is the fully qualified path name for the folder returned by
    // the IMAP server.  Elements of the hierarchy are delimited by "separator" characters.
    protected String fullname;
    // the name of this folder.  The is the last element of the fully qualified name.
    protected String name;
    // the folder open state
	protected boolean folderOpen = false;
    // the type information on what the folder can hold
    protected int folderType;
    // the subscription status
    protected boolean subscribed = false;

    // the message identifier ticker, used to assign message numbers.
    protected int nextMessageID = 1;
    // the current count of messages in our cache.
    protected int maxSequenceNumber = 0;
    // the reported count of new messages (updated as a result of untagged message resposes)
    protected int recentMessages = -1;
    // the reported count of unseen messages
    protected int unseenMessages = 0;
    // the uidValidity value reported back from the server
    protected long uidValidity = 0;
    // the uidNext value reported back from the server
    protected long uidNext = 0;
    // the persistent flags we save in the store
    protected Flags permanentFlags;
    // the settable flags the server reports back to us
    protected Flags availableFlags;
    // Our cached status information.  We will only hold this for the timeout interval.
    protected IMAPMailboxStatus cachedStatus;
    // Folder information retrieved from the server.  Good info here indicates the
    // folder exists.
    protected IMAPListResponse listInfo;
    // the configured status cache timeout value.
    protected long statusCacheTimeout;
    // the last time we took a status snap shot.
    protected long lastStatusTimeStamp;
    // Our current connection.  We get one of these when opened, and release it when closed.
    // We do this because for any folder (and message) operations, the folder must be selected on
    // the connection.
    // Note, however, that there are operations which will require us to borrow a connection
    // temporarily because we need to touch the server when the folder is not open.  In those
    // cases, we grab a connection, then immediately return it to the pool.
    protected IMAPConnection currentConnection;



    /**
     * Super class constructor the base IMAPFolder class.
     *
     * @param store     The javamail store this folder is attached to.
     * @param fullname  The fully qualified name of this folder.
     * @param separator The separtor character used to delimit the different
     *                  levels of the folder hierarchy.  This is used to
     *                  decompose the full name into smaller parts and
     *                  create the names of subfolders.
     */
	protected IMAPFolder(IMAPStore store, String fullname, char separator) {
		super(store);
		this.session = store.getSession();
        this.fullname = fullname;
        this.separator = separator;
        // get the status timeout value from the folder.
        statusCacheTimeout = store.statusCacheTimeout;
	}

    /**
     * Retrieve the folder name.  This is the simple folder
     * name at the its hiearchy level.  This can be invoked when the folder is closed.
     *
     * @return The folder's name.
     */
	public String getName() {
        // At the time we create the folder, we might not know the separator character yet.
        // Because of this we need to delay creating the name element until
        // it's required.
        if (name == null) {
            // extract the name from the full name
            int lastLevel = -1;
            try {
                lastLevel = fullname.lastIndexOf(getSeparator());
            } catch (MessagingException e) {
                // not likely to occur, but the link could go down before we
                // get this.  Just assume a failure to locate the character
                // occurred.
            }
            if (lastLevel == -1) {
                name = fullname;
            }
            else {
                name = fullname.substring(lastLevel + 1);
            }
        }
        return name;
	}

    /**
     * Retrieve the folder's full name (including hierarchy information).
     * This can be invoked when the folder is closed.
     *
     * @return The full name value.
     */
	public String getFullName() {
        return fullname;
	}



    /**
     * Return the parent for this folder; if the folder is at the root of a heirarchy
     * this returns null.
     * This can be invoked when the folder is closed.
     *
     * @return this folder's parent
     * @throws MessagingException
     */
	public Folder getParent() throws MessagingException {
        // NB:  We need to use the method form because the separator
        // might not have been retrieved from the server yet.
        char separator = getSeparator();
        // we don't hold a reference to the parent folder, as that would pin the instance in memory
        // as long as any any leaf item in the hierarchy is still open.
        int lastLevel = fullname.lastIndexOf(separator);
        // no parent folder?  Get the root one from the Store.
        if (lastLevel == -1) {
            return ((IMAPStore)store).getDefaultFolder();
        }
        else {
            // create a folder for the parent.
            return new IMAPFolder((IMAPStore)store, fullname.substring(0, lastLevel), separator);
        }
	}


    /**
     * Check to see if this folder physically exists in the store.
     * This can be invoked when the folder is closed.
     *
     * @return true if the folder really exists
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized boolean exists() throws MessagingException {
        IMAPConnection connection = getConnection();
        try {
            return checkExistance(connection);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Internal routine for checking existance using an
     * already obtained connection.  Used for situations
     * where the list information needs updating but
     * we'd end up acquiring a new connection because
     * the folder isn't open yet.
     *
     * @param connection The connection to use.
     *
     * @return true if the folder exists, false for non-existence.
     * @exception MessagingException
     */
    private boolean checkExistance(IMAPConnection connection) throws MessagingException {
        // get the list response for this folder.
        List responses = connection.list("", fullname);
        // NB, this grabs the latest information and updates
        // the type information also.  Note also that we need to
        // use the mailbox name, not the full name.  This is so
        // the namespace folders will return the correct response.
        listInfo = findListResponse(responses, getMailBoxName());

        if (listInfo == null) {
            return false;
        }

        // update the type information from the status.
        folderType = 0;
        if (!listInfo.noinferiors) {
            folderType |= HOLDS_FOLDERS;
        }
        if (!listInfo.noselect) {
            folderType |= HOLDS_MESSAGES;
        }

        // also update the separator information.  This will allow
        // use to skip a call later
        separator = listInfo.separator;
        // this can be omitted in the response, so assume a default
        if (separator == '\0') {
            separator = '/';
        }

        // updated ok, so it must be there.
        return true;
    }



    /**
     * Return a list of folders from this Folder's namespace that match the supplied pattern.
     * Patterns may contain the following wildcards:
     * <ul><li>'%' which matches any characater except hierarchy delimiters</li>
     * <li>'*' which matches any character including hierarchy delimiters</li>
     * </ul>
     * This can be invoked when the folder is closed.
     *
     * @param pattern the pattern to search for
     *
     * @return a possibly empty array containing Folders that matched the pattern
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized Folder[] list(String pattern) throws MessagingException {
        // go filter the folders based on the pattern.  The server does most of the
        // heavy lifting on the pattern matching.
        return filterFolders(pattern, false);
    }


    /**
     * Return a list of folders to which the user is subscribed and which match the supplied pattern.
     * If the store does not support the concept of subscription then this should match against
     * all folders; the default implementation of this method achieves this by defaulting to the
     * {@link #list(String)} method.
     *
     * @param pattern the pattern to search for
     *
     * @return a possibly empty array containing subscribed Folders that matched the pattern
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized Folder[] listSubscribed(String pattern) throws MessagingException {
        // go filter the folders based on the pattern.  The server does most of the
        // heavy lifting on the pattern matching.
        return filterFolders(pattern, true);
    }


    /**
     * Return the character used by this folder's Store to separate path components.
     *
     * @return the name separater character
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized char getSeparator() throws MessagingException {
        // not determined yet, we need to ask the server for the information
        if (separator == UNDETERMINED) {
            IMAPConnection connection = getConnection();
            try {
                List responses = connection.list("", fullname);
                IMAPListResponse info = findListResponse(responses, fullname);

                // if we didn't get any hits, then we just assume a reasonable default.
                if (info == null) {
                    separator = '/';
                }
                else {
                    separator = info.separator;
                    // this can be omitted in the response, so assume a default
                    if (separator == '\0') {
                        separator = '/';
                    }
                }
            } finally {
                releaseConnection(connection);
            }
        }
        return separator;
	}


    /**
     * Return whether this folder can hold just messages or also
     * subfolders.
     *
     * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending
     * on the folder capabilities.
     * @exception MessagingException
     */
	public int getType() throws MessagingException {
        // checking the validity will update the type information
        // if it succeeds.
        checkFolderValidity();
		return folderType;
	}


    /**
     * Create a new folder capable of containing subfolder and/or messages as
     * determined by the type parameter. Any hierarchy defined by the folder
     * name will be recursively created.
     * If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent}
     * is sent to all FolderListeners registered with this Folder or with the Store.
     *
     * @param newType the type, indicating if this folder should contain subfolders, messages or both
     *
     * @return true if the folder was sucessfully created
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
	public synchronized boolean create(int newType) throws MessagingException {
        IMAPConnection connection = getConnection();
        try {

            // by default, just create using the fullname.
            String newPath = fullname;

            // if this folder is expected to only hold additional folders, we need to
            // add a separator on to the end when we create this.
            if ((newType & HOLDS_MESSAGES) == 0) {
                newPath = fullname + separator;
            }
            try {
                // go create this
                connection.createMailbox(newPath);
                // verify this exists...also updates some of the status
                boolean reallyCreated = checkExistance(connection);
                // broadcast a creation event.
                notifyFolderListeners(FolderEvent.CREATED);
                return reallyCreated;
            } catch (MessagingException e) {
                //TODO add folder level debug logging.
            }
            // we have a failure
            return false;
        } finally {
            releaseConnection(connection);
        }
	}


    /**
     * Return the subscription status of this folder.
     *
     * @return true if the folder is marked as subscribed, false for
     *         unsubscribed.
     */
    public synchronized boolean isSubscribed() {
        try {
            IMAPConnection connection = getConnection();
            try {
                // get the lsub response for this folder.
                List responses = connection.listSubscribed("", fullname);

                IMAPListResponse response = findListResponse(responses, fullname);
                if (response == null) {
                    return false;
                }
                else {
                    // a NOSELECT flag response indicates the mailbox is no longer
                    // selectable, so it's also no longer subscribed to.
                    return !response.noselect;
                }
            } finally {
                releaseConnection(connection);
            }
        } catch (MessagingException e) {
            // Can't override to throw a MessagingException on this method, so
            // just swallow any exceptions and assume false is the answer.
        }
        return false;
    }


    /**
     * Set or clear the subscription status of a file.
     *
     * @param flag
     *            The new subscription state.
     */
    public synchronized void setSubscribed(boolean flag) throws MessagingException {
        IMAPConnection connection = getConnection();
        try {
            if (flag) {
                connection.subscribe(fullname);
            }
            else {
                connection.unsubscribe(fullname);
            }
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Check to see if this Folder conatins messages with the {@link Flag.RECENT} flag set.
     * This can be used when the folder is closed to perform a light-weight check for new mail;
     * to perform an incremental check for new mail the folder must be opened.
     *
     * @return true if the Store has recent messages
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized boolean hasNewMessages() throws MessagingException {
        // the folder must exist for this to work.
        checkFolderValidity();

        // get the freshest status information.
        refreshStatus(true);
        // return the indicator from the message state.
        return recentMessages > 0;
	}

    /**
     * Get the Folder determined by the supplied name; if the name is relative
     * then it is interpreted relative to this folder. This does not check that
     * the named folder actually exists.
     *
     * @param name the name of the folder to return
     * @return the named folder
     * @throws MessagingException if there was a problem accessing the store
     */
    public Folder getFolder(String name) throws MessagingException {
        // this must be a real, valid folder to hold a subfolder
        checkFolderValidity();
        if (!holdsFolders()) {
            throw new MessagingException("Folder " + fullname + " cannot hold subfolders");
        }
        // our separator does not get determined until we ping the server for it.  We
        // might need to do that now, so we need to use the getSeparator() method to retrieve this.
        char separator = getSeparator();

        return new IMAPFolder((IMAPStore)store, fullname + separator + name, separator);
    }


    /**
     * Delete this folder and possibly any subfolders. This operation can only be
     * performed on a closed folder.
     * If recurse is true, then all subfolders are deleted first, then any messages in
     * this folder are removed and it is finally deleted; {@link FolderEvent#DELETED}
     * events are sent as appropriate.
     * If recurse is false, then the behaviour depends on the folder type and store
     * implementation as followd:
     * <ul>
     * <li>If the folder can only conrain messages, then all messages are removed and
     * then the folder is deleted; a {@link FolderEvent#DELETED} event is sent.</li>
     * <li>If the folder can onlu contain subfolders, then if it is empty it will be
     * deleted and a {@link FolderEvent#DELETED} event is sent; if the folder is not
     * empty then the delete fails and this method returns false.</li>
     * <li>If the folder can contain both subfolders and messages, then if the folder
     * does not contain any subfolders, any messages are deleted, the folder itself
     * is deleted and a {@link FolderEvent#DELETED} event is sent; if the folder does
     * contain subfolders then the implementation may choose from the following three
     * behaviors:
     * <ol>
     * <li>it may return false indicting the operation failed</li>
     * <li>it may remove all messages within the folder, send a {@link FolderEvent#DELETED}
     * event, and then return true to indicate the delete was performed. Note this does
     * not delete the folder itself and the {@link #exists()} operation for this folder
     * will return true</li>
     * <li>it may remove all messages within the folder as per the previous option; in
     * addition it may change the type of the Folder to only HOLDS_FOLDERS indictaing
     * that messages may no longer be added</li>
     * </li>
     * </ul>
     * FolderEvents are sent to all listeners registered with this folder or
     * with the Store.
     *
     * @param recurse whether subfolders should be recursively deleted as well
     * @return true if the delete operation succeeds
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized boolean delete(boolean recurse) throws MessagingException {
        // we must be in the closed state.
        checkClosed();

        // if recursive, get the list of subfolders and delete them first.
        if (recurse) {

            Folder[] subfolders = list();
            for (int i = 0; i < subfolders.length; i++) {
                // this is a recursive delete also
                subfolders[i].delete(true);
            }
        }

        IMAPConnection connection = getConnection();
        try {
            // delete this one now.
            connection.deleteMailbox(fullname);
            // this folder no longer exists on the server.
            listInfo = null;

            // notify interested parties about the deletion.
            notifyFolderListeners(FolderEvent.DELETED);
            return true;

        } catch (MessagingException e) {
            // ignored
        } finally {
            releaseConnection(connection);
        }
        return false;
	}


    /**
     * Rename this folder; the folder must be closed.
     * If the rename is successfull, a {@link FolderEvent#RENAMED} event is sent to
     * all listeners registered with this folder or with the store.
     *
     * @param newName the new name for this folder
     * @return true if the rename succeeded
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized boolean renameTo(Folder f) throws MessagingException {
        // we must be in the closed state.
        checkClosed();
        // but we must also exist
        checkFolderValidity();

        IMAPConnection connection = getConnection();
        try {
            // delete this one now.
            connection.renameMailbox(fullname, f.getFullName());
            // we renamed, so get a fresh set of status
            refreshStatus(false);

            // notify interested parties about the deletion.
            notifyFolderRenamedListeners(f);
            return true;
        } catch (MessagingException e) {
            // ignored
        } finally {
            releaseConnection(connection);
        }
        return false;
	}


    /**
     * Open this folder; the folder must be able to contain messages and
     * must currently be closed. If the folder is opened successfully then
     * a {@link ConnectionEvent#OPENED} event is sent to listeners registered
     * with this Folder.
     * <p/>
     * Whether the Store allows multiple connections or if it allows multiple
     * writers is implementation defined.
     *
     * @param mode READ_ONLY or READ_WRITE
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized void open(int mode) throws MessagingException {

        // we use a synchronized block rather than use a synchronized method so that we
        // can notify the event listeners while not holding the lock.
        synchronized(this) {
            // can only be performed on a closed folder
            checkClosed();
            // ask the store to kindly hook us up with a connection.
            // We're going to hang on to this until we're closed, so store it in
            // the Folder field.  We need to make sure our mailbox is selected while
            // we're working things.
            currentConnection = ((IMAPStore)store).getFolderConnection(this);
            // we need to make ourselves a handler of unsolicited responses
            currentConnection.addResponseHandler(this);
            // record our open mode
            this.mode = mode;


            try {
                // try to open, which gives us a lot of initial mailbox state.
                IMAPMailboxStatus status = currentConnection.openMailbox(fullname, mode == Folder.READ_ONLY);

                // not available in the requested mode?
                if (status.mode != mode) {
                    // trying to open READ_WRITE and this isn't available?
                    if (mode == READ_WRITE) {
                        throw new ReadOnlyFolderException(this, "Cannot open READ_ONLY folder in READ_WRITE mode");
                    }
                }

                // save this status and when we got it for later updating.
                cachedStatus = status;
                // mark when we got this
                lastStatusTimeStamp = System.currentTimeMillis();

                // now copy the status information over and flip over the open sign.
                this.mode = status.mode;
                maxSequenceNumber = status.messages;
                recentMessages = status.recentMessages;
                uidValidity = status.uidValidity;
                uidNext = status.uidNext;

                availableFlags = status.availableFlags;
                permanentFlags = status.permanentFlags;

                // create a our caches.  These are empty initially
                messageCache = new HashMap();
                uidCache = new HashMap();

                // we're open for business folks!
                folderOpen = true;
                notifyConnectionListeners(ConnectionEvent.OPENED);
            } finally {
                // NB:  this doesn't really release this, but it does drive
                // the processing of any unsolicited responses.
                releaseConnection(currentConnection);
            }
        }
	}


    /**
     * Close this folder; it must already be open.
     * A  @link ConnectionEvent#CLOSED} event is sent to all listeners registered
     {*
     * with this folder.
     *
     * @param expunge whether to expunge all deleted messages
     * @throws MessagingException if there was a problem accessing the store; the folder is still closed
     */
	public synchronized void close(boolean expunge) throws MessagingException {
		// Can only be performed on an open folder
		checkOpen();
        cleanupFolder(expunge, false);
	}


    /**
     * Do folder cleanup.  This is used both for normal
     * close operations, and adnormal closes where the
     * server has sent us a BYE message.
     *
     * @param expunge Indicates whether open messages should be expunged.
     * @param disconnected
     *                The disconnected flag.  If true, the server has cut
     *                us off, which means our connection can not be returned
     *                to the connection pool.
     *
     * @exception MessagingException
     */
    protected void cleanupFolder(boolean expunge, boolean disconnected) throws MessagingException {
		folderOpen = false;
        uidCache = null;
        messageCache = null;
        // if we have a connection active at the moment
        if (currentConnection != null) {
            // was this a forced disconnect by the server?
            if (disconnected) {
                currentConnection.setClosed();
            }
            else {
                // The CLOSE operation depends on what mode was used to select the mailbox.
                // If we're open in READ-WRITE mode, we used a SELECT operation.  When CLOSE
                // is issued, any deleted messages will be expunged.  If we've been asked not
                // to expunge the messages, we have a problem.  The solution is to reselect the
                // mailbox using EXAMINE, which will not expunge messages when closed.
                if (mode == READ_WRITE && !expunge) {
                    // we can ignore the result...we're just switching modes.
                    currentConnection.openMailbox(fullname, true);
                }

                // have this close the selected mailbox
                currentConnection.closeMailbox();
            }
            currentConnection.removeResponseHandler(this);
            // we need to release the connection to the Store once we're closed
            ((IMAPStore)store).releaseFolderConnection(this, currentConnection);
            currentConnection = null;
        }
		notifyConnectionListeners(ConnectionEvent.CLOSED);
    }


    /**
     * Tests the open status of the folder.
     *
     * @return true if the folder is open, false otherwise.
     */
	public boolean isOpen() {
		return folderOpen;
	}

    /**
     * Get the permanentFlags
     *
     * @return The set of permanent flags we support (only SEEN).
     */
	public synchronized Flags getPermanentFlags() {
        if (permanentFlags != null) {
            // we need a copy of our master set.
            return new Flags(permanentFlags);
        }
        else {
            // a null return is expected if not there.
            return null;
        }
	}


    /**
     * Return the number of messages this folder contains.
     * If this operation is invoked on a closed folder, the implementation
     * may choose to return -1 to avoid the expense of opening the folder.
     *
     * @return the number of messages, or -1 if unknown
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized int getMessageCount() throws MessagingException {
        checkFolderValidity();

        // if we haven't opened the folder yet, we might not have good status information.
        // go request some, which updates the folder fields also.
        refreshStatus(false);
		return maxSequenceNumber;
	}

    /**
     * Return the numbew of messages in this folder that have the {@link Flag.RECENT} flag set.
     * If this operation is invoked on a closed folder, the implementation
     * may choose to return -1 to avoid the expense of opening the folder.
     * The default implmentation of this method iterates over all messages
     * in the folder; subclasses should override if possible to provide a more
     * efficient implementation.
     *
     * NB:  This is an override of the default Folder implementation, which
     * examines each of the messages in the folder.  IMAP has more efficient
     * mechanisms for grabbing the information.
     *
     * @return the number of new messages, or -1 if unknown
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized int getNewMessageCount() throws MessagingException {
        // the folder must be a real one for this to work.
        checkFolderValidity();
        // now get current status from the folder
        refreshStatus(false);
        // this should be current now.
        return recentMessages;
    }



    /**
     * Return the number of messages in this folder that do not have the {@link Flag.SEEN} flag set.
     * If this operation is invoked on a closed folder, the implementation
     * may choose to return -1 to avoid the expense of opening the folder.
     * The default implmentation of this method iterates over all messages
     * in the folder; subclasses should override if possible to provide a more
     * efficient implementation.
     *
     * NB:  This is an override of the default Folder implementation, which
     * examines each of the messages in the folder.  IMAP has more efficient
     * mechanisms for grabbing the information.
     *
     * @return the number of new messages, or -1 if unknown
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized int getUnreadMessageCount() throws MessagingException {
        checkFolderValidity();
        // if we haven't opened the folder yet, we might not have good status information.
        // go request some, which updates the folder fields also.
        if (!folderOpen) {
            refreshStatus(false);
        }
        else {
            // if we have an open connection, then search the folder for any messages
            // marked UNSEEN.

            // UNSEEN is a false test on SEEN using the search criteria.
            SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.SEEN), false);

            // ask the store to kindly hook us up with a connection.
            IMAPConnection connection = getConnection();
            try {
                // search using the connection directly rather than calling our search() method so we don't
                // need to instantiate each of the matched messages.  We're really only interested in the count
                // right now.
                int[] matches = connection.searchMailbox(criteria);
                // update the unseen count.
                unseenMessages = matches == null ? 0 : matches.length;
            } finally {
                releaseConnection(connection);
            }
        }
        // return our current message count.
		return unseenMessages;
	}



    /**
     * Return the number of messages in this folder that have the {@link Flag.DELETED} flag set.
     * If this operation is invoked on a closed folder, the implementation
     * may choose to return -1 to avoid the expense of opening the folder.
     * The default implmentation of this method iterates over all messages
     * in the folder; subclasses should override if possible to provide a more
     * efficient implementation.
     *
     * @return the number of new messages, or -1 if unknown
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized int getDeletedMessageCount() throws MessagingException {
        checkFolderValidity();

        // if we haven't opened the folder yet, we might not have good status information.
        // go request some, which updates the folder fields also.
        if (!folderOpen) {
            // the status update doesn't return deleted messages.  These can only be obtained by
            // searching an open folder.  Just return a bail-out response
            return -1;
        }
        else {
            // if we have an open connection, then search the folder for any messages
            // marked DELETED.

            // UNSEEN is a false test on SEEN using the search criteria.
            SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.DELETED), true);

            // ask the store to kindly hook us up with a connection.
            IMAPConnection connection = getConnection();
            try {
                // search using the connection directly rather than calling our search() method so we don't
                // need to instantiate each of the matched messages.  We're really only interested in the count
                // right now.
                int[] matches = connection.searchMailbox(criteria);
                return matches == null ? 0 : matches.length;
            } finally {
                releaseConnection(connection);
            }
        }
	}


    /**
     * Retrieve the message with the specified index in this Folder;
     * messages indices start at 1 not zero.
     * Clients should note that the index for a specific message may change
     * if the folder is expunged; {@link Message} objects should be used as
     * references instead.
     *
     * @param msgNum The message sequence number of the target message.
     *
     * @return the message
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized Message getMessage(int msgNum) throws MessagingException {
        // Can only be performed on an Open folder
        checkOpen();
        // Check the validity of the message number.  This may require pinging the server to
        // see if there are new messages in the folder.
        checkMessageValidity(msgNum);
        // create the mapping key for this
        Integer messageKey = new Integer(msgNum);
        // ok, if the message number is within range, we should have this in the
        // messages list.  Just return the element.
        Message message = (Message)messageCache.get(messageKey);
        // if not in the cache, create a dummy add it in.  The message body will be
        // retrieved on demand
        if (message == null) {
            message = new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, msgNum);
            messageCache.put(messageKey, message);
        }
        return message;
    }


    /**
     * Retrieve a range of messages for this folder.
     * messages indices start at 1 not zero.
     *
     * @param start  Index of the first message to fetch, inclusive.
     * @param end    Index of the last message to fetch, inclusive.
     *
     * @return An array of the fetched messages.
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized Message[] getMessages(int start, int end) throws MessagingException {
        // Can only be performed on an Open folder
        checkOpen();
        Message[] messageRange = new Message[end - start + 1];

        for (int i = 0; i < messageRange.length; i++) {
            // NB:  getMessage() requires values that are origin 1, so there's
            // no need to adjust the value by other than the start position.
            messageRange[i] = getMessage(start + i);
        }
        return messageRange;
    }


    /**
     * Append the supplied messages to this folder. A {@link MessageCountEvent} is sent
     * to all listeners registered with this folder when all messages have been appended.
     * If the array contains a previously expunged message, it must be re-appended to the Store
     * and implementations must not abort this operation.
     *
     * @param msgs   The array of messages to append to the folder.
     *
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
	public synchronized void appendMessages(Message[] msgs) throws MessagingException {
        checkFolderValidity();
        for (int i = 0; i < msgs.length; i++) {
            Message msg = msgs[i];

            appendMessage(msg);
        }
	}

    /**
     * Hint to the store to prefetch information on the supplied messages.
     * Subclasses should override this method to provide an efficient implementation;
     * the default implementation in this class simply returns.
     *
     * @param messages messages for which information should be fetched
     * @param profile  the information to fetch
     * @throws MessagingException if there was a problem accessing the store
     * @see FetchProfile
     */
    public void fetch(Message[] messages, FetchProfile profile) throws MessagingException {

        // we might already have the information being requested, so ask each of the
        // messages in the list to evaluate itself against the profile.  We'll only ask
        // the server to send information that's required.
        List fetchSet = new ArrayList();

        for (int i = 0; i < messages.length; i++) {
            Message msg = messages[i];
            // the message is missing some of the information still.  Keep this in the list.
            // even if the message is only missing one piece of information, we still fetch everything.
            if (((IMAPMessage)msg).evaluateFetch(profile)) {
                fetchSet.add(msg);
            }
        }

        // we've got everything already, no sense bothering the server
        if (fetchSet.isEmpty()) {
            return;
        }
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();
        try {
            // ok, from this point onward, we don't want any threads messing with the
            // message cache.  A single processed EXPUNGE could make for a very bad day
            synchronized(this) {
                // get the message set for this
                String messageSet = generateMessageSet(fetchSet);
                // fetch all of the responses
                List responses = connection.fetch(messageSet, profile);

                // IMPORTANT:  We must do our updates while synchronized to keep the
                // cache from getting updated underneath us.   This includes
                // not releasing the connection until we're done to delay processing any
                // pending expunge responses.
                for (int i = 0; i < responses.size(); i++) {
                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
                    Message msg = getMessage(response.getSequenceNumber());
                    // Belt and Braces.  This should never be false.
                    if (msg != null) {
                        // have the message apply this to itself.
                        ((IMAPMessage)msg).updateMessageInformation(response);
                    }
                }
            }
        } finally {
            releaseConnection(connection);
        }
        return;
    }

    /**
     * Set flags on the messages to the supplied value; all messages must belong to this folder.
     * This method may be overridden by subclasses that can optimize the setting
     * of flags on multiple messages at once; the default implementation simply calls
     * {@link Message#setFlags(Flags, boolean)} for each supplied messages.
     *
     * @param messages whose flags should be set
     * @param flags    the set of flags to modify
     * @param set      Indicates whether the flags should be set or cleared.
     *
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public void setFlags(Message[] messages, Flags flags, boolean set) throws MessagingException {
        // this is a list of messages for the change broadcast after the update
        List updatedMessages = new ArrayList();

        synchronized(this) {
            // the folder must be open and writeable.
            checkOpenReadWrite();

            // now make sure these are settable flags.
            if (!availableFlags.contains(flags))
            {
                throw new MessagingException("The IMAP server does not support changing of this flag set");
            }

            // turn this into a set of message numbers
            String messageSet = generateMessageSet(messages);
            // if all of the messages have been expunged, nothing to do.
            if (messageSet == null) {
                return;
            }
            // ask the store to kindly hook us up with a connection.
            IMAPConnection connection = getConnection();

            try {
                // and have the connection set this
                List responses = connection.setFlags(messageSet, flags, set);
                // retrieve each of the messages from our cache, and do the flag update.
                // we need to keep the list so we can broadcast a change update event
                // when we're finished.
                for (int i = 0; i < responses.size(); i++) {
                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);

                    // get the updated message and update the internal state.
                    Message message = getMessage(response.sequenceNumber);
                    // this shouldn't happen, but it might have been expunged too.
                    if (message != null) {
                        ((IMAPMessage)message).updateMessageInformation(response);
                        updatedMessages.add(message);
                    }
                }
            } finally {
                releaseConnection(connection);
            }
        }

        // ok, we're no longer holding the lock.  Now go broadcast the update for each
        // of the affected messages.
        for (int i = 0; i < updatedMessages.size(); i++) {
            Message message = (Message)updatedMessages.get(i);
            notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
        }
    }


    /**
     * Set flags on a range of messages to the supplied value.
     * This method may be overridden by subclasses that can optimize the setting
     * of flags on multiple messages at once; the default implementation simply
     * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
     *
     * @param start  first message end set
     * @param end    last message end set
     * @param flags  the set of flags end modify
     * @param value  Indicates whether the flags should be set or cleared.
     *
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException {
        Message[] msgs = new Message[end - start + 1];

        for (int i = start; i <= end; i++) {
            msgs[i] = getMessage(i);
        }
        // go do a bulk set operation on these messages
        setFlags(msgs, flags, value);
    }

    /**
     * Set flags on a set of messages to the supplied value.
     * This method may be overridden by subclasses that can optimize the setting
     * of flags on multiple messages at once; the default implementation simply
     * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
     *
     * @param ids    the indexes of the messages to set
     * @param flags  the set of flags end modify
     * @param value  Indicates whether the flags should be set or cleared.
     *
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized void setFlags(int ids[], Flags flags, boolean value) throws MessagingException {
        Message[] msgs = new Message[ids.length];

        for (int i = 0; i <ids.length; i++) {
            msgs[i] = getMessage(ids[i]);
        }
        // go do a bulk set operation on these messages
        setFlags(msgs, flags, value);
    }


    /**
     * Copy the specified messages to another folder.
     * The default implementation simply appends the supplied messages to the
     * target folder using {@link #appendMessages(Message[])}.
     * @param messages the messages to copy
     * @param folder the folder to copy to
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized void copyMessages(Message[] messages, Folder folder) throws MessagingException {
        // the default implementation just appends the messages to the target.  If
        // we're copying between two folders of the same store, we can get the server to
        // do most of the work for us without needing to fetch all of the message data.
        // If we're dealing with two different Store instances, we need to do this the
        // hardway.
        if (getStore() != folder.getStore()) {
            super.copyMessages(messages, folder);
            return;
        }

        // turn this into a set of message numbers
        String messageSet = generateMessageSet(messages);
        // if all of the messages have been expunged, nothing to do.
        if (messageSet == null) {
            return;
        }
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // ask the server to copy this information over to the other mailbox.
            connection.copyMessages(messageSet, folder.getFullName());
        } finally {
            releaseConnection(connection);
        }
    }



    /**
     * Permanently delete all supplied messages that have the DELETED flag set from the Store.
     * The original message indices of all messages actually deleted are returned and a
     * {@link MessageCountEvent} event is sent to all listeners with this folder. The expunge
     * may cause the indices of all messaged that remain in the folder to change.
     *
     * @return the original indices of messages that were actually deleted
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized Message[] expunge() throws MessagingException {
        // must be open to do this.
        checkOpen();
        // and changes need to be allowed
        checkReadWrite();

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();
        List expunges = null;

        try {
            // send the expunge notification.  This operation results in "nn EXPUNGE" responses getting returned
            // for each expunged messages.  These will be dispatched to our response handler, which will process
            // the expunge operation.  We could process this directly, but we may have received asynchronous
            // expunge messages that also marked messages as expunged.
            expunges = connection.expungeMailbox();
        } finally {
            releaseConnection(connection);
        }

        // we get one EXPUNGE message for each message that's expunged.  They MUST be processed in
        // order, as the message sequence numbers represent a relative position that takes into account
        // previous expunge operations.  For example, if message sequence numbers 5, 6, and 7 are
        // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged.
        Message[] messages = new Message[expunges.size()];

        // now we need to protect the internal structures
        synchronized (this) {
            // expunge all of the messages from the message cache.  This keeps the sequence
            // numbers up to-date.
            for (int i = 0; i < expunges.size(); i++) {
                IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i);
                messages[i] = expungeMessage(response.getSize());
            }
        }
        // if we have messages that have been removed, broadcast the notification.
        if (messages.length > 0) {
            notifyMessageRemovedListeners(true, messages);
        }

        // note, we're expected to return an array in all cases, even if the expunged count was zero.
        return messages;
	}



    /**
     * Search the supplied messages for those that match the supplied criteria;
     * messages must belong to this folder.
     * The default implementation iterates through the messages, returning those
     * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
     * subclasses may provide a more efficient implementation.
     *
     * @param term the search criteria
     * @param messages the messages to search
     * @return an array containing messages that match the criteria
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized Message[] search(SearchTerm term) throws MessagingException {
        // only allowed on open folders
        checkOpen();

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // just search everything
            int[] messageNumbers = connection.searchMailbox(term);
            return resolveMessages(messageNumbers);
        } finally {
            releaseConnection(connection);
        }
    }



    /**
     * Search the supplied messages for those that match the supplied criteria;
     * messages must belong to this folder.
     * The default implementation iterates through the messages, returning those
     * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
     * subclasses may provide a more efficient implementation.
     *
     * @param term the search criteria
     * @param messages the messages to search
     * @return an array containing messages that match the criteria
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized Message[] search(SearchTerm term, Message[] messages) throws MessagingException {
        // only allowed on open folders
        checkOpen();

        // turn this into a string specifier for these messages.  We'll weed out the expunged messages first.
        String messageSet = generateMessageSet(messages);

        // If we have no "live" messages to search, just return now.  We're required to return a non-null
        // value, so give an empy array back.
        if (messageSet == null) {
            return new Message[0];
        }

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {

            // now go do the search.
            int[] messageNumbers = connection.searchMailbox(messageSet, term);
            return resolveMessages(messageNumbers);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Get the UID validity value for this Folder.
     *
     * @return The current UID validity value, as a long.
     * @exception MessagingException
     */
    public synchronized long getUIDValidity() throws MessagingException
    {
        // get the latest status to make sure we have the
        // most current.
        refreshStatus(true);
        return uidValidity;
    }

    /**
     * Retrieve a message using the UID rather than the
     * message sequence number.  Returns null if the message
     * doesn't exist.
     *
     * @param uid    The target UID.
     *
     * @return the Message object.  Returns null if the message does
     *         not exist.
     * @exception MessagingException
     */
    public synchronized Message getMessageByUID(long uid) throws MessagingException
    {
        // only allowed on open folders
        checkOpen();

        Long key = new Long(uid);
        // first check to see if we have a cached value for this
        synchronized(messageCache) {
            Message msg = (Message)uidCache.get(key);
            if (msg != null) {
                return msg;
            }
        }

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // locate the message identifier
            IMAPUid imapuid = connection.getSequenceNumberForUid(uid);
            // if nothing is returned, the message doesn't exist
            if (imapuid == null) {
                return null;
            }


            // retrieve the actual message object and place this in the UID cache
            return retrieveMessageByUid(key, imapuid.messageNumber);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Get a series of messages using a UID range.  The
     * special value LASTUID can be used to mark the
     * last available message.
     *
     * @param start  The start of the UID range.
     * @param end    The end of the UID range.  The special value
     *               LASTUID can be used to request all messages up
     *               to the last UID.
     *
     * @return An array containing all of the messages in the
     *         range.
     * @exception MessagingException
     */
    public synchronized Message[] getMessagesByUID(long start, long end) throws MessagingException
    {
        // only allowed on open folders
        checkOpen();
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // locate the message identifier
            List uids = connection.getSequenceNumbersForUids(start, end);
            Message[] msgs = new Message[uids.size()];

            // fill in each of the messages based on the returned value
            for (int i = 0; i < msgs.length; i++) {
                IMAPUid uid = (IMAPUid)uids.get(i);
                msgs[i] = retrieveMessageByUid(new Long(uid.uid), uid.messageNumber);
            }

            return msgs;
        } finally {
            releaseConnection(connection);
        }


    }

    /**
     * Retrieve a set of messages by explicit UIDs.  If
     * any message in the list does not exist, null
     * will be returned for the corresponding item.
     *
     * @param ids    An array of UID values to be retrieved.
     *
     * @return An array of Message items the same size as the ids
     *         argument array.  This array will contain null
     *         entries for any UIDs that do not exist.
     * @exception MessagingException
     */
    public synchronized Message[] getMessagesByUID(long[] ids) throws MessagingException
    {
        // only allowed on open folders
        checkOpen();

        Message[] msgs = new Message[ids.length];

        for (int i = 0; i < msgs.length; i++) {
            msgs[i] = getMessageByUID(ids[i]);
        }

        return msgs;
    }

    /**
     * Retrieve the UID for a message from this Folder.
     * The argument Message MUST belong to this Folder
     * instance, otherwise a NoSuchElementException will
     * be thrown.
     *
     * @param message The target message.
     *
     * @return The UID associated with this message.
     * @exception MessagingException
     */
    public synchronized long getUID(Message message) throws MessagingException
    {
        // verify this actually is in this folder.
        checkMessageFolder(message);
        IMAPMessage msg = (IMAPMessage)message;

        // we might already know this bit of information
        if (msg.getUID() != -1) {
            return msg.getUID();
        }

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // locate the message identifier
            IMAPUid imapuid = connection.getUidForSequenceNumber(msg.getMessageNumber());
            // if nothing is returned, the message doesn't exist
            if (imapuid == null) {
                return -1;
            }
            // cache this information now that we've gotten it.
            addToUidCache(new Long(imapuid.uid), getMessage(imapuid.messageNumber));
            // return the UID information.
            return imapuid.uid;
        } finally {
            releaseConnection(connection);
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java [48:1481]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public class IMAPFolder extends Folder implements UIDFolder, IMAPUntaggedResponseHandler {

    /**
     * Special profile item used for fetching SIZE and HEADER information.
     * These items are extensions that Sun has added to their IMAPFolder immplementation.
     * We're supporting the same set.
     */
    public static class FetchProfileItem extends FetchProfile.Item {
        public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS");
        public static final FetchProfileItem SIZE = new FetchProfileItem("SIZE");

        protected FetchProfileItem(String name) {
            super(name);
        }
    }

    // marker that we don't know the separator yet for this folder.
    // This occurs when we obtain a folder reference from the
    // default folder.  At that point, we've not queried the
    // server for specifics yet.
    static final protected char UNDETERMINED = 0;

    // our attached session
    protected Session session;
    // retrieved messages, mapped by sequence number.
    protected Map messageCache;
    // mappings of UIDs to retrieved messages.
    protected Map uidCache;

    // the separator the server indicates is used as the hierarchy separator
    protected char separator;
    // the "full" name of the folder.  This is the fully qualified path name for the folder returned by
    // the IMAP server.  Elements of the hierarchy are delimited by "separator" characters.
    protected String fullname;
    // the name of this folder.  The is the last element of the fully qualified name.
    protected String name;
    // the folder open state
	protected boolean folderOpen = false;
    // the type information on what the folder can hold
    protected int folderType;
    // the subscription status
    protected boolean subscribed = false;

    // the message identifier ticker, used to assign message numbers.
    protected int nextMessageID = 1;
    // the current count of messages in our cache.
    protected int maxSequenceNumber = 0;
    // the reported count of new messages (updated as a result of untagged message resposes)
    protected int recentMessages = -1;
    // the reported count of unseen messages
    protected int unseenMessages = 0;
    // the uidValidity value reported back from the server
    protected long uidValidity = 0;
    // the uidNext value reported back from the server
    protected long uidNext = 0;
    // the persistent flags we save in the store
    protected Flags permanentFlags;
    // the settable flags the server reports back to us
    protected Flags availableFlags;
    // Our cached status information.  We will only hold this for the timeout interval.
    protected IMAPMailboxStatus cachedStatus;
    // Folder information retrieved from the server.  Good info here indicates the
    // folder exists.
    protected IMAPListResponse listInfo;
    // the configured status cache timeout value.
    protected long statusCacheTimeout;
    // the last time we took a status snap shot.
    protected long lastStatusTimeStamp;
    // Our current connection.  We get one of these when opened, and release it when closed.
    // We do this because for any folder (and message) operations, the folder must be selected on
    // the connection.
    // Note, however, that there are operations which will require us to borrow a connection
    // temporarily because we need to touch the server when the folder is not open.  In those
    // cases, we grab a connection, then immediately return it to the pool.
    protected IMAPConnection currentConnection;



    /**
     * Super class constructor the base IMAPFolder class.
     *
     * @param store     The javamail store this folder is attached to.
     * @param fullname  The fully qualified name of this folder.
     * @param separator The separtor character used to delimit the different
     *                  levels of the folder hierarchy.  This is used to
     *                  decompose the full name into smaller parts and
     *                  create the names of subfolders.
     */
	protected IMAPFolder(IMAPStore store, String fullname, char separator) {
		super(store);
		this.session = store.getSession();
        this.fullname = fullname;
        this.separator = separator;
        // get the status timeout value from the folder.
        statusCacheTimeout = store.statusCacheTimeout;
	}

    /**
     * Retrieve the folder name.  This is the simple folder
     * name at the its hiearchy level.  This can be invoked when the folder is closed.
     *
     * @return The folder's name.
     */
	public String getName() {
        // At the time we create the folder, we might not know the separator character yet.
        // Because of this we need to delay creating the name element until
        // it's required.
        if (name == null) {
            // extract the name from the full name
            int lastLevel = -1;
            try {
                lastLevel = fullname.lastIndexOf(getSeparator());
            } catch (MessagingException e) {
                // not likely to occur, but the link could go down before we
                // get this.  Just assume a failure to locate the character
                // occurred.
            }
            if (lastLevel == -1) {
                name = fullname;
            }
            else {
                name = fullname.substring(lastLevel + 1);
            }
        }
        return name;
	}

    /**
     * Retrieve the folder's full name (including hierarchy information).
     * This can be invoked when the folder is closed.
     *
     * @return The full name value.
     */
	public String getFullName() {
        return fullname;
	}



    /**
     * Return the parent for this folder; if the folder is at the root of a heirarchy
     * this returns null.
     * This can be invoked when the folder is closed.
     *
     * @return this folder's parent
     * @throws MessagingException
     */
	public Folder getParent() throws MessagingException {
        // NB:  We need to use the method form because the separator
        // might not have been retrieved from the server yet.
        char separator = getSeparator();
        // we don't hold a reference to the parent folder, as that would pin the instance in memory
        // as long as any any leaf item in the hierarchy is still open.
        int lastLevel = fullname.lastIndexOf(separator);
        // no parent folder?  Get the root one from the Store.
        if (lastLevel == -1) {
            return ((IMAPStore)store).getDefaultFolder();
        }
        else {
            // create a folder for the parent.
            return new IMAPFolder((IMAPStore)store, fullname.substring(0, lastLevel), separator);
        }
	}


    /**
     * Check to see if this folder physically exists in the store.
     * This can be invoked when the folder is closed.
     *
     * @return true if the folder really exists
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized boolean exists() throws MessagingException {
        IMAPConnection connection = getConnection();
        try {
            return checkExistance(connection);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Internal routine for checking existance using an
     * already obtained connection.  Used for situations
     * where the list information needs updating but
     * we'd end up acquiring a new connection because
     * the folder isn't open yet.
     *
     * @param connection The connection to use.
     *
     * @return true if the folder exists, false for non-existence.
     * @exception MessagingException
     */
    private boolean checkExistance(IMAPConnection connection) throws MessagingException {
        // get the list response for this folder.
        List responses = connection.list("", fullname);
        // NB, this grabs the latest information and updates
        // the type information also.  Note also that we need to
        // use the mailbox name, not the full name.  This is so
        // the namespace folders will return the correct response.
        listInfo = findListResponse(responses, getMailBoxName());

        if (listInfo == null) {
            return false;
        }

        // update the type information from the status.
        folderType = 0;
        if (!listInfo.noinferiors) {
            folderType |= HOLDS_FOLDERS;
        }
        if (!listInfo.noselect) {
            folderType |= HOLDS_MESSAGES;
        }

        // also update the separator information.  This will allow
        // use to skip a call later
        separator = listInfo.separator;
        // this can be omitted in the response, so assume a default
        if (separator == '\0') {
            separator = '/';
        }

        // updated ok, so it must be there.
        return true;
    }



    /**
     * Return a list of folders from this Folder's namespace that match the supplied pattern.
     * Patterns may contain the following wildcards:
     * <ul><li>'%' which matches any characater except hierarchy delimiters</li>
     * <li>'*' which matches any character including hierarchy delimiters</li>
     * </ul>
     * This can be invoked when the folder is closed.
     *
     * @param pattern the pattern to search for
     *
     * @return a possibly empty array containing Folders that matched the pattern
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized Folder[] list(String pattern) throws MessagingException {
        // go filter the folders based on the pattern.  The server does most of the
        // heavy lifting on the pattern matching.
        return filterFolders(pattern, false);
    }


    /**
     * Return a list of folders to which the user is subscribed and which match the supplied pattern.
     * If the store does not support the concept of subscription then this should match against
     * all folders; the default implementation of this method achieves this by defaulting to the
     * {@link #list(String)} method.
     *
     * @param pattern the pattern to search for
     *
     * @return a possibly empty array containing subscribed Folders that matched the pattern
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized Folder[] listSubscribed(String pattern) throws MessagingException {
        // go filter the folders based on the pattern.  The server does most of the
        // heavy lifting on the pattern matching.
        return filterFolders(pattern, true);
    }


    /**
     * Return the character used by this folder's Store to separate path components.
     *
     * @return the name separater character
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized char getSeparator() throws MessagingException {
        // not determined yet, we need to ask the server for the information
        if (separator == UNDETERMINED) {
            IMAPConnection connection = getConnection();
            try {
                List responses = connection.list("", fullname);
                IMAPListResponse info = findListResponse(responses, fullname);

                // if we didn't get any hits, then we just assume a reasonable default.
                if (info == null) {
                    separator = '/';
                }
                else {
                    separator = info.separator;
                    // this can be omitted in the response, so assume a default
                    if (separator == '\0') {
                        separator = '/';
                    }
                }
            } finally {
                releaseConnection(connection);
            }
        }
        return separator;
	}


    /**
     * Return whether this folder can hold just messages or also
     * subfolders.
     *
     * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending
     * on the folder capabilities.
     * @exception MessagingException
     */
	public int getType() throws MessagingException {
        // checking the validity will update the type information
        // if it succeeds.
        checkFolderValidity();
		return folderType;
	}


    /**
     * Create a new folder capable of containing subfolder and/or messages as
     * determined by the type parameter. Any hierarchy defined by the folder
     * name will be recursively created.
     * If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent}
     * is sent to all FolderListeners registered with this Folder or with the Store.
     *
     * @param newType the type, indicating if this folder should contain subfolders, messages or both
     *
     * @return true if the folder was sucessfully created
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
	public synchronized boolean create(int newType) throws MessagingException {
        IMAPConnection connection = getConnection();
        try {

            // by default, just create using the fullname.
            String newPath = fullname;

            // if this folder is expected to only hold additional folders, we need to
            // add a separator on to the end when we create this.
            if ((newType & HOLDS_MESSAGES) == 0) {
                newPath = fullname + separator;
            }
            try {
                // go create this
                connection.createMailbox(newPath);
                // verify this exists...also updates some of the status
                boolean reallyCreated = checkExistance(connection);
                // broadcast a creation event.
                notifyFolderListeners(FolderEvent.CREATED);
                return reallyCreated;
            } catch (MessagingException e) {
                //TODO add folder level debug logging.
            }
            // we have a failure
            return false;
        } finally {
            releaseConnection(connection);
        }
	}


    /**
     * Return the subscription status of this folder.
     *
     * @return true if the folder is marked as subscribed, false for
     *         unsubscribed.
     */
    public synchronized boolean isSubscribed() {
        try {
            IMAPConnection connection = getConnection();
            try {
                // get the lsub response for this folder.
                List responses = connection.listSubscribed("", fullname);

                IMAPListResponse response = findListResponse(responses, fullname);
                if (response == null) {
                    return false;
                }
                else {
                    // a NOSELECT flag response indicates the mailbox is no longer
                    // selectable, so it's also no longer subscribed to.
                    return !response.noselect;
                }
            } finally {
                releaseConnection(connection);
            }
        } catch (MessagingException e) {
            // Can't override to throw a MessagingException on this method, so
            // just swallow any exceptions and assume false is the answer.
        }
        return false;
    }


    /**
     * Set or clear the subscription status of a file.
     *
     * @param flag
     *            The new subscription state.
     */
    public synchronized void setSubscribed(boolean flag) throws MessagingException {
        IMAPConnection connection = getConnection();
        try {
            if (flag) {
                connection.subscribe(fullname);
            }
            else {
                connection.unsubscribe(fullname);
            }
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Check to see if this Folder conatins messages with the {@link Flag.RECENT} flag set.
     * This can be used when the folder is closed to perform a light-weight check for new mail;
     * to perform an incremental check for new mail the folder must be opened.
     *
     * @return true if the Store has recent messages
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized boolean hasNewMessages() throws MessagingException {
        // the folder must exist for this to work.
        checkFolderValidity();

        // get the freshest status information.
        refreshStatus(true);
        // return the indicator from the message state.
        return recentMessages > 0;
	}

    /**
     * Get the Folder determined by the supplied name; if the name is relative
     * then it is interpreted relative to this folder. This does not check that
     * the named folder actually exists.
     *
     * @param name the name of the folder to return
     * @return the named folder
     * @throws MessagingException if there was a problem accessing the store
     */
    public Folder getFolder(String name) throws MessagingException {
        // this must be a real, valid folder to hold a subfolder
        checkFolderValidity();
        if (!holdsFolders()) {
            throw new MessagingException("Folder " + fullname + " cannot hold subfolders");
        }
        // our separator does not get determined until we ping the server for it.  We
        // might need to do that now, so we need to use the getSeparator() method to retrieve this.
        char separator = getSeparator();

        return new IMAPFolder((IMAPStore)store, fullname + separator + name, separator);
    }


    /**
     * Delete this folder and possibly any subfolders. This operation can only be
     * performed on a closed folder.
     * If recurse is true, then all subfolders are deleted first, then any messages in
     * this folder are removed and it is finally deleted; {@link FolderEvent#DELETED}
     * events are sent as appropriate.
     * If recurse is false, then the behaviour depends on the folder type and store
     * implementation as followd:
     * <ul>
     * <li>If the folder can only conrain messages, then all messages are removed and
     * then the folder is deleted; a {@link FolderEvent#DELETED} event is sent.</li>
     * <li>If the folder can onlu contain subfolders, then if it is empty it will be
     * deleted and a {@link FolderEvent#DELETED} event is sent; if the folder is not
     * empty then the delete fails and this method returns false.</li>
     * <li>If the folder can contain both subfolders and messages, then if the folder
     * does not contain any subfolders, any messages are deleted, the folder itself
     * is deleted and a {@link FolderEvent#DELETED} event is sent; if the folder does
     * contain subfolders then the implementation may choose from the following three
     * behaviors:
     * <ol>
     * <li>it may return false indicting the operation failed</li>
     * <li>it may remove all messages within the folder, send a {@link FolderEvent#DELETED}
     * event, and then return true to indicate the delete was performed. Note this does
     * not delete the folder itself and the {@link #exists()} operation for this folder
     * will return true</li>
     * <li>it may remove all messages within the folder as per the previous option; in
     * addition it may change the type of the Folder to only HOLDS_FOLDERS indictaing
     * that messages may no longer be added</li>
     * </li>
     * </ul>
     * FolderEvents are sent to all listeners registered with this folder or
     * with the Store.
     *
     * @param recurse whether subfolders should be recursively deleted as well
     * @return true if the delete operation succeeds
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized boolean delete(boolean recurse) throws MessagingException {
        // we must be in the closed state.
        checkClosed();

        // if recursive, get the list of subfolders and delete them first.
        if (recurse) {

            Folder[] subfolders = list();
            for (int i = 0; i < subfolders.length; i++) {
                // this is a recursive delete also
                subfolders[i].delete(true);
            }
        }

        IMAPConnection connection = getConnection();
        try {
            // delete this one now.
            connection.deleteMailbox(fullname);
            // this folder no longer exists on the server.
            listInfo = null;

            // notify interested parties about the deletion.
            notifyFolderListeners(FolderEvent.DELETED);
            return true;

        } catch (MessagingException e) {
            // ignored
        } finally {
            releaseConnection(connection);
        }
        return false;
	}


    /**
     * Rename this folder; the folder must be closed.
     * If the rename is successfull, a {@link FolderEvent#RENAMED} event is sent to
     * all listeners registered with this folder or with the store.
     *
     * @param newName the new name for this folder
     * @return true if the rename succeeded
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized boolean renameTo(Folder f) throws MessagingException {
        // we must be in the closed state.
        checkClosed();
        // but we must also exist
        checkFolderValidity();

        IMAPConnection connection = getConnection();
        try {
            // delete this one now.
            connection.renameMailbox(fullname, f.getFullName());
            // we renamed, so get a fresh set of status
            refreshStatus(false);

            // notify interested parties about the deletion.
            notifyFolderRenamedListeners(f);
            return true;
        } catch (MessagingException e) {
            // ignored
        } finally {
            releaseConnection(connection);
        }
        return false;
	}


    /**
     * Open this folder; the folder must be able to contain messages and
     * must currently be closed. If the folder is opened successfully then
     * a {@link ConnectionEvent#OPENED} event is sent to listeners registered
     * with this Folder.
     * <p/>
     * Whether the Store allows multiple connections or if it allows multiple
     * writers is implementation defined.
     *
     * @param mode READ_ONLY or READ_WRITE
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized void open(int mode) throws MessagingException {

        // we use a synchronized block rather than use a synchronized method so that we
        // can notify the event listeners while not holding the lock.
        synchronized(this) {
            // can only be performed on a closed folder
            checkClosed();
            // ask the store to kindly hook us up with a connection.
            // We're going to hang on to this until we're closed, so store it in
            // the Folder field.  We need to make sure our mailbox is selected while
            // we're working things.
            currentConnection = ((IMAPStore)store).getFolderConnection(this);
            // we need to make ourselves a handler of unsolicited responses
            currentConnection.addResponseHandler(this);
            // record our open mode
            this.mode = mode;


            try {
                // try to open, which gives us a lot of initial mailbox state.
                IMAPMailboxStatus status = currentConnection.openMailbox(fullname, mode == Folder.READ_ONLY);

                // not available in the requested mode?
                if (status.mode != mode) {
                    // trying to open READ_WRITE and this isn't available?
                    if (mode == READ_WRITE) {
                        throw new ReadOnlyFolderException(this, "Cannot open READ_ONLY folder in READ_WRITE mode");
                    }
                }

                // save this status and when we got it for later updating.
                cachedStatus = status;
                // mark when we got this
                lastStatusTimeStamp = System.currentTimeMillis();

                // now copy the status information over and flip over the open sign.
                this.mode = status.mode;
                maxSequenceNumber = status.messages;
                recentMessages = status.recentMessages;
                uidValidity = status.uidValidity;
                uidNext = status.uidNext;

                availableFlags = status.availableFlags;
                permanentFlags = status.permanentFlags;

                // create a our caches.  These are empty initially
                messageCache = new HashMap();
                uidCache = new HashMap();

                // we're open for business folks!
                folderOpen = true;
                notifyConnectionListeners(ConnectionEvent.OPENED);
            } finally {
                // NB:  this doesn't really release this, but it does drive
                // the processing of any unsolicited responses.
                releaseConnection(currentConnection);
            }
        }
	}


    /**
     * Close this folder; it must already be open.
     * A  @link ConnectionEvent#CLOSED} event is sent to all listeners registered
     {*
     * with this folder.
     *
     * @param expunge whether to expunge all deleted messages
     * @throws MessagingException if there was a problem accessing the store; the folder is still closed
     */
	public synchronized void close(boolean expunge) throws MessagingException {
		// Can only be performed on an open folder
		checkOpen();
        cleanupFolder(expunge, false);
	}


    /**
     * Do folder cleanup.  This is used both for normal
     * close operations, and adnormal closes where the
     * server has sent us a BYE message.
     *
     * @param expunge Indicates whether open messages should be expunged.
     * @param disconnected
     *                The disconnected flag.  If true, the server has cut
     *                us off, which means our connection can not be returned
     *                to the connection pool.
     *
     * @exception MessagingException
     */
    protected void cleanupFolder(boolean expunge, boolean disconnected) throws MessagingException {
		folderOpen = false;
        uidCache = null;
        messageCache = null;
        // if we have a connection active at the moment
        if (currentConnection != null) {
            // was this a forced disconnect by the server?
            if (disconnected) {
                currentConnection.setClosed();
            }
            else {
                // The CLOSE operation depends on what mode was used to select the mailbox.
                // If we're open in READ-WRITE mode, we used a SELECT operation.  When CLOSE
                // is issued, any deleted messages will be expunged.  If we've been asked not
                // to expunge the messages, we have a problem.  The solution is to reselect the
                // mailbox using EXAMINE, which will not expunge messages when closed.
                if (mode == READ_WRITE && !expunge) {
                    // we can ignore the result...we're just switching modes.
                    currentConnection.openMailbox(fullname, true);
                }

                // have this close the selected mailbox
                currentConnection.closeMailbox();
            }
            currentConnection.removeResponseHandler(this);
            // we need to release the connection to the Store once we're closed
            ((IMAPStore)store).releaseFolderConnection(this, currentConnection);
            currentConnection = null;
        }
		notifyConnectionListeners(ConnectionEvent.CLOSED);
    }


    /**
     * Tests the open status of the folder.
     *
     * @return true if the folder is open, false otherwise.
     */
	public boolean isOpen() {
		return folderOpen;
	}

    /**
     * Get the permanentFlags
     *
     * @return The set of permanent flags we support (only SEEN).
     */
	public synchronized Flags getPermanentFlags() {
        if (permanentFlags != null) {
            // we need a copy of our master set.
            return new Flags(permanentFlags);
        }
        else {
            // a null return is expected if not there.
            return null;
        }
	}


    /**
     * Return the number of messages this folder contains.
     * If this operation is invoked on a closed folder, the implementation
     * may choose to return -1 to avoid the expense of opening the folder.
     *
     * @return the number of messages, or -1 if unknown
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized int getMessageCount() throws MessagingException {
        checkFolderValidity();

        // if we haven't opened the folder yet, we might not have good status information.
        // go request some, which updates the folder fields also.
        refreshStatus(false);
		return maxSequenceNumber;
	}

    /**
     * Return the numbew of messages in this folder that have the {@link Flag.RECENT} flag set.
     * If this operation is invoked on a closed folder, the implementation
     * may choose to return -1 to avoid the expense of opening the folder.
     * The default implmentation of this method iterates over all messages
     * in the folder; subclasses should override if possible to provide a more
     * efficient implementation.
     *
     * NB:  This is an override of the default Folder implementation, which
     * examines each of the messages in the folder.  IMAP has more efficient
     * mechanisms for grabbing the information.
     *
     * @return the number of new messages, or -1 if unknown
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized int getNewMessageCount() throws MessagingException {
        // the folder must be a real one for this to work.
        checkFolderValidity();
        // now get current status from the folder
        refreshStatus(false);
        // this should be current now.
        return recentMessages;
    }



    /**
     * Return the number of messages in this folder that do not have the {@link Flag.SEEN} flag set.
     * If this operation is invoked on a closed folder, the implementation
     * may choose to return -1 to avoid the expense of opening the folder.
     * The default implmentation of this method iterates over all messages
     * in the folder; subclasses should override if possible to provide a more
     * efficient implementation.
     *
     * NB:  This is an override of the default Folder implementation, which
     * examines each of the messages in the folder.  IMAP has more efficient
     * mechanisms for grabbing the information.
     *
     * @return the number of new messages, or -1 if unknown
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized int getUnreadMessageCount() throws MessagingException {
        checkFolderValidity();
        // if we haven't opened the folder yet, we might not have good status information.
        // go request some, which updates the folder fields also.
        if (!folderOpen) {
            refreshStatus(false);
        }
        else {
            // if we have an open connection, then search the folder for any messages
            // marked UNSEEN.

            // UNSEEN is a false test on SEEN using the search criteria.
            SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.SEEN), false);

            // ask the store to kindly hook us up with a connection.
            IMAPConnection connection = getConnection();
            try {
                // search using the connection directly rather than calling our search() method so we don't
                // need to instantiate each of the matched messages.  We're really only interested in the count
                // right now.
                int[] matches = connection.searchMailbox(criteria);
                // update the unseen count.
                unseenMessages = matches == null ? 0 : matches.length;
            } finally {
                releaseConnection(connection);
            }
        }
        // return our current message count.
		return unseenMessages;
	}



    /**
     * Return the number of messages in this folder that have the {@link Flag.DELETED} flag set.
     * If this operation is invoked on a closed folder, the implementation
     * may choose to return -1 to avoid the expense of opening the folder.
     * The default implmentation of this method iterates over all messages
     * in the folder; subclasses should override if possible to provide a more
     * efficient implementation.
     *
     * @return the number of new messages, or -1 if unknown
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized int getDeletedMessageCount() throws MessagingException {
        checkFolderValidity();

        // if we haven't opened the folder yet, we might not have good status information.
        // go request some, which updates the folder fields also.
        if (!folderOpen) {
            // the status update doesn't return deleted messages.  These can only be obtained by
            // searching an open folder.  Just return a bail-out response
            return -1;
        }
        else {
            // if we have an open connection, then search the folder for any messages
            // marked DELETED.

            // UNSEEN is a false test on SEEN using the search criteria.
            SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.DELETED), true);

            // ask the store to kindly hook us up with a connection.
            IMAPConnection connection = getConnection();
            try {
                // search using the connection directly rather than calling our search() method so we don't
                // need to instantiate each of the matched messages.  We're really only interested in the count
                // right now.
                int[] matches = connection.searchMailbox(criteria);
                return matches == null ? 0 : matches.length;
            } finally {
                releaseConnection(connection);
            }
        }
	}


    /**
     * Retrieve the message with the specified index in this Folder;
     * messages indices start at 1 not zero.
     * Clients should note that the index for a specific message may change
     * if the folder is expunged; {@link Message} objects should be used as
     * references instead.
     *
     * @param msgNum The message sequence number of the target message.
     *
     * @return the message
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized Message getMessage(int msgNum) throws MessagingException {
        // Can only be performed on an Open folder
        checkOpen();
        // Check the validity of the message number.  This may require pinging the server to
        // see if there are new messages in the folder.
        checkMessageValidity(msgNum);
        // create the mapping key for this
        Integer messageKey = new Integer(msgNum);
        // ok, if the message number is within range, we should have this in the
        // messages list.  Just return the element.
        Message message = (Message)messageCache.get(messageKey);
        // if not in the cache, create a dummy add it in.  The message body will be
        // retrieved on demand
        if (message == null) {
            message = new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, msgNum);
            messageCache.put(messageKey, message);
        }
        return message;
    }


    /**
     * Retrieve a range of messages for this folder.
     * messages indices start at 1 not zero.
     *
     * @param start  Index of the first message to fetch, inclusive.
     * @param end    Index of the last message to fetch, inclusive.
     *
     * @return An array of the fetched messages.
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized Message[] getMessages(int start, int end) throws MessagingException {
        // Can only be performed on an Open folder
        checkOpen();
        Message[] messageRange = new Message[end - start + 1];

        for (int i = 0; i < messageRange.length; i++) {
            // NB:  getMessage() requires values that are origin 1, so there's
            // no need to adjust the value by other than the start position.
            messageRange[i] = getMessage(start + i);
        }
        return messageRange;
    }


    /**
     * Append the supplied messages to this folder. A {@link MessageCountEvent} is sent
     * to all listeners registered with this folder when all messages have been appended.
     * If the array contains a previously expunged message, it must be re-appended to the Store
     * and implementations must not abort this operation.
     *
     * @param msgs   The array of messages to append to the folder.
     *
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
	public synchronized void appendMessages(Message[] msgs) throws MessagingException {
        checkFolderValidity();
        for (int i = 0; i < msgs.length; i++) {
            Message msg = msgs[i];

            appendMessage(msg);
        }
	}

    /**
     * Hint to the store to prefetch information on the supplied messages.
     * Subclasses should override this method to provide an efficient implementation;
     * the default implementation in this class simply returns.
     *
     * @param messages messages for which information should be fetched
     * @param profile  the information to fetch
     * @throws MessagingException if there was a problem accessing the store
     * @see FetchProfile
     */
    public void fetch(Message[] messages, FetchProfile profile) throws MessagingException {

        // we might already have the information being requested, so ask each of the
        // messages in the list to evaluate itself against the profile.  We'll only ask
        // the server to send information that's required.
        List fetchSet = new ArrayList();

        for (int i = 0; i < messages.length; i++) {
            Message msg = messages[i];
            // the message is missing some of the information still.  Keep this in the list.
            // even if the message is only missing one piece of information, we still fetch everything.
            if (((IMAPMessage)msg).evaluateFetch(profile)) {
                fetchSet.add(msg);
            }
        }

        // we've got everything already, no sense bothering the server
        if (fetchSet.isEmpty()) {
            return;
        }
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();
        try {
            // ok, from this point onward, we don't want any threads messing with the
            // message cache.  A single processed EXPUNGE could make for a very bad day
            synchronized(this) {
                // get the message set for this
                String messageSet = generateMessageSet(fetchSet);
                // fetch all of the responses
                List responses = connection.fetch(messageSet, profile);

                // IMPORTANT:  We must do our updates while synchronized to keep the
                // cache from getting updated underneath us.   This includes
                // not releasing the connection until we're done to delay processing any
                // pending expunge responses.
                for (int i = 0; i < responses.size(); i++) {
                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
                    Message msg = getMessage(response.getSequenceNumber());
                    // Belt and Braces.  This should never be false.
                    if (msg != null) {
                        // have the message apply this to itself.
                        ((IMAPMessage)msg).updateMessageInformation(response);
                    }
                }
            }
        } finally {
            releaseConnection(connection);
        }
        return;
    }

    /**
     * Set flags on the messages to the supplied value; all messages must belong to this folder.
     * This method may be overridden by subclasses that can optimize the setting
     * of flags on multiple messages at once; the default implementation simply calls
     * {@link Message#setFlags(Flags, boolean)} for each supplied messages.
     *
     * @param messages whose flags should be set
     * @param flags    the set of flags to modify
     * @param set      Indicates whether the flags should be set or cleared.
     *
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public void setFlags(Message[] messages, Flags flags, boolean set) throws MessagingException {
        // this is a list of messages for the change broadcast after the update
        List updatedMessages = new ArrayList();

        synchronized(this) {
            // the folder must be open and writeable.
            checkOpenReadWrite();

            // now make sure these are settable flags.
            if (!availableFlags.contains(flags))
            {
                throw new MessagingException("The IMAP server does not support changing of this flag set");
            }

            // turn this into a set of message numbers
            String messageSet = generateMessageSet(messages);
            // if all of the messages have been expunged, nothing to do.
            if (messageSet == null) {
                return;
            }
            // ask the store to kindly hook us up with a connection.
            IMAPConnection connection = getConnection();

            try {
                // and have the connection set this
                List responses = connection.setFlags(messageSet, flags, set);
                // retrieve each of the messages from our cache, and do the flag update.
                // we need to keep the list so we can broadcast a change update event
                // when we're finished.
                for (int i = 0; i < responses.size(); i++) {
                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);

                    // get the updated message and update the internal state.
                    Message message = getMessage(response.sequenceNumber);
                    // this shouldn't happen, but it might have been expunged too.
                    if (message != null) {
                        ((IMAPMessage)message).updateMessageInformation(response);
                        updatedMessages.add(message);
                    }
                }
            } finally {
                releaseConnection(connection);
            }
        }

        // ok, we're no longer holding the lock.  Now go broadcast the update for each
        // of the affected messages.
        for (int i = 0; i < updatedMessages.size(); i++) {
            Message message = (Message)updatedMessages.get(i);
            notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
        }
    }


    /**
     * Set flags on a range of messages to the supplied value.
     * This method may be overridden by subclasses that can optimize the setting
     * of flags on multiple messages at once; the default implementation simply
     * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
     *
     * @param start  first message end set
     * @param end    last message end set
     * @param flags  the set of flags end modify
     * @param value  Indicates whether the flags should be set or cleared.
     *
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException {
        Message[] msgs = new Message[end - start + 1];

        for (int i = start; i <= end; i++) {
            msgs[i] = getMessage(i);
        }
        // go do a bulk set operation on these messages
        setFlags(msgs, flags, value);
    }

    /**
     * Set flags on a set of messages to the supplied value.
     * This method may be overridden by subclasses that can optimize the setting
     * of flags on multiple messages at once; the default implementation simply
     * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
     *
     * @param ids    the indexes of the messages to set
     * @param flags  the set of flags end modify
     * @param value  Indicates whether the flags should be set or cleared.
     *
     * @throws MessagingException
     *                if there was a problem accessing the store
     */
    public synchronized void setFlags(int ids[], Flags flags, boolean value) throws MessagingException {
        Message[] msgs = new Message[ids.length];

        for (int i = 0; i <ids.length; i++) {
            msgs[i] = getMessage(ids[i]);
        }
        // go do a bulk set operation on these messages
        setFlags(msgs, flags, value);
    }


    /**
     * Copy the specified messages to another folder.
     * The default implementation simply appends the supplied messages to the
     * target folder using {@link #appendMessages(Message[])}.
     * @param messages the messages to copy
     * @param folder the folder to copy to
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized void copyMessages(Message[] messages, Folder folder) throws MessagingException {
        // the default implementation just appends the messages to the target.  If
        // we're copying between two folders of the same store, we can get the server to
        // do most of the work for us without needing to fetch all of the message data.
        // If we're dealing with two different Store instances, we need to do this the
        // hardway.
        if (getStore() != folder.getStore()) {
            super.copyMessages(messages, folder);
            return;
        }

        // turn this into a set of message numbers
        String messageSet = generateMessageSet(messages);
        // if all of the messages have been expunged, nothing to do.
        if (messageSet == null) {
            return;
        }
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // ask the server to copy this information over to the other mailbox.
            connection.copyMessages(messageSet, folder.getFullName());
        } finally {
            releaseConnection(connection);
        }
    }



    /**
     * Permanently delete all supplied messages that have the DELETED flag set from the Store.
     * The original message indices of all messages actually deleted are returned and a
     * {@link MessageCountEvent} event is sent to all listeners with this folder. The expunge
     * may cause the indices of all messaged that remain in the folder to change.
     *
     * @return the original indices of messages that were actually deleted
     * @throws MessagingException if there was a problem accessing the store
     */
	public synchronized Message[] expunge() throws MessagingException {
        // must be open to do this.
        checkOpen();
        // and changes need to be allowed
        checkReadWrite();

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();
        List expunges = null;

        try {
            // send the expunge notification.  This operation results in "nn EXPUNGE" responses getting returned
            // for each expunged messages.  These will be dispatched to our response handler, which will process
            // the expunge operation.  We could process this directly, but we may have received asynchronous
            // expunge messages that also marked messages as expunged.
            expunges = connection.expungeMailbox();
        } finally {
            releaseConnection(connection);
        }

        // we get one EXPUNGE message for each message that's expunged.  They MUST be processed in
        // order, as the message sequence numbers represent a relative position that takes into account
        // previous expunge operations.  For example, if message sequence numbers 5, 6, and 7 are
        // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged.
        Message[] messages = new Message[expunges.size()];

        // now we need to protect the internal structures
        synchronized (this) {
            // expunge all of the messages from the message cache.  This keeps the sequence
            // numbers up to-date.
            for (int i = 0; i < expunges.size(); i++) {
                IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i);
                messages[i] = expungeMessage(response.getSize());
            }
        }
        // if we have messages that have been removed, broadcast the notification.
        if (messages.length > 0) {
            notifyMessageRemovedListeners(true, messages);
        }

        // note, we're expected to return an array in all cases, even if the expunged count was zero.
        return messages;
	}



    /**
     * Search the supplied messages for those that match the supplied criteria;
     * messages must belong to this folder.
     * The default implementation iterates through the messages, returning those
     * whose {@link Message#match(SearchTerm)} method returns true;
     * subclasses may provide a more efficient implementation.
     *
     * @param term the search criteria
     * @param messages the messages to search
     * @return an array containing messages that match the criteria
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized Message[] search(SearchTerm term) throws MessagingException {
        // only allowed on open folders
        checkOpen();

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // just search everything
            int[] messageNumbers = connection.searchMailbox(term);
            return resolveMessages(messageNumbers);
        } finally {
            releaseConnection(connection);
        }
    }



    /**
     * Search the supplied messages for those that match the supplied criteria;
     * messages must belong to this folder.
     * The default implementation iterates through the messages, returning those
     * whose {@link Message#match(SearchTerm)} method returns true;
     * subclasses may provide a more efficient implementation.
     *
     * @param term the search criteria
     * @param messages the messages to search
     * @return an array containing messages that match the criteria
     * @throws MessagingException if there was a problem accessing the store
     */
    public synchronized Message[] search(SearchTerm term, Message[] messages) throws MessagingException {
        // only allowed on open folders
        checkOpen();

        // turn this into a string specifier for these messages.  We'll weed out the expunged messages first.
        String messageSet = generateMessageSet(messages);

        // If we have no "live" messages to search, just return now.  We're required to return a non-null
        // value, so give an empy array back.
        if (messageSet == null) {
            return new Message[0];
        }

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {

            // now go do the search.
            int[] messageNumbers = connection.searchMailbox(messageSet, term);
            return resolveMessages(messageNumbers);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Get the UID validity value for this Folder.
     *
     * @return The current UID validity value, as a long.
     * @exception MessagingException
     */
    public synchronized long getUIDValidity() throws MessagingException
    {
        // get the latest status to make sure we have the
        // most current.
        refreshStatus(true);
        return uidValidity;
    }

    /**
     * Retrieve a message using the UID rather than the
     * message sequence number.  Returns null if the message
     * doesn't exist.
     *
     * @param uid    The target UID.
     *
     * @return the Message object.  Returns null if the message does
     *         not exist.
     * @exception MessagingException
     */
    public synchronized Message getMessageByUID(long uid) throws MessagingException
    {
        // only allowed on open folders
        checkOpen();

        Long key = new Long(uid);
        // first check to see if we have a cached value for this
        synchronized(messageCache) {
            Message msg = (Message)uidCache.get(key);
            if (msg != null) {
                return msg;
            }
        }

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // locate the message identifier
            IMAPUid imapuid = connection.getSequenceNumberForUid(uid);
            // if nothing is returned, the message doesn't exist
            if (imapuid == null) {
                return null;
            }


            // retrieve the actual message object and place this in the UID cache
            return retrieveMessageByUid(key, imapuid.messageNumber);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Get a series of messages using a UID range.  The
     * special value LASTUID can be used to mark the
     * last available message.
     *
     * @param start  The start of the UID range.
     * @param end    The end of the UID range.  The special value
     *               LASTUID can be used to request all messages up
     *               to the last UID.
     *
     * @return An array containing all of the messages in the
     *         range.
     * @exception MessagingException
     */
    public synchronized Message[] getMessagesByUID(long start, long end) throws MessagingException
    {
        // only allowed on open folders
        checkOpen();
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // locate the message identifier
            List uids = connection.getSequenceNumbersForUids(start, end);
            Message[] msgs = new Message[uids.size()];

            // fill in each of the messages based on the returned value
            for (int i = 0; i < msgs.length; i++) {
                IMAPUid uid = (IMAPUid)uids.get(i);
                msgs[i] = retrieveMessageByUid(new Long(uid.uid), uid.messageNumber);
            }

            return msgs;
        } finally {
            releaseConnection(connection);
        }


    }

    /**
     * Retrieve a set of messages by explicit UIDs.  If
     * any message in the list does not exist, null
     * will be returned for the corresponding item.
     *
     * @param ids    An array of UID values to be retrieved.
     *
     * @return An array of Message items the same size as the ids
     *         argument array.  This array will contain null
     *         entries for any UIDs that do not exist.
     * @exception MessagingException
     */
    public synchronized Message[] getMessagesByUID(long[] ids) throws MessagingException
    {
        // only allowed on open folders
        checkOpen();

        Message[] msgs = new Message[ids.length];

        for (int i = 0; i < msgs.length; i++) {
            msgs[i] = getMessageByUID(ids[i]);
        }

        return msgs;
    }

    /**
     * Retrieve the UID for a message from this Folder.
     * The argument Message MUST belong to this Folder
     * instance, otherwise a NoSuchElementException will
     * be thrown.
     *
     * @param message The target message.
     *
     * @return The UID associated with this message.
     * @exception MessagingException
     */
    public synchronized long getUID(Message message) throws MessagingException
    {
        // verify this actually is in this folder.
        checkMessageFolder(message);
        IMAPMessage msg = (IMAPMessage)message;

        // we might already know this bit of information
        if (msg.getUID() != -1) {
            return msg.getUID();
        }

        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // locate the message identifier
            IMAPUid imapuid = connection.getUidForSequenceNumber(msg.getMessageNumber());
            // if nothing is returned, the message doesn't exist
            if (imapuid == null) {
                return -1;
            }
            // cache this information now that we've gotten it.
            addToUidCache(new Long(imapuid.uid), getMessage(imapuid.messageNumber));
            // return the UID information.
            return imapuid.uid;
        } finally {
            releaseConnection(connection);
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



