geronimo-javamail_1.5/geronimo-javamail_1.5_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java [1503:2390]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    protected synchronized Message retrieveMessageByUid(Long key, int msgNumber) throws MessagingException
    {
        synchronized (messageCache) {
            // first check the cache...this might have already been added.
            Message msg = (Message)uidCache.get(key);
            if (msg != null) {
                return msg;
            }

            // retrieve the message by sequence number
            msg = getMessage(msgNumber);
            // add this to our UID mapping cache.
            addToUidCache(key, msg);
            return msg;
        }
    }


    /**
     * Add a message to the UID mapping cache, ensuring that
     * the UID value is updated.
     *
     * @param key    The UID key.
     * @param msg    The message to add.
     */
    protected void addToUidCache(Long key, Message msg) {
        synchronized (messageCache) {
            ((IMAPMessage)msg).setUID(key.longValue());
            uidCache.put(key, msg);
        }
    }


    /**
     * Append a single message to the IMAP Folder.
     *
     * @param msg    The message to append.
     *
     * @exception MessagingException
     */
    protected synchronized void appendMessage(Message msg) throws MessagingException
    {
        // sort out the dates.  If no received date, use the sent date.
        Date date = msg.getReceivedDate();
        if (date == null) {
            date = msg.getSentDate();
        }

        Flags flags = msg.getFlags();

        // convert the message into an array of bytes we can attach as a literal.
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try {
            msg.writeTo(out);
        } catch (IOException e) {
        }

        // now issue the append command
        IMAPConnection connection = getConnection();
        try {
            connection.appendMessage(getFullName(), date, flags, out.toByteArray());
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     * Retrieve the list of matching groups from the IMAP server using the LIST
     * or LSUB command. The server does the wildcard matching for us.
     *
     * @param pattern
     *            The pattern string (in wildmat format) used to match.
     *
     * @return An array of folders for the matching groups.
     */
    protected synchronized Folder[] filterFolders(String pattern, boolean subscribed) throws MessagingException {
        IMAPConnection connection = getConnection();
        // this is used to filter out our own folder from the search
        String root = fullname + getSeparator();

        List responses = null;
        try {


            if (subscribed) {
                // get the lsub response for this folder.
                responses = connection.listSubscribed(root, pattern);
            }
            else {
                // grab using the LIST command.
                responses = connection.list(root, pattern);
            }
        } finally {
            releaseConnection(connection);
        }

        List folders = new ArrayList();

        for (int i = 0; i < responses.size(); i++) {
            IMAPListResponse response = (IMAPListResponse)responses.get(i);
            // if a full wildcard is specified, the root folder can be returned too.  Make sure we
            // filter that one out.
            if (!response.mailboxName.equals(root)) {
                IMAPFolder folder = new IMAPFolder((IMAPStore)store, response.mailboxName, response.separator);
                folders.add(folder);
            }
        }

        // convert into an array and return
        return (Folder[])folders.toArray(new Folder[folders.size()]);
    }


    /**
     * Test if a folder can hold sub folders.
     *
     * @return True if the folder is allowed to have subfolders.
     */
    protected synchronized boolean holdsFolders() throws MessagingException {
        checkFolderValidity();
        return (folderType & HOLDS_FOLDERS) != 0;
    }


    /**
     * Validate that a target message number is considered valid
     * by the IMAP server.  If outside of the range we currently
     * are a ware of, we'll ping the IMAP server to see if there
     * have been any updates.
     *
     * @param messageNumber
     *               The message number we're checking.
     *
     * @exception MessagingException
     */
    protected void checkMessageValidity(int messageNumber) throws MessagingException {
        // lower range for a message is 1.
        if (messageNumber < 1) {
            throw new MessagingException("Invalid message number for IMAP folder: " + messageNumber);
        }
        // if within our current known range, we'll accept this
        if (messageNumber <= maxSequenceNumber) {
            return;
        }

        IMAPConnection connection = getConnection();

        synchronized (this) {
            try {
                // ping the server to see if there's any updates to process.  The updates are handled
                // by the response handlers.
                connection.updateMailboxStatus();
            } finally {
                releaseConnection(connection);
            }
        }

        // still out of range?
        if (messageNumber > maxSequenceNumber) {
            throw new MessagingException("Message " + messageNumber + " does not exist on server");
        }
    }


	/**
	 * Below is a list of convenience methods that avoid repeated checking for a
	 * value and throwing an exception
	 */

    /**
     * Ensure the folder is open.  Throws a MessagingException
     * if not in the correct state for the operation.
     *
     * @exception IllegalStateException
     */
    protected void checkOpen() throws IllegalStateException {
		if (!folderOpen){
		    throw new IllegalStateException("Folder is not Open");
		}
    }

    /**
     * Ensure the folder is not open for operations
     * that require the folder to be closed.
     *
     * @exception IllegalStateException
     */
    protected void checkClosed() throws IllegalStateException {
		if (folderOpen){
		    throw new IllegalStateException("Folder is Open");
		}
    }

    /**
     * Ensure that the folder is open for read/write mode before doing
     * an operation that would make a change.
     *
     * @exception IllegalStateException
     */
    protected void checkReadWrite() throws IllegalStateException {
        if (mode != READ_WRITE) {
		    throw new IllegalStateException("Folder is opened READY_ONLY");
        }
    }


    /**
     * Check that the folder is open and in read/write mode.
     *
     * @exception IllegalStateException
     */
    protected void checkOpenReadWrite() throws IllegalStateException {
        checkOpen();
        checkReadWrite();
    }



    /**
     * Notify the message changed listeners that a
     * message contained in the folder has been updated.
     *
     * @param type   The type of update made to the message.
     * @param m      The message that was updated.
     *
     * @see javax.mail.Folder#notifyMessageChangedListeners(int, javax.mail.Message)
     */
    public void notifyMessageChangedListeners(int type, Message m) {
    	super.notifyMessageChangedListeners(type, m);
    }


    /**
     * Retrieve the connection attached to this folder.  Throws an
     * exception if we don't have an active connection.
     *
     * @return The current connection object.
     * @exception MessagingException
     */
    protected synchronized IMAPConnection getConnection() throws MessagingException {
        // don't have an open connection yet?  Just request a pool connection.
        if (currentConnection == null) {
            // request a connection from the central store.
            IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);
            // we need to make ourselves a handler of unsolicited responses
            connection.addResponseHandler(this);
            return connection;
        }
        // we have a connection for our use.  Just return it.
        return currentConnection;
    }


    /**
     * Release our connection back to the Store.
     *
     * @param connection The connection to release.
     *
     * @exception MessagingException
     */
    protected void releaseConnection(IMAPConnection connection) throws MessagingException {
        // This is a bit of a pain.  We need to delay processing of the
        // unsolicited responses until after each user of the connection has
        // finished processing the expected responses.  We need to do this because
        // the unsolicited responses may include EXPUNGED messages.  The EXPUNGED
        // messages will alter the message sequence numbers for the messages in the
        // cache.  Processing the EXPUNGED messages too early will result in
        // updates getting applied to the wrong message instances.  So, as a result,
        // we delay that stage of the processing until all expected responses have
        // been handled.

        // process any pending messages before returning.
        connection.processPendingResponses();
        // if no cached connection or this is somehow different from the cached one, just
        // return it.
        if (currentConnection == null || connection != currentConnection) {
            connection.removeResponseHandler(this);
            ((IMAPStore)store).releaseFolderConnection(this, connection);
        }
        // if we're open, then we don't have to worry about returning this connection
        // to the Store.  This is set up perfectly for our use right now.
    }


    /**
     * Obtain a connection object for a Message attached to this Folder.  This
     * will be the Folder's connection, which is only available if the Folder
     * is currently open.
     *
     * @return The connection object for the Message instance to use.
     * @exception MessagingException
     */
    synchronized IMAPConnection getMessageConnection() throws MessagingException {
        // if we're not open, the messages can't communicate either
        if (currentConnection == null) {
            throw new FolderClosedException(this, "No Folder connections available");
        }
        // return the current Folder connection.  At this point, we'll be sharing the
        // connection between the Folder and the Message (and potentially, other messages).  The
        // command operations on the connection are synchronized so only a single command can be
        // issued at one time.
        return currentConnection;
    }


    /**
     * Release the connection object back to the Folder instance.
     *
     * @param connection The connection being released.
     *
     * @exception MessagingException
     */
    void releaseMessageConnection(IMAPConnection connection) throws MessagingException {
        // release it back to ourselves...this will drive unsolicited message processing.
        releaseConnection(connection);
    }


    /**
     * Refresh the status information on this folder.
     *
     * @param force  Force a status refresh always.
     *
     * @exception MessagingException
     */
    protected void refreshStatus(boolean force) throws MessagingException {
        // first check that any cached status we've received has gotten a little moldy.
        if (cachedStatus != null) {
            // if not forcing, check the time out.
            if (!force) {
                if (statusCacheTimeout > 0) {
                    long age = System.currentTimeMillis() - lastStatusTimeStamp;
                    if (age < statusCacheTimeout) {
                        return;
                    }
                }
            }
            // make sure the stale information is cleared out.
            cachedStatus = null;
        }

        IMAPConnection connection = getConnection();
        try {
            // ping the server for the list information for this folder
            cachedStatus = connection.getMailboxStatus(fullname);
            // mark when we got this
            lastStatusTimeStamp = System.currentTimeMillis();
        } finally {
            releaseConnection(connection);
        }

        // refresh the internal state from the message information
        maxSequenceNumber = cachedStatus.messages;
        recentMessages = cachedStatus.recentMessages;
        unseenMessages = cachedStatus.unseenMessages;
        uidValidity = cachedStatus.uidValidity;
    }


    /**
     * Process an EXPUNGE response for a message, removing the
     * message from the message cache.
     *
     * @param sequenceNumber
     *               The sequence number for the expunged message.
     *
     * @return The Message object corresponding to this expunged
     *         message.
     * @exception MessagingException
     */
    protected synchronized Message expungeMessage(int sequenceNumber) throws MessagingException {

        // first process the expunged message.  We need to return a Message instance, so
        // force this to be added to the cache
        IMAPMessage expungedMessage = (IMAPMessage)getMessage(sequenceNumber);
        // mark the message as expunged.
        expungedMessage.setExpunged(true);
        // have we retrieved a UID for this message?  If we have, then it's in the UID cache and
        // needs removal from there also
        long uid = ((IMAPMessage)expungedMessage).getUID();
        if (uid >= 0) {
            uidCache.remove(new Long(uid));
        }
        // because we need to jigger the keys of some of these, we had better have a working
        // copy.
        Map newCache = new HashMap();

        // now process each message in the cache, making adjustments as necessary
        Iterator i = messageCache.keySet().iterator();

        while (i.hasNext()) {
            Integer key = (Integer)i.next();
            int index = key.intValue();
            // if before the expunged message, just copy over to the
            // new cache
            if (index < sequenceNumber) {
                newCache.put(key, messageCache.get(key));
            }
            // after the expunged message...we need to adjust this
            else if (index > sequenceNumber) {
                // retrieve the message using the current position,
                // adjust the message sequence number, and add to the new
                // message cache under the new key value
                IMAPMessage message = (IMAPMessage)messageCache.get(key);
                message.setSequenceNumber(index - 1);
                newCache.put(new Integer(index - 1), message);
            }
            else {
                // the expunged message.  We don't move this over to the new
                // cache, and we've already done all processing of that message that's
                // required
            }
        }

        // replace the old cache now that everything has been adjusted
        messageCache = newCache;

        // adjust the message count downward
        maxSequenceNumber--;
        return expungedMessage;
    }


    /**
     * Resolve an array of message numbers into an array of the
     * referenced messages.
     *
     * @param messageNumbers
     *               The array of message numbers (can be null).
     *
     * @return An array of Message[] containing the resolved messages from
     *         the list.  Returns a zero-length array if there are no
     *         messages to resolve.
     * @exception MessagingException
     */
    protected Message[] resolveMessages(int[] messageNumbers) throws MessagingException {
        // the connection search returns a null pointer if nothing was found, just convert this into a
        // null array.
        if (messageNumbers == null) {
            return new Message[0];
        }

        Message[] messages = new Message[messageNumbers.length];

        // retrieve each of the message numbers in turn.
        for (int i = 0; i < messageNumbers.length; i++) {
            messages[i] = getMessage(messageNumbers[i]);
        }

        return messages;
    }

    /**
     * Generate a message set string from a List of messages rather than an
     * array.
     *
     * @param messages The List of messages.
     *
     * @return The evaluated message set string.
     * @exception MessagingException
     */
    protected String generateMessageSet(List messages) throws MessagingException {
        Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]);
        return generateMessageSet(msgs);
    }


    /**
     * Take an array of messages and generate a String <message set>
     * argument as specified by RFC 2060.  The message set argument
     * is a comma-separated list of message number ranges.  A
     * single element range is just one number.  A longer range is
     * a pair of numbers separated by a ":".  The generated string
     * should not have any blanks.  This will attempt to locate
     * consequetive ranges of message numbers, but will only do this
     * for messages that are already ordered in the array (i.e., we
     * don't try to sort).  Expunged messages are excluded from the
     * search, since they don't exist anymore.  A valid search string
     * will look something like this:
     *
     *    "3,6:10,15,21:35"
     *
     * @param messages The array of messages we generate from.
     *
     * @return A string formatted version of these message identifiers that
     *         can be used on an IMAP command.
     */
    protected String generateMessageSet(Message[] messages) throws MessagingException {
        StringBuffer set = new StringBuffer();

        for (int i = 0; i < messages.length; i++) {
            // first scan the list looking for a "live" message.
            IMAPMessage start = (IMAPMessage)messages[i];
            if (!start.isExpunged()) {

                // we can go ahead and add this to the list now.  If we find this is the start of a
                // range, we'll tack on the ":end" bit once we find the last message in the range.
                if (set.length() != 0) {
                    // only append the comma if not the first element of the list
                    set.append(',');
                }

                // append the first number.  NOTE:  We append this directly rather than
                // use appendInteger(), which appends it using atom rules.
                set.append(Integer.toString(start.getSequenceNumber()));

                // ok, we have a live one.  Now scan the list from here looking for the end of
                // a range of consequetive messages.
                int endIndex = -1; ;
                // get the number we're checking against.
                int previousSequence = start.getSequenceNumber();
                for (int j = i + 1; j < messages.length; j++) {
                    IMAPMessage message = (IMAPMessage)messages[j];
                    if (!message.isExpunged()) {
                        // still consequetive?
                        if (message.getSequenceNumber() == previousSequence + 1) {
                            // step this for the next check.
                            previousSequence++;
                            // record this as the current end of the range.
                            endIndex = j;
                        }
                        else {
                            // found a non-consequetive one, stop here
                            break;
                        }
                    }
                }

                // have a range end point?  Add the range specifier and step the loop index point
                // to skip over this
                if (endIndex != -1) {
                    // pick up the scan at the next location
                    i = endIndex;

                    set.append(':');
                    set.append(Integer.toString(((IMAPMessage)messages[endIndex]).getSequenceNumber()));
                }
            }
        }

        // return null for an empty list. This is possible because either an empty array has been handed to
        // us or all of the messages in the array have been expunged.
        if (set.length() == 0) {
            return null;
        }
        return set.toString();
    }

    /**
     * Verify that this folder exists on the server before
     * performning an operation that requires a valid
     * Folder instance.
     *
     * @exception MessagingException
     */
    protected void checkFolderValidity() throws MessagingException {
        // if we are holding a current listinfo response, then
        // we have chached existance information.  In that case,
        // all of our status is presumed up-to-date and we can go
        // with that.  If we don't have the information, then we
        // ping the server for it.
        if (listInfo == null && !exists()) {
            throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server");
        }
    }


    /**
     * Check if a Message is properly within the target
     * folder.
     *
     * @param msg    The message we're checking.
     *
     * @exception MessagingException
     */
    protected void checkMessageFolder(Message msg) throws MessagingException {
        if (msg.getFolder() != this) {
            throw new NoSuchElementException("Message is not within the target Folder");
        }
    }


    /**
     * Search a list of LIST responses for one containing information
     * for a particular mailbox name.
     *
     * @param responses The list of responses.
     * @param name      The desired mailbox name.
     *
     * @return The IMAPListResponse information for the requested name.
     */
    protected IMAPListResponse findListResponse(List responses, String name) {
        for (int i = 0; i < responses.size(); i++) {
            IMAPListResponse response = (IMAPListResponse)responses.get(i);
            if (response.mailboxName.equals(name)) {
                return response;
            }
        }
        return null;
    }


    /**
     * Protected class intended for subclass overrides.  For normal folders,
     * the mailbox name is fullname.  For Namespace root folders, the mailbox
     * name is the prefix + separator.
     *
     * @return The string name to use as the mailbox name for exists() and issubscribed()
     *         calls.
     */
    protected String getMailBoxName() {
        return fullname;
    }

    /**
     * Handle an unsolicited response from the server.  Most unsolicited responses
     * are replies to specific commands sent to the server.  The remainder must
     * be handled by the Store or the Folder using the connection.  These are
     * critical to handle, as events such as expunged messages will alter the
     * sequence numbers of the live messages.  We need to keep things in sync.
     *
     * @param response The UntaggedResponse to process.
     *
     * @return true if we handled this response and no further handling is required.  false
     *         means this one wasn't one of ours.
     */
    public boolean handleResponse(IMAPUntaggedResponse response) {
        // "you've got mail".  The message count has been updated.  There
        // are two posibilities.  Either there really are new messages, or
        // this is an update following an expunge.  If there are new messages,
        // we need to update the message cache and broadcast the change to
        // any listeners.
        if (response.isKeyword("EXISTS")) {
            // we need to update our cache, and also retrieve the new messages and
            // send them out in a broadcast update.
            int oldCount = maxSequenceNumber;
            maxSequenceNumber = ((IMAPSizeResponse)response).getSize();
            // has the size grown?  We have to send the "you've got mail" announcement.
            if (oldCount < maxSequenceNumber) {
                try {
                    Message[] messages = getMessages(oldCount + 1, maxSequenceNumber);
                    notifyMessageAddedListeners(messages);
                } catch (MessagingException e) {
                    // should never happen in this context
                }
            }
            return true;
        }
        // "you had mail".  A message was expunged from the server.  This MUST
        // be processed immediately, as any subsequent expunge messages will
        // shift the message numbers as a result of previous messages getting
        // removed.  We need to keep our internal cache in sync with the server.
        else if (response.isKeyword("EXPUNGE")) {
            int messageNumber = ((IMAPSizeResponse)response).getSize();
            try {
                Message message = expungeMessage(messageNumber);

                // broadcast the message update.
                notifyMessageRemovedListeners(false, new Message[] {message});
            } catch (MessagingException e) {
            }
            // we handled this one.
            return true;
        }
        // just an update of recently arrived stuff?  Just update the field.
        else if (response.isKeyword("RECENT")) {
            recentMessages = ((IMAPSizeResponse)response).getSize();
            return true;
        }
        // The spec is not particularly clear what types of unsolicited
        // FETCH response can be sent.  The only one that is specifically
        // spelled out is flag updates.  If this is one of those, then
        // handle it.
        else if (response.isKeyword("FETCH")) {
            IMAPFetchResponse fetch = (IMAPFetchResponse)response;
            IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS);
            // if this is a flags response, get the message and update
            if (flags != null) {
                try {
                    // get the updated message and update the internal state.
                    IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber);
                    // this shouldn't happen, but it might have been expunged too.
                    if (message != null) {
                        message.updateMessageInformation(fetch);
                    }
                    notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
                } catch (MessagingException e) {
                }
                return true;
            }
        }
        // this is a BYE response on our connection.  This forces us to close, but
        // when we return the connection, the pool needs to get rid of it.
        else if (response.isKeyword("BYE")) {
            // this is essentially a close event.  We need to clean everything up
            // and make sure our connection is not returned to the general pool.
            try {
                cleanupFolder(false, true);
            } catch (MessagingException e) {
            }
            return true;
        }

        // not a response the folder knows how to deal with.
        return false;
    }

// The following set of methods are extensions that exist in the Sun implementation.  They
// match the Sun version in intent, but are not 100% compatible because the Sun implementation
// uses com.sun.* class instances as opposed to the org.apache.geronimo.* classes.



    /**
     *   Remove an entry from the access control list for this folder.
     *
     * @param acl    The ACL element to remove.
     *
     * @exception MessagingException
     */
    public synchronized void removeACL(ACL acl) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.removeACLRights(fullname, acl);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     *   Add an entry to the access control list for this folder.
     *
     * @param acl    The new ACL to add.
     */
    public synchronized void addACL(ACL acl) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.setACLRights(fullname, acl);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     * Add Rights to a given ACL entry.
     *
     * @param acl    The target ACL to update.
     *
     * @exception MessagingException
     */
    public synchronized void addRights(ACL acl) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.addACLRights(fullname, acl);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     * Remove ACL Rights from a folder.
     *
     * @param acl    The ACL describing the Rights to remove.
     *
     * @exception MessagingException
     */
    public synchronized void removeRights(ACL acl) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.removeACLRights(fullname, acl);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     *   List the rights associated with a given name.
     *
     * @param name   The user name for the Rights.
     *
     * @return The set of Rights associated with the user name.
     * @exception MessagingException
     */
    public synchronized Rights[] listRights(String name) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            return connection.listACLRights(fullname, name);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     *   List the rights for the currently authenticated user.
     *
     * @return The set of Rights for the current user.
     * @exception MessagingException
     */
    public synchronized Rights myRights() throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            return connection.getMyRights(fullname);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Get the quota values assigned to the current folder.
     *
     * @return The Quota information for the folder.
     * @exception MessagingException
     */
    public synchronized Quota[] getQuota() throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            return connection.fetchQuotaRoot(fullname);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Set the quota value for a quota root
     *
     * @param quota  The new quota information to set.
     *
     * @exception MessagingException
     */
    public synchronized void setQuota(Quota quota) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.setQuota(quota);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Get the set of attributes defined for the folder
     * as the set of capabilities returned when the folder
     * was opened.
     *
     * @return The set of attributes associated with the folder.
     * @exception MessagingException
     */
    public synchronized String[] getAttributes() throws MessagingException {
        // if we don't have the LIST command information for this folder yet,
        // call exists() to force this to be updated so we can return.
        if (listInfo == null) {
            // return a null reference if this is not valid.
            if (!exists()) {
                return null;
            }
        }
        // return a copy of the attributes array.
        return (String[])listInfo.attributes.clone();
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java [1503:2390]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    protected synchronized Message retrieveMessageByUid(Long key, int msgNumber) throws MessagingException
    {
        synchronized (messageCache) {
            // first check the cache...this might have already been added.
            Message msg = (Message)uidCache.get(key);
            if (msg != null) {
                return msg;
            }

            // retrieve the message by sequence number
            msg = getMessage(msgNumber);
            // add this to our UID mapping cache.
            addToUidCache(key, msg);
            return msg;
        }
    }


    /**
     * Add a message to the UID mapping cache, ensuring that
     * the UID value is updated.
     *
     * @param key    The UID key.
     * @param msg    The message to add.
     */
    protected void addToUidCache(Long key, Message msg) {
        synchronized (messageCache) {
            ((IMAPMessage)msg).setUID(key.longValue());
            uidCache.put(key, msg);
        }
    }


    /**
     * Append a single message to the IMAP Folder.
     *
     * @param msg    The message to append.
     *
     * @exception MessagingException
     */
    protected synchronized void appendMessage(Message msg) throws MessagingException
    {
        // sort out the dates.  If no received date, use the sent date.
        Date date = msg.getReceivedDate();
        if (date == null) {
            date = msg.getSentDate();
        }

        Flags flags = msg.getFlags();

        // convert the message into an array of bytes we can attach as a literal.
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try {
            msg.writeTo(out);
        } catch (IOException e) {
        }

        // now issue the append command
        IMAPConnection connection = getConnection();
        try {
            connection.appendMessage(getFullName(), date, flags, out.toByteArray());
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     * Retrieve the list of matching groups from the IMAP server using the LIST
     * or LSUB command. The server does the wildcard matching for us.
     *
     * @param pattern
     *            The pattern string (in wildmat format) used to match.
     *
     * @return An array of folders for the matching groups.
     */
    protected synchronized Folder[] filterFolders(String pattern, boolean subscribed) throws MessagingException {
        IMAPConnection connection = getConnection();
        // this is used to filter out our own folder from the search
        String root = fullname + getSeparator();

        List responses = null;
        try {


            if (subscribed) {
                // get the lsub response for this folder.
                responses = connection.listSubscribed(root, pattern);
            }
            else {
                // grab using the LIST command.
                responses = connection.list(root, pattern);
            }
        } finally {
            releaseConnection(connection);
        }

        List folders = new ArrayList();

        for (int i = 0; i < responses.size(); i++) {
            IMAPListResponse response = (IMAPListResponse)responses.get(i);
            // if a full wildcard is specified, the root folder can be returned too.  Make sure we
            // filter that one out.
            if (!response.mailboxName.equals(root)) {
                IMAPFolder folder = new IMAPFolder((IMAPStore)store, response.mailboxName, response.separator);
                folders.add(folder);
            }
        }

        // convert into an array and return
        return (Folder[])folders.toArray(new Folder[folders.size()]);
    }


    /**
     * Test if a folder can hold sub folders.
     *
     * @return True if the folder is allowed to have subfolders.
     */
    protected synchronized boolean holdsFolders() throws MessagingException {
        checkFolderValidity();
        return (folderType & HOLDS_FOLDERS) != 0;
    }


    /**
     * Validate that a target message number is considered valid
     * by the IMAP server.  If outside of the range we currently
     * are a ware of, we'll ping the IMAP server to see if there
     * have been any updates.
     *
     * @param messageNumber
     *               The message number we're checking.
     *
     * @exception MessagingException
     */
    protected void checkMessageValidity(int messageNumber) throws MessagingException {
        // lower range for a message is 1.
        if (messageNumber < 1) {
            throw new MessagingException("Invalid message number for IMAP folder: " + messageNumber);
        }
        // if within our current known range, we'll accept this
        if (messageNumber <= maxSequenceNumber) {
            return;
        }

        IMAPConnection connection = getConnection();

        synchronized (this) {
            try {
                // ping the server to see if there's any updates to process.  The updates are handled
                // by the response handlers.
                connection.updateMailboxStatus();
            } finally {
                releaseConnection(connection);
            }
        }

        // still out of range?
        if (messageNumber > maxSequenceNumber) {
            throw new MessagingException("Message " + messageNumber + " does not exist on server");
        }
    }


	/**
	 * Below is a list of convenience methods that avoid repeated checking for a
	 * value and throwing an exception
	 */

    /**
     * Ensure the folder is open.  Throws a MessagingException
     * if not in the correct state for the operation.
     *
     * @exception IllegalStateException
     */
    protected void checkOpen() throws IllegalStateException {
		if (!folderOpen){
		    throw new IllegalStateException("Folder is not Open");
		}
    }

    /**
     * Ensure the folder is not open for operations
     * that require the folder to be closed.
     *
     * @exception IllegalStateException
     */
    protected void checkClosed() throws IllegalStateException {
		if (folderOpen){
		    throw new IllegalStateException("Folder is Open");
		}
    }

    /**
     * Ensure that the folder is open for read/write mode before doing
     * an operation that would make a change.
     *
     * @exception IllegalStateException
     */
    protected void checkReadWrite() throws IllegalStateException {
        if (mode != READ_WRITE) {
		    throw new IllegalStateException("Folder is opened READY_ONLY");
        }
    }


    /**
     * Check that the folder is open and in read/write mode.
     *
     * @exception IllegalStateException
     */
    protected void checkOpenReadWrite() throws IllegalStateException {
        checkOpen();
        checkReadWrite();
    }



    /**
     * Notify the message changed listeners that a
     * message contained in the folder has been updated.
     *
     * @param type   The type of update made to the message.
     * @param m      The message that was updated.
     *
     * @see Folder#notifyMessageChangedListeners(int, Message)
     */
    public void notifyMessageChangedListeners(int type, Message m) {
    	super.notifyMessageChangedListeners(type, m);
    }


    /**
     * Retrieve the connection attached to this folder.  Throws an
     * exception if we don't have an active connection.
     *
     * @return The current connection object.
     * @exception MessagingException
     */
    protected synchronized IMAPConnection getConnection() throws MessagingException {
        // don't have an open connection yet?  Just request a pool connection.
        if (currentConnection == null) {
            // request a connection from the central store.
            IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);
            // we need to make ourselves a handler of unsolicited responses
            connection.addResponseHandler(this);
            return connection;
        }
        // we have a connection for our use.  Just return it.
        return currentConnection;
    }


    /**
     * Release our connection back to the Store.
     *
     * @param connection The connection to release.
     *
     * @exception MessagingException
     */
    protected void releaseConnection(IMAPConnection connection) throws MessagingException {
        // This is a bit of a pain.  We need to delay processing of the
        // unsolicited responses until after each user of the connection has
        // finished processing the expected responses.  We need to do this because
        // the unsolicited responses may include EXPUNGED messages.  The EXPUNGED
        // messages will alter the message sequence numbers for the messages in the
        // cache.  Processing the EXPUNGED messages too early will result in
        // updates getting applied to the wrong message instances.  So, as a result,
        // we delay that stage of the processing until all expected responses have
        // been handled.

        // process any pending messages before returning.
        connection.processPendingResponses();
        // if no cached connection or this is somehow different from the cached one, just
        // return it.
        if (currentConnection == null || connection != currentConnection) {
            connection.removeResponseHandler(this);
            ((IMAPStore)store).releaseFolderConnection(this, connection);
        }
        // if we're open, then we don't have to worry about returning this connection
        // to the Store.  This is set up perfectly for our use right now.
    }


    /**
     * Obtain a connection object for a Message attached to this Folder.  This
     * will be the Folder's connection, which is only available if the Folder
     * is currently open.
     *
     * @return The connection object for the Message instance to use.
     * @exception MessagingException
     */
    synchronized IMAPConnection getMessageConnection() throws MessagingException {
        // if we're not open, the messages can't communicate either
        if (currentConnection == null) {
            throw new FolderClosedException(this, "No Folder connections available");
        }
        // return the current Folder connection.  At this point, we'll be sharing the
        // connection between the Folder and the Message (and potentially, other messages).  The
        // command operations on the connection are synchronized so only a single command can be
        // issued at one time.
        return currentConnection;
    }


    /**
     * Release the connection object back to the Folder instance.
     *
     * @param connection The connection being released.
     *
     * @exception MessagingException
     */
    void releaseMessageConnection(IMAPConnection connection) throws MessagingException {
        // release it back to ourselves...this will drive unsolicited message processing.
        releaseConnection(connection);
    }


    /**
     * Refresh the status information on this folder.
     *
     * @param force  Force a status refresh always.
     *
     * @exception MessagingException
     */
    protected void refreshStatus(boolean force) throws MessagingException {
        // first check that any cached status we've received has gotten a little moldy.
        if (cachedStatus != null) {
            // if not forcing, check the time out.
            if (!force) {
                if (statusCacheTimeout > 0) {
                    long age = System.currentTimeMillis() - lastStatusTimeStamp;
                    if (age < statusCacheTimeout) {
                        return;
                    }
                }
            }
            // make sure the stale information is cleared out.
            cachedStatus = null;
        }

        IMAPConnection connection = getConnection();
        try {
            // ping the server for the list information for this folder
            cachedStatus = connection.getMailboxStatus(fullname);
            // mark when we got this
            lastStatusTimeStamp = System.currentTimeMillis();
        } finally {
            releaseConnection(connection);
        }

        // refresh the internal state from the message information
        maxSequenceNumber = cachedStatus.messages;
        recentMessages = cachedStatus.recentMessages;
        unseenMessages = cachedStatus.unseenMessages;
        uidValidity = cachedStatus.uidValidity;
    }


    /**
     * Process an EXPUNGE response for a message, removing the
     * message from the message cache.
     *
     * @param sequenceNumber
     *               The sequence number for the expunged message.
     *
     * @return The Message object corresponding to this expunged
     *         message.
     * @exception MessagingException
     */
    protected synchronized Message expungeMessage(int sequenceNumber) throws MessagingException {

        // first process the expunged message.  We need to return a Message instance, so
        // force this to be added to the cache
        IMAPMessage expungedMessage = (IMAPMessage)getMessage(sequenceNumber);
        // mark the message as expunged.
        expungedMessage.setExpunged(true);
        // have we retrieved a UID for this message?  If we have, then it's in the UID cache and
        // needs removal from there also
        long uid = ((IMAPMessage)expungedMessage).getUID();
        if (uid >= 0) {
            uidCache.remove(new Long(uid));
        }
        // because we need to jigger the keys of some of these, we had better have a working
        // copy.
        Map newCache = new HashMap();

        // now process each message in the cache, making adjustments as necessary
        Iterator i = messageCache.keySet().iterator();

        while (i.hasNext()) {
            Integer key = (Integer)i.next();
            int index = key.intValue();
            // if before the expunged message, just copy over to the
            // new cache
            if (index < sequenceNumber) {
                newCache.put(key, messageCache.get(key));
            }
            // after the expunged message...we need to adjust this
            else if (index > sequenceNumber) {
                // retrieve the message using the current position,
                // adjust the message sequence number, and add to the new
                // message cache under the new key value
                IMAPMessage message = (IMAPMessage)messageCache.get(key);
                message.setSequenceNumber(index - 1);
                newCache.put(new Integer(index - 1), message);
            }
            else {
                // the expunged message.  We don't move this over to the new
                // cache, and we've already done all processing of that message that's
                // required
            }
        }

        // replace the old cache now that everything has been adjusted
        messageCache = newCache;

        // adjust the message count downward
        maxSequenceNumber--;
        return expungedMessage;
    }


    /**
     * Resolve an array of message numbers into an array of the
     * referenced messages.
     *
     * @param messageNumbers
     *               The array of message numbers (can be null).
     *
     * @return An array of Message[] containing the resolved messages from
     *         the list.  Returns a zero-length array if there are no
     *         messages to resolve.
     * @exception MessagingException
     */
    protected Message[] resolveMessages(int[] messageNumbers) throws MessagingException {
        // the connection search returns a null pointer if nothing was found, just convert this into a
        // null array.
        if (messageNumbers == null) {
            return new Message[0];
        }

        Message[] messages = new Message[messageNumbers.length];

        // retrieve each of the message numbers in turn.
        for (int i = 0; i < messageNumbers.length; i++) {
            messages[i] = getMessage(messageNumbers[i]);
        }

        return messages;
    }

    /**
     * Generate a message set string from a List of messages rather than an
     * array.
     *
     * @param messages The List of messages.
     *
     * @return The evaluated message set string.
     * @exception MessagingException
     */
    protected String generateMessageSet(List messages) throws MessagingException {
        Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]);
        return generateMessageSet(msgs);
    }


    /**
     * Take an array of messages and generate a String <message set>
     * argument as specified by RFC 2060.  The message set argument
     * is a comma-separated list of message number ranges.  A
     * single element range is just one number.  A longer range is
     * a pair of numbers separated by a ":".  The generated string
     * should not have any blanks.  This will attempt to locate
     * consequetive ranges of message numbers, but will only do this
     * for messages that are already ordered in the array (i.e., we
     * don't try to sort).  Expunged messages are excluded from the
     * search, since they don't exist anymore.  A valid search string
     * will look something like this:
     *
     *    "3,6:10,15,21:35"
     *
     * @param messages The array of messages we generate from.
     *
     * @return A string formatted version of these message identifiers that
     *         can be used on an IMAP command.
     */
    protected String generateMessageSet(Message[] messages) throws MessagingException {
        StringBuffer set = new StringBuffer();

        for (int i = 0; i < messages.length; i++) {
            // first scan the list looking for a "live" message.
            IMAPMessage start = (IMAPMessage)messages[i];
            if (!start.isExpunged()) {

                // we can go ahead and add this to the list now.  If we find this is the start of a
                // range, we'll tack on the ":end" bit once we find the last message in the range.
                if (set.length() != 0) {
                    // only append the comma if not the first element of the list
                    set.append(',');
                }

                // append the first number.  NOTE:  We append this directly rather than
                // use appendInteger(), which appends it using atom rules.
                set.append(Integer.toString(start.getSequenceNumber()));

                // ok, we have a live one.  Now scan the list from here looking for the end of
                // a range of consequetive messages.
                int endIndex = -1; ;
                // get the number we're checking against.
                int previousSequence = start.getSequenceNumber();
                for (int j = i + 1; j < messages.length; j++) {
                    IMAPMessage message = (IMAPMessage)messages[j];
                    if (!message.isExpunged()) {
                        // still consequetive?
                        if (message.getSequenceNumber() == previousSequence + 1) {
                            // step this for the next check.
                            previousSequence++;
                            // record this as the current end of the range.
                            endIndex = j;
                        }
                        else {
                            // found a non-consequetive one, stop here
                            break;
                        }
                    }
                }

                // have a range end point?  Add the range specifier and step the loop index point
                // to skip over this
                if (endIndex != -1) {
                    // pick up the scan at the next location
                    i = endIndex;

                    set.append(':');
                    set.append(Integer.toString(((IMAPMessage)messages[endIndex]).getSequenceNumber()));
                }
            }
        }

        // return null for an empty list. This is possible because either an empty array has been handed to
        // us or all of the messages in the array have been expunged.
        if (set.length() == 0) {
            return null;
        }
        return set.toString();
    }

    /**
     * Verify that this folder exists on the server before
     * performning an operation that requires a valid
     * Folder instance.
     *
     * @exception MessagingException
     */
    protected void checkFolderValidity() throws MessagingException {
        // if we are holding a current listinfo response, then
        // we have chached existance information.  In that case,
        // all of our status is presumed up-to-date and we can go
        // with that.  If we don't have the information, then we
        // ping the server for it.
        if (listInfo == null && !exists()) {
            throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server");
        }
    }


    /**
     * Check if a Message is properly within the target
     * folder.
     *
     * @param msg    The message we're checking.
     *
     * @exception MessagingException
     */
    protected void checkMessageFolder(Message msg) throws MessagingException {
        if (msg.getFolder() != this) {
            throw new NoSuchElementException("Message is not within the target Folder");
        }
    }


    /**
     * Search a list of LIST responses for one containing information
     * for a particular mailbox name.
     *
     * @param responses The list of responses.
     * @param name      The desired mailbox name.
     *
     * @return The IMAPListResponse information for the requested name.
     */
    protected IMAPListResponse findListResponse(List responses, String name) {
        for (int i = 0; i < responses.size(); i++) {
            IMAPListResponse response = (IMAPListResponse)responses.get(i);
            if (response.mailboxName.equals(name)) {
                return response;
            }
        }
        return null;
    }


    /**
     * Protected class intended for subclass overrides.  For normal folders,
     * the mailbox name is fullname.  For Namespace root folders, the mailbox
     * name is the prefix + separator.
     *
     * @return The string name to use as the mailbox name for exists() and issubscribed()
     *         calls.
     */
    protected String getMailBoxName() {
        return fullname;
    }

    /**
     * Handle an unsolicited response from the server.  Most unsolicited responses
     * are replies to specific commands sent to the server.  The remainder must
     * be handled by the Store or the Folder using the connection.  These are
     * critical to handle, as events such as expunged messages will alter the
     * sequence numbers of the live messages.  We need to keep things in sync.
     *
     * @param response The UntaggedResponse to process.
     *
     * @return true if we handled this response and no further handling is required.  false
     *         means this one wasn't one of ours.
     */
    public boolean handleResponse(IMAPUntaggedResponse response) {
        // "you've got mail".  The message count has been updated.  There
        // are two posibilities.  Either there really are new messages, or
        // this is an update following an expunge.  If there are new messages,
        // we need to update the message cache and broadcast the change to
        // any listeners.
        if (response.isKeyword("EXISTS")) {
            // we need to update our cache, and also retrieve the new messages and
            // send them out in a broadcast update.
            int oldCount = maxSequenceNumber;
            maxSequenceNumber = ((IMAPSizeResponse)response).getSize();
            // has the size grown?  We have to send the "you've got mail" announcement.
            if (oldCount < maxSequenceNumber) {
                try {
                    Message[] messages = getMessages(oldCount + 1, maxSequenceNumber);
                    notifyMessageAddedListeners(messages);
                } catch (MessagingException e) {
                    // should never happen in this context
                }
            }
            return true;
        }
        // "you had mail".  A message was expunged from the server.  This MUST
        // be processed immediately, as any subsequent expunge messages will
        // shift the message numbers as a result of previous messages getting
        // removed.  We need to keep our internal cache in sync with the server.
        else if (response.isKeyword("EXPUNGE")) {
            int messageNumber = ((IMAPSizeResponse)response).getSize();
            try {
                Message message = expungeMessage(messageNumber);

                // broadcast the message update.
                notifyMessageRemovedListeners(false, new Message[] {message});
            } catch (MessagingException e) {
            }
            // we handled this one.
            return true;
        }
        // just an update of recently arrived stuff?  Just update the field.
        else if (response.isKeyword("RECENT")) {
            recentMessages = ((IMAPSizeResponse)response).getSize();
            return true;
        }
        // The spec is not particularly clear what types of unsolicited
        // FETCH response can be sent.  The only one that is specifically
        // spelled out is flag updates.  If this is one of those, then
        // handle it.
        else if (response.isKeyword("FETCH")) {
            IMAPFetchResponse fetch = (IMAPFetchResponse)response;
            IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS);
            // if this is a flags response, get the message and update
            if (flags != null) {
                try {
                    // get the updated message and update the internal state.
                    IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber);
                    // this shouldn't happen, but it might have been expunged too.
                    if (message != null) {
                        message.updateMessageInformation(fetch);
                    }
                    notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
                } catch (MessagingException e) {
                }
                return true;
            }
        }
        // this is a BYE response on our connection.  This forces us to close, but
        // when we return the connection, the pool needs to get rid of it.
        else if (response.isKeyword("BYE")) {
            // this is essentially a close event.  We need to clean everything up
            // and make sure our connection is not returned to the general pool.
            try {
                cleanupFolder(false, true);
            } catch (MessagingException e) {
            }
            return true;
        }

        // not a response the folder knows how to deal with.
        return false;
    }

// The following set of methods are extensions that exist in the Sun implementation.  They
// match the Sun version in intent, but are not 100% compatible because the Sun implementation
// uses com.sun.* class instances as opposed to the org.apache.geronimo.* classes.



    /**
     *   Remove an entry from the access control list for this folder.
     *
     * @param acl    The ACL element to remove.
     *
     * @exception MessagingException
     */
    public synchronized void removeACL(ACL acl) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.removeACLRights(fullname, acl);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     *   Add an entry to the access control list for this folder.
     *
     * @param acl    The new ACL to add.
     */
    public synchronized void addACL(ACL acl) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.setACLRights(fullname, acl);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     * Add Rights to a given ACL entry.
     *
     * @param acl    The target ACL to update.
     *
     * @exception MessagingException
     */
    public synchronized void addRights(ACL acl) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.addACLRights(fullname, acl);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     * Remove ACL Rights from a folder.
     *
     * @param acl    The ACL describing the Rights to remove.
     *
     * @exception MessagingException
     */
    public synchronized void removeRights(ACL acl) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.removeACLRights(fullname, acl);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     *   List the rights associated with a given name.
     *
     * @param name   The user name for the Rights.
     *
     * @return The set of Rights associated with the user name.
     * @exception MessagingException
     */
    public synchronized Rights[] listRights(String name) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            return connection.listACLRights(fullname, name);
        } finally {
            releaseConnection(connection);
        }
    }


    /**
     *   List the rights for the currently authenticated user.
     *
     * @return The set of Rights for the current user.
     * @exception MessagingException
     */
    public synchronized Rights myRights() throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            return connection.getMyRights(fullname);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Get the quota values assigned to the current folder.
     *
     * @return The Quota information for the folder.
     * @exception MessagingException
     */
    public synchronized Quota[] getQuota() throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            return connection.fetchQuotaRoot(fullname);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Set the quota value for a quota root
     *
     * @param quota  The new quota information to set.
     *
     * @exception MessagingException
     */
    public synchronized void setQuota(Quota quota) throws MessagingException {
        // ask the store to kindly hook us up with a connection.
        IMAPConnection connection = getConnection();

        try {
            // the connection does the heavy lifting
            connection.setQuota(quota);
        } finally {
            releaseConnection(connection);
        }
    }

    /**
     * Get the set of attributes defined for the folder
     * as the set of capabilities returned when the folder
     * was opened.
     *
     * @return The set of attributes associated with the folder.
     * @exception MessagingException
     */
    public synchronized String[] getAttributes() throws MessagingException {
        // if we don't have the LIST command information for this folder yet,
        // call exists() to force this to be updated so we can return.
        if (listInfo == null) {
            // return a null reference if this is not valid.
            if (!exists()) {
                return null;
            }
        }
        // return a copy of the attributes array.
        return (String[])listInfo.attributes.clone();
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



