001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.geronimo.javamail.store.imap;
019    
020    import java.io.ByteArrayOutputStream;
021    import java.io.IOException;
022    import java.util.ArrayList;
023    import java.util.Date;
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.LinkedList;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.NoSuchElementException;
030    import java.util.Vector;
031    
032    import javax.mail.*;
033    import javax.mail.event.ConnectionEvent;
034    import javax.mail.event.FolderEvent;
035    import javax.mail.event.MessageChangedEvent;
036    import javax.mail.search.FlagTerm;
037    import javax.mail.search.SearchTerm;
038    
039    import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
040    import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem;
041    import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse;
042    import org.apache.geronimo.javamail.store.imap.connection.IMAPFlags;
043    import org.apache.geronimo.javamail.store.imap.connection.IMAPListResponse;
044    import org.apache.geronimo.javamail.store.imap.connection.IMAPMailboxStatus;
045    import org.apache.geronimo.javamail.store.imap.connection.IMAPSizeResponse;
046    import org.apache.geronimo.javamail.store.imap.connection.IMAPUid;
047    import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponse;
048    import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponseHandler;
049    
050    /**
051     * The base IMAP implementation of the javax.mail.Folder
052     * This is a base class for both the Root IMAP server and each IMAP group folder.
053     * @see javax.mail.Folder
054     *
055     * @version $Rev: 594520 $
056     */
057    public class IMAPFolder extends Folder implements UIDFolder, IMAPUntaggedResponseHandler {
058    
059        /**
060         * Special profile item used for fetching SIZE and HEADER information.
061         * These items are extensions that Sun has added to their IMAPFolder immplementation. 
062         * We're supporting the same set. 
063         */
064        public static class FetchProfileItem extends FetchProfile.Item {
065            public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS");
066            public static final FetchProfileItem SIZE = new FetchProfileItem("SIZE");
067    
068            protected FetchProfileItem(String name) {
069                super(name);
070            }
071        }
072        
073        // marker that we don't know the separator yet for this folder. 
074        // This occurs when we obtain a folder reference from the 
075        // default folder.  At that point, we've not queried the 
076        // server for specifics yet. 
077        static final protected char UNDETERMINED = 0; 
078        
079        // our attached session
080        protected Session session;
081        // retrieved messages, mapped by sequence number.
082        protected LinkedList messages;
083        // mappings of UIDs to retrieved messages.
084        protected Map uidCache;
085    
086        // the separator the server indicates is used as the hierarchy separator
087        protected char separator;
088        // the "full" name of the folder.  This is the fully qualified path name for the folder returned by
089        // the IMAP server.  Elements of the hierarchy are delimited by "separator" characters.
090        protected String fullname;
091        // the name of this folder.  The is the last element of the fully qualified name.
092        protected String name;
093        // the folder open state
094            protected boolean folderOpen = false;
095        // the type information on what the folder can hold
096        protected int folderType;
097        // the subscription status
098        protected boolean subscribed = false;
099    
100        // the message identifier ticker, used to assign message numbers. 
101        protected int nextMessageID = 1; 
102        // the current count of messages in our cache. 
103        protected int maxSequenceNumber = 0;  
104        // the reported count of new messages (updated as a result of untagged message resposes)
105        protected int recentMessages = -1;
106        // the reported count of unseen messages
107        protected int unseenMessages = 0;
108        // the uidValidity value reported back from the server
109        protected long uidValidity = 0;
110        // the uidNext value reported back from the server
111        protected long uidNext = 0;
112        // the persistent flags we save in the store
113        protected Flags permanentFlags;
114        // the settable flags the server reports back to us
115        protected Flags availableFlags;
116        // Our cached status information.  We will only hold this for the timeout interval.
117        protected IMAPMailboxStatus cachedStatus;
118        // Folder information retrieved from the server.  Good info here indicates the 
119        // folder exists. 
120        protected IMAPListResponse listInfo; 
121        // the configured status cache timeout value.
122        protected long statusCacheTimeout;
123        // the last time we took a status snap shot.
124        protected long lastStatusTimeStamp;
125        // Our current connection.  We get one of these when opened, and release it when closed. 
126        // We do this because for any folder (and message) operations, the folder must be selected on 
127        // the connection.  
128        // Note, however, that there are operations which will require us to borrow a connection 
129        // temporarily because we need to touch the server when the folder is not open.  In those 
130        // cases, we grab a connection, then immediately return it to the pool. 
131        protected IMAPConnection currentConnection; 
132        
133        
134    
135        /**
136         * Super class constructor the base IMAPFolder class.
137         * 
138         * @param store     The javamail store this folder is attached to.
139         * @param fullname  The fully qualified name of this folder.
140         * @param separator The separtor character used to delimit the different
141         *                  levels of the folder hierarchy.  This is used to
142         *                  decompose the full name into smaller parts and
143         *                  create the names of subfolders.
144         */
145            protected IMAPFolder(IMAPStore store, String fullname, char separator) {
146                    super(store);
147                    this.session = store.getSession();
148            this.fullname = fullname;
149            this.separator = separator;
150            // get the status timeout value from the folder. 
151            statusCacheTimeout = store.statusCacheTimeout; 
152            }
153    
154        /**
155         * Retrieve the folder name.  This is the simple folder
156         * name at the its hiearchy level.  This can be invoked when the folder is closed.
157         * 
158         * @return The folder's name.
159         */
160            public String getName() {
161            // At the time we create the folder, we might not know the separator character yet. 
162            // Because of this we need to delay creating the name element until 
163            // it's required.  
164            if (name == null) {
165                // extract the name from the full name
166                int lastLevel = -1; 
167                try {
168                    lastLevel = fullname.lastIndexOf(getSeparator());
169                } catch (MessagingException e) {
170                    // not likely to occur, but the link could go down before we 
171                    // get this.  Just assume a failure to locate the character 
172                    // occurred. 
173                }
174                if (lastLevel == -1) {
175                    name = fullname;
176                }
177                else {
178                    name = fullname.substring(lastLevel + 1);
179                }
180            }
181            return name;
182            }
183    
184        /**
185         * Retrieve the folder's full name (including hierarchy information).
186         * This can be invoked when the folder is closed.
187         *
188         * @return The full name value.
189         */
190            public String getFullName() {
191            return fullname;
192            }
193    
194    
195    
196        /**
197         * Return the parent for this folder; if the folder is at the root of a heirarchy
198         * this returns null.
199         * This can be invoked when the folder is closed.
200         *
201         * @return this folder's parent
202         * @throws MessagingException
203         */
204            public Folder getParent() throws MessagingException {
205            // NB:  We need to use the method form because the separator 
206            // might not have been retrieved from the server yet. 
207            char separator = getSeparator(); 
208            // we don't hold a reference to the parent folder, as that would pin the instance in memory 
209            // as long as any any leaf item in the hierarchy is still open.  
210            int lastLevel = fullname.lastIndexOf(separator);
211            // no parent folder?  Get the root one from the Store.
212            if (lastLevel == -1) {
213                return ((IMAPStore)store).getDefaultFolder();
214            }
215            else {
216                // create a folder for the parent.
217                return new IMAPFolder((IMAPStore)store, fullname.substring(0, lastLevel), separator);
218            }
219            }
220    
221    
222        /**
223         * Check to see if this folder physically exists in the store.
224         * This can be invoked when the folder is closed.
225         *
226         * @return true if the folder really exists
227         * @throws MessagingException if there was a problem accessing the store
228         */
229        public synchronized boolean exists() throws MessagingException {
230            IMAPConnection connection = getConnection(); 
231            try {
232                return checkExistance(connection); 
233            } finally {
234                releaseConnection(connection); 
235            }
236        }
237        
238        /**
239         * Internal routine for checking existance using an 
240         * already obtained connection.  Used for situations
241         * where the list information needs updating but 
242         * we'd end up acquiring a new connection because 
243         * the folder isn't open yet. 
244         * 
245         * @param connection The connection to use.
246         * 
247         * @return true if the folder exists, false for non-existence.
248         * @exception MessagingException
249         */
250        private boolean checkExistance(IMAPConnection connection) throws MessagingException {
251            // get the list response for this folder.
252            List responses = connection.list("", fullname);
253            // NB, this grabs the latest information and updates 
254            // the type information also.  Note also that we need to  
255            // use the mailbox name, not the full name.  This is so 
256            // the namespace folders will return the correct response. 
257            listInfo = findListResponse(responses, getMailBoxName());
258    
259            if (listInfo == null) {
260                return false;
261            }
262    
263            // update the type information from the status.
264            folderType = 0;
265            if (!listInfo.noinferiors) {
266                folderType |= HOLDS_FOLDERS;
267            }
268            if (!listInfo.noselect) {
269                folderType |= HOLDS_MESSAGES;
270            }
271    
272            // also update the separator information.  This will allow 
273            // use to skip a call later 
274            separator = listInfo.separator;
275            // this can be omitted in the response, so assume a default 
276            if (separator == '\0') {
277                separator = '/';
278            }
279    
280            // updated ok, so it must be there.
281            return true;
282        }
283        
284    
285    
286        /**
287         * Return a list of folders from this Folder's namespace that match the supplied pattern.
288         * Patterns may contain the following wildcards:
289         * <ul><li>'%' which matches any characater except hierarchy delimiters</li>
290         * <li>'*' which matches any character including hierarchy delimiters</li>
291         * </ul>
292         * This can be invoked when the folder is closed.
293         * 
294         * @param pattern the pattern to search for
295         * 
296         * @return a possibly empty array containing Folders that matched the pattern
297         * @throws MessagingException
298         *                if there was a problem accessing the store
299         */
300        public synchronized Folder[] list(String pattern) throws MessagingException {
301            // go filter the folders based on the pattern.  The server does most of the
302            // heavy lifting on the pattern matching.
303            return filterFolders(pattern, false);
304        }
305    
306    
307        /**
308         * Return a list of folders to which the user is subscribed and which match the supplied pattern.
309         * If the store does not support the concept of subscription then this should match against
310         * all folders; the default implementation of this method achieves this by defaulting to the
311         * {@link #list(String)} method.
312         * 
313         * @param pattern the pattern to search for
314         * 
315         * @return a possibly empty array containing subscribed Folders that matched the pattern
316         * @throws MessagingException
317         *                if there was a problem accessing the store
318         */
319        public synchronized Folder[] listSubscribed(String pattern) throws MessagingException {
320            // go filter the folders based on the pattern.  The server does most of the
321            // heavy lifting on the pattern matching.
322            return filterFolders(pattern, true);
323        }
324    
325    
326        /**
327         * Return the character used by this folder's Store to separate path components.
328         *
329         * @return the name separater character
330         * @throws MessagingException if there was a problem accessing the store
331         */
332            public synchronized char getSeparator() throws MessagingException {
333            // not determined yet, we need to ask the server for the information 
334            if (separator == UNDETERMINED) {
335                IMAPConnection connection = getConnection(); 
336                try {
337                    List responses = connection.list("", fullname);
338                    IMAPListResponse info = findListResponse(responses, fullname);
339    
340                    // if we didn't get any hits, then we just assume a reasonable default. 
341                    if (info == null) {
342                        separator = '/';
343                    }
344                    else {
345                        separator = info.separator;
346                        // this can be omitted in the response, so assume a default 
347                        if (separator == '\0') {
348                            separator = '/';
349                        }
350                    }
351                } finally {
352                    releaseConnection(connection); 
353                }
354            }
355            return separator;
356            }
357    
358    
359        /**
360         * Return whether this folder can hold just messages or also
361         * subfolders.  
362         *
363         * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending 
364         * on the folder capabilities. 
365         * @exception MessagingException
366         */
367            public int getType() throws MessagingException {
368            // checking the validity will update the type information 
369            // if it succeeds. 
370            checkFolderValidity();
371                    return folderType;
372            }
373        
374    
375        /**
376         * Create a new folder capable of containing subfolder and/or messages as
377         * determined by the type parameter. Any hierarchy defined by the folder
378         * name will be recursively created.
379         * If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent}
380         * is sent to all FolderListeners registered with this Folder or with the Store.
381         * 
382         * @param newType the type, indicating if this folder should contain subfolders, messages or both
383         * 
384         * @return true if the folder was sucessfully created
385         * @throws MessagingException
386         *                if there was a problem accessing the store
387         */
388            public synchronized boolean create(int newType) throws MessagingException {
389            IMAPConnection connection = getConnection(); 
390            try {
391    
392                // by default, just create using the fullname.  
393                String newPath = fullname;
394    
395                // if this folder is expected to only hold additional folders, we need to
396                // add a separator on to the end when we create this.
397                if ((newType & HOLDS_MESSAGES) == 0) {
398                    newPath = fullname + separator;
399                }
400                try {
401                    // go create this
402                    connection.createMailbox(newPath);
403                    // verify this exists...also updates some of the status 
404                    boolean reallyCreated = checkExistance(connection); 
405                    // broadcast a creation event.
406                    notifyFolderListeners(FolderEvent.CREATED);
407                    return reallyCreated; 
408                } catch (MessagingException e) {
409                    //TODO add folder level debug logging.
410                }
411                // we have a failure
412                return false;
413            } finally {
414                releaseConnection(connection); 
415            }
416            }
417        
418    
419        /**
420         * Return the subscription status of this folder.
421         *
422         * @return true if the folder is marked as subscribed, false for
423         *         unsubscribed.
424         */
425        public synchronized boolean isSubscribed() {
426            try {
427                IMAPConnection connection = getConnection(); 
428                try {
429                    // get the lsub response for this folder.
430                    List responses = connection.listSubscribed("", fullname);
431    
432                    IMAPListResponse response = findListResponse(responses, fullname);
433                    if (response == null) {
434                        return false;
435                    }
436                    else {
437                        // a NOSELECT flag response indicates the mailbox is no longer 
438                        // selectable, so it's also no longer subscribed to. 
439                        return !response.noselect; 
440                    }
441                } finally {
442                    releaseConnection(connection); 
443                }
444            } catch (MessagingException e) {
445                // Can't override to throw a MessagingException on this method, so 
446                // just swallow any exceptions and assume false is the answer. 
447            }
448            return false; 
449        }
450    
451    
452        /**
453         * Set or clear the subscription status of a file.
454         *
455         * @param flag
456         *            The new subscription state.
457         */
458        public synchronized void setSubscribed(boolean flag) throws MessagingException {
459            IMAPConnection connection = getConnection(); 
460            try {             
461                if (flag) {
462                    connection.subscribe(fullname);
463                }
464                else {
465                    connection.unsubscribe(fullname);
466                }
467            } finally {
468                releaseConnection(connection); 
469            }
470        }
471    
472        /**
473         * Check to see if this Folder conatins messages with the {@link Flag.RECENT} flag set.
474         * This can be used when the folder is closed to perform a light-weight check for new mail;
475         * to perform an incremental check for new mail the folder must be opened.
476         *
477         * @return true if the Store has recent messages
478         * @throws MessagingException if there was a problem accessing the store
479         */
480            public synchronized boolean hasNewMessages() throws MessagingException {
481            // the folder must exist for this to work.
482            checkFolderValidity();
483            
484            // get the freshest status information.
485            refreshStatus(true);
486            // return the indicator from the message state.
487            return recentMessages > 0;
488            }
489    
490        /**
491         * Get the Folder determined by the supplied name; if the name is relative
492         * then it is interpreted relative to this folder. This does not check that
493         * the named folder actually exists.
494         *
495         * @param name the name of the folder to return
496         * @return the named folder
497         * @throws MessagingException if there was a problem accessing the store
498         */
499        public Folder getFolder(String name) throws MessagingException {
500            // this must be a real, valid folder to hold a subfolder
501            checkFolderValidity(); 
502            if (!holdsFolders()) {
503                throw new MessagingException("Folder " + fullname + " cannot hold subfolders"); 
504            }
505            // our separator does not get determined until we ping the server for it.  We 
506            // might need to do that now, so we need to use the getSeparator() method to retrieve this. 
507            char separator = getSeparator(); 
508            
509            return new IMAPFolder((IMAPStore)store, fullname + separator + name, separator);
510        }
511        
512    
513        /**
514         * Delete this folder and possibly any subfolders. This operation can only be
515         * performed on a closed folder.
516         * If recurse is true, then all subfolders are deleted first, then any messages in
517         * this folder are removed and it is finally deleted; {@link FolderEvent#DELETED}
518         * events are sent as appropriate.
519         * If recurse is false, then the behaviour depends on the folder type and store
520         * implementation as followd:
521         * <ul>
522         * <li>If the folder can only conrain messages, then all messages are removed and
523         * then the folder is deleted; a {@link FolderEvent#DELETED} event is sent.</li>
524         * <li>If the folder can onlu contain subfolders, then if it is empty it will be
525         * deleted and a {@link FolderEvent#DELETED} event is sent; if the folder is not
526         * empty then the delete fails and this method returns false.</li>
527         * <li>If the folder can contain both subfolders and messages, then if the folder
528         * does not contain any subfolders, any messages are deleted, the folder itself
529         * is deleted and a {@link FolderEvent#DELETED} event is sent; if the folder does
530         * contain subfolders then the implementation may choose from the following three
531         * behaviors:
532         * <ol>
533         * <li>it may return false indicting the operation failed</li>
534         * <li>it may remove all messages within the folder, send a {@link FolderEvent#DELETED}
535         * event, and then return true to indicate the delete was performed. Note this does
536         * not delete the folder itself and the {@link #exists()} operation for this folder
537         * will return true</li>
538         * <li>it may remove all messages within the folder as per the previous option; in
539         * addition it may change the type of the Folder to only HOLDS_FOLDERS indictaing
540         * that messages may no longer be added</li>
541         * </li>
542         * </ul>
543         * FolderEvents are sent to all listeners registered with this folder or
544         * with the Store.
545         *
546         * @param recurse whether subfolders should be recursively deleted as well
547         * @return true if the delete operation succeeds
548         * @throws MessagingException if there was a problem accessing the store
549         */
550            public synchronized boolean delete(boolean recurse) throws MessagingException {
551            // we must be in the closed state.
552            checkClosed();
553    
554            // if recursive, get the list of subfolders and delete them first.
555            if (recurse) {
556    
557                Folder[] subfolders = list();
558                for (int i = 0; i < subfolders.length; i++) {
559                    // this is a recursive delete also
560                    subfolders[i].delete(true);
561                }
562            }
563    
564            IMAPConnection connection = getConnection(); 
565            try {
566                // delete this one now.
567                connection.deleteMailbox(fullname);
568                // this folder no longer exists on the server.
569                listInfo = null;
570    
571                // notify interested parties about the deletion.
572                notifyFolderListeners(FolderEvent.DELETED);
573                return true;
574    
575            } catch (MessagingException e) {
576                // ignored
577            } finally {
578                releaseConnection(connection); 
579            }
580            return false;
581            }
582    
583    
584        /**
585         * Rename this folder; the folder must be closed.
586         * If the rename is successfull, a {@link FolderEvent#RENAMED} event is sent to
587         * all listeners registered with this folder or with the store.
588         *
589         * @param newName the new name for this folder
590         * @return true if the rename succeeded
591         * @throws MessagingException if there was a problem accessing the store
592         */
593            public synchronized boolean renameTo(Folder f) throws MessagingException {
594            // we must be in the closed state.
595            checkClosed();
596            // but we must also exist
597            checkFolderValidity();
598    
599            IMAPConnection connection = getConnection(); 
600            try {
601                // delete this one now.
602                connection.renameMailbox(fullname, f.getFullName());
603                // we renamed, so get a fresh set of status 
604                refreshStatus(false); 
605    
606                // notify interested parties about the deletion.
607                notifyFolderRenamedListeners(f);
608                return true;
609            } catch (MessagingException e) {
610                // ignored
611            } finally {
612                releaseConnection(connection); 
613            }
614            return false;
615            }
616    
617    
618        /**
619         * Open this folder; the folder must be able to contain messages and
620         * must currently be closed. If the folder is opened successfully then
621         * a {@link ConnectionEvent#OPENED} event is sent to listeners registered
622         * with this Folder.
623         * <p/>
624         * Whether the Store allows multiple connections or if it allows multiple
625         * writers is implementation defined.
626         *
627         * @param mode READ_ONLY or READ_WRITE
628         * @throws MessagingException if there was a problem accessing the store
629         */
630            public synchronized void open(int mode) throws MessagingException {
631    
632            // we use a synchronized block rather than use a synchronized method so that we
633            // can notify the event listeners while not holding the lock.
634            synchronized(this) {
635                // can only be performed on a closed folder
636                checkClosed();
637                // ask the store to kindly hook us up with a connection.
638                // We're going to hang on to this until we're closed, so store it in 
639                // the Folder field.  We need to make sure our mailbox is selected while 
640                // we're working things. 
641                currentConnection = ((IMAPStore)store).getFolderConnection(this); 
642                // we need to make ourselves a handler of unsolicited responses 
643                currentConnection.addResponseHandler(this); 
644                // record our open mode
645                this.mode = mode;
646    
647    
648                try {
649                    // try to open, which gives us a lot of initial mailbox state.
650                    IMAPMailboxStatus status = currentConnection.openMailbox(fullname, mode == Folder.READ_ONLY);
651    
652                    // not available in the requested mode?
653                    if (status.mode != mode) {
654                        // trying to open READ_WRITE and this isn't available?
655                        if (mode == READ_WRITE) {
656                            throw new ReadOnlyFolderException(this, "Cannot open READ_ONLY folder in READ_WRITE mode");
657                        }
658                    }
659                    
660                    // save this status and when we got it for later updating. 
661                    cachedStatus = status; 
662                    // mark when we got this
663                    lastStatusTimeStamp = System.currentTimeMillis();
664    
665                    // now copy the status information over and flip over the open sign.
666                    this.mode = status.mode;
667                    maxSequenceNumber = status.messages;
668                    recentMessages = status.recentMessages;
669                    uidValidity = status.uidValidity;
670                    uidNext = status.uidNext;
671    
672                    availableFlags = status.availableFlags;
673                    permanentFlags = status.permanentFlags;
674    
675                    // create a our caches
676                    messages = new LinkedList(); 
677                    uidCache = new HashMap();
678                    // this is a real pain, but because we need to track updates 
679                    // to message sequence numbers while the folder is open, the 
680                    // messages list needs to be populated with Message objects 
681                    // to keep track of things.  The IMAPMessage objects will not 
682                    // retrieve information from the server until required, so they're
683                    // relatively lightweight at this point. 
684                    populateMessageCache(); 
685    
686                    // we're open for business folks!
687                    folderOpen = true;
688                    notifyConnectionListeners(ConnectionEvent.OPENED);
689                } finally {
690                    // NB:  this doesn't really release this, but it does drive 
691                    // the processing of any unsolicited responses. 
692                    releaseConnection(currentConnection); 
693                }
694            }
695            }
696        
697        
698        /**
699         * Populate the message cache with dummy messages, ensuring we're filled 
700         * up to the server-reported number of messages. 
701         * 
702         * @exception MessagingException
703         */
704        protected void populateMessageCache() {
705            // spin through the server-reported number of messages and add a dummy one to 
706            // the cache.  The message number is assigned from the id counter, the 
707            // sequence number is the cache position + 1. 
708            for (int i = messages.size(); i < maxSequenceNumber; i++) {
709                messages.add(new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, i+1));  
710            }
711        }
712        
713    
714        /**
715         * Close this folder; it must already be open.
716         * A  @link ConnectionEvent#CLOSED} event is sent to all listeners registered
717         {* 
718         * with this folder.
719         *
720         * @param expunge whether to expunge all deleted messages
721         * @throws MessagingException if there was a problem accessing the store; the folder is still closed
722         */
723            public synchronized void close(boolean expunge) throws MessagingException {
724                    // Can only be performed on an open folder
725                    checkOpen();
726            cleanupFolder(expunge, false); 
727            }
728        
729        
730        /**
731         * Do folder cleanup.  This is used both for normal
732         * close operations, and adnormal closes where the
733         * server has sent us a BYE message.
734         * 
735         * @param expunge Indicates whether open messages should be expunged.
736         * @param disconnected
737         *                The disconnected flag.  If true, the server has cut
738         *                us off, which means our connection can not be returned
739         *                to the connection pool.
740         * 
741         * @exception MessagingException
742         */
743        protected void cleanupFolder(boolean expunge, boolean disconnected) throws MessagingException {
744                    folderOpen = false;
745            uidCache = null; 
746            messages = null; 
747            // if we have a connection active at the moment
748            if (currentConnection != null) {
749                // was this a forced disconnect by the server?
750                if (disconnected) {
751                    currentConnection.setClosed(); 
752                }
753                else {
754                    // The CLOSE operation depends on what mode was used to select the mailbox.  
755                    // If we're open in READ-WRITE mode, we used a SELECT operation.  When CLOSE 
756                    // is issued, any deleted messages will be expunged.  If we've been asked not 
757                    // to expunge the messages, we have a problem.  The solution is to reselect the 
758                    // mailbox using EXAMINE, which will not expunge messages when closed.  
759                    if (mode == READ_WRITE && !expunge) {
760                        // we can ignore the result...we're just switching modes. 
761                        currentConnection.openMailbox(fullname, true);
762                    }
763                    
764                    // have this close the selected mailbox 
765                    currentConnection.closeMailbox(); 
766                }
767                currentConnection.removeResponseHandler(this); 
768                // we need to release the connection to the Store once we're closed 
769                ((IMAPStore)store).releaseFolderConnection(this, currentConnection); 
770                currentConnection = null; 
771            }
772                    notifyConnectionListeners(ConnectionEvent.CLOSED);
773        }
774    
775        
776        /**
777         * Tests the open status of the folder.
778         *
779         * @return true if the folder is open, false otherwise.
780         */
781            public boolean isOpen() {
782                    return folderOpen;
783            }
784    
785        /**
786         * Get the permanentFlags
787         *
788         * @return The set of permanent flags we support (only SEEN).
789         */
790            public synchronized Flags getPermanentFlags() {
791            if (permanentFlags != null) {
792                // we need a copy of our master set.
793                return new Flags(permanentFlags);
794            }
795            else {
796                // a null return is expected if not there. 
797                return null; 
798            }
799            }
800    
801    
802        /**
803         * Return the number of messages this folder contains.
804         * If this operation is invoked on a closed folder, the implementation
805         * may choose to return -1 to avoid the expense of opening the folder.
806         *
807         * @return the number of messages, or -1 if unknown
808         * @throws MessagingException if there was a problem accessing the store
809         */
810            public synchronized int getMessageCount() throws MessagingException {
811            checkFolderValidity();
812    
813            // if we haven't opened the folder yet, we might not have good status information.
814            // go request some, which updates the folder fields also.
815            refreshStatus(false);
816                    return maxSequenceNumber;
817            }
818    
819        /**
820         * Return the numbew of messages in this folder that have the {@link Flag.RECENT} flag set.
821         * If this operation is invoked on a closed folder, the implementation
822         * may choose to return -1 to avoid the expense of opening the folder.
823         * The default implmentation of this method iterates over all messages
824         * in the folder; subclasses should override if possible to provide a more
825         * efficient implementation.
826         * 
827         * NB:  This is an override of the default Folder implementation, which 
828         * examines each of the messages in the folder.  IMAP has more efficient 
829         * mechanisms for grabbing the information. 
830         *
831         * @return the number of new messages, or -1 if unknown
832         * @throws MessagingException if there was a problem accessing the store
833         */
834        public synchronized int getNewMessageCount() throws MessagingException {
835            // the folder must be a real one for this to work. 
836            checkFolderValidity(); 
837            // now get current status from the folder 
838            refreshStatus(false); 
839            // this should be current now. 
840            return recentMessages; 
841        }
842    
843    
844    
845        /**
846         * Return the number of messages in this folder that do not have the {@link Flag.SEEN} flag set.
847         * If this operation is invoked on a closed folder, the implementation
848         * may choose to return -1 to avoid the expense of opening the folder.
849         * The default implmentation of this method iterates over all messages
850         * in the folder; subclasses should override if possible to provide a more
851         * efficient implementation.
852         * 
853         * NB:  This is an override of the default Folder implementation, which 
854         * examines each of the messages in the folder.  IMAP has more efficient 
855         * mechanisms for grabbing the information. 
856         *
857         * @return the number of new messages, or -1 if unknown
858         * @throws MessagingException if there was a problem accessing the store
859         */
860            public synchronized int getUnreadMessageCount() throws MessagingException {
861            checkFolderValidity();
862            // if we haven't opened the folder yet, we might not have good status information.
863            // go request some, which updates the folder fields also.
864            if (!folderOpen) {
865                refreshStatus(false);
866            }
867            else {
868                // if we have an open connection, then search the folder for any messages
869                // marked UNSEEN.
870    
871                // UNSEEN is a false test on SEEN using the search criteria.
872                SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
873                
874                // ask the store to kindly hook us up with a connection.
875                IMAPConnection connection = getConnection(); 
876                try {
877                    // search using the connection directly rather than calling our search() method so we don't
878                    // need to instantiate each of the matched messages.  We're really only interested in the count
879                    // right now.
880                    int[] matches = connection.searchMailbox(criteria);
881                    // update the unseen count.
882                    unseenMessages = matches == null ? 0 : matches.length;
883                } finally {
884                    releaseConnection(connection); 
885                }
886            }
887            // return our current message count.
888                    return unseenMessages;
889            }
890    
891    
892    
893        /**
894         * Return the number of messages in this folder that have the {@link Flag.DELETED} flag set.
895         * If this operation is invoked on a closed folder, the implementation
896         * may choose to return -1 to avoid the expense of opening the folder.
897         * The default implmentation of this method iterates over all messages
898         * in the folder; subclasses should override if possible to provide a more
899         * efficient implementation.
900         *
901         * @return the number of new messages, or -1 if unknown
902         * @throws MessagingException if there was a problem accessing the store
903         */
904            public synchronized int getDeletedMessageCount() throws MessagingException {
905            checkFolderValidity();
906    
907            // if we haven't opened the folder yet, we might not have good status information.
908            // go request some, which updates the folder fields also.
909            if (!folderOpen) {
910                // the status update doesn't return deleted messages.  These can only be obtained by
911                // searching an open folder.  Just return a bail-out response
912                return -1;
913            }
914            else {
915                // if we have an open connection, then search the folder for any messages
916                // marked DELETED.
917    
918                // UNSEEN is a false test on SEEN using the search criteria.
919                SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.DELETED), true);
920                
921                // ask the store to kindly hook us up with a connection.
922                IMAPConnection connection = getConnection(); 
923                try {
924                    // search using the connection directly rather than calling our search() method so we don't
925                    // need to instantiate each of the matched messages.  We're really only interested in the count
926                    // right now.
927                    int[] matches = connection.searchMailbox(criteria);
928                    return matches == null ? 0 : matches.length;
929                } finally {
930                    releaseConnection(connection); 
931                }
932            }
933            }
934    
935    
936        /**
937         * Retrieve the message with the specified index in this Folder;
938         * messages indices start at 1 not zero.
939         * Clients should note that the index for a specific message may change
940         * if the folder is expunged; {@link Message} objects should be used as
941         * references instead.
942         * 
943         * @param msgNum The message sequence number of the target message.
944         * 
945         * @return the message
946         * @throws MessagingException
947         *                if there was a problem accessing the store
948         */
949        public synchronized Message getMessage(int msgNum) throws MessagingException {
950            // Can only be performed on an Open folder
951            checkOpen();
952            // Check the validity of the message number.  This may require pinging the server to
953            // see if there are new messages in the folder.
954            checkMessageValidity(msgNum);
955            // ok, if the message number is within range, we should have this in the 
956            // messages list.  Just return the element. 
957            return (Message)messages.get(msgNum - 1); 
958        }
959    
960    
961        /**
962         * Retrieve a range of messages for this folder.
963         * messages indices start at 1 not zero.
964         * 
965         * @param start  Index of the first message to fetch, inclusive.
966         * @param end    Index of the last message to fetch, inclusive.
967         * 
968         * @return An array of the fetched messages.
969         * @throws MessagingException
970         *                if there was a problem accessing the store
971         */
972        public synchronized Message[] getMessages(int start, int end) throws MessagingException {
973            // Can only be performed on an Open folder
974            checkOpen();
975            Message[] messageRange = new Message[end - start + 1];
976            
977            for (int i = 0; i < messageRange.length; i++) {
978                // NB:  getMessage() requires values that are origin 1, so there's 
979                // no need to adjust the value by other than the start position. 
980                messageRange[i] = getMessage(start + i);
981            }
982            return messageRange; 
983        }
984    
985    
986        /**
987         * Append the supplied messages to this folder. A {@link MessageCountEvent} is sent
988         * to all listeners registered with this folder when all messages have been appended.
989         * If the array contains a previously expunged message, it must be re-appended to the Store
990         * and implementations must not abort this operation.
991         * 
992         * @param msgs   The array of messages to append to the folder.
993         * 
994         * @throws MessagingException
995         *                if there was a problem accessing the store
996         */
997            public synchronized void appendMessages(Message[] msgs) throws MessagingException {
998            checkFolderValidity(); 
999            for (int i = 0; i < msgs.length; i++) {
1000                Message msg = msgs[i]; 
1001                
1002                appendMessage(msg); 
1003            }
1004            }
1005    
1006        /**
1007         * Hint to the store to prefetch information on the supplied messages.
1008         * Subclasses should override this method to provide an efficient implementation;
1009         * the default implementation in this class simply returns.
1010         *
1011         * @param messages messages for which information should be fetched
1012         * @param profile  the information to fetch
1013         * @throws MessagingException if there was a problem accessing the store
1014         * @see FetchProfile
1015         */
1016        public void fetch(Message[] messages, FetchProfile profile) throws MessagingException {
1017            
1018            // we might already have the information being requested, so ask each of the 
1019            // messages in the list to evaluate itself against the profile.  We'll only ask 
1020            // the server to send information that's required. 
1021            List fetchSet = new ArrayList(); 
1022            
1023            for (int i = 0; i < messages.length; i++) {
1024                Message msg = messages[i]; 
1025                // the message is missing some of the information still.  Keep this in the list. 
1026                // even if the message is only missing one piece of information, we still fetch everything. 
1027                if (((IMAPMessage)msg).evaluateFetch(profile)) {
1028                    fetchSet.add(msg); 
1029                }
1030            }
1031            
1032            // we've got everything already, no sense bothering the server 
1033            if (fetchSet.isEmpty()) {
1034                return;
1035            }
1036            // ask the store to kindly hook us up with a connection.
1037            IMAPConnection connection = getConnection(); 
1038            try {
1039                // ok, from this point onward, we don't want any threads messing with the 
1040                // message cache.  A single processed EXPUNGE could make for a very bad day 
1041                synchronized(this) {
1042                    // get the message set for this 
1043                    String messageSet = generateMessageSet(fetchSet); 
1044                    // fetch all of the responses 
1045                    List responses = connection.fetch(messageSet, profile); 
1046                    
1047                    // IMPORTANT:  We must do our updates while synchronized to keep the 
1048                    // cache from getting updated underneath us.   This includes 
1049                    // not releasing the connection until we're done to delay processing any 
1050                    // pending expunge responses.  
1051                    for (int i = 0; i < responses.size(); i++) {
1052                        IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
1053                        Message msg = getMessage(response.getSequenceNumber()); 
1054                        // Belt and Braces.  This should never be false. 
1055                        if (msg != null) {
1056                            // have the message apply this to itself. 
1057                            ((IMAPMessage)msg).updateMessageInformation(response); 
1058                        }
1059                    }
1060                }
1061            } finally {
1062                releaseConnection(connection); 
1063            }
1064            return;
1065        }
1066    
1067        /**
1068         * Set flags on the messages to the supplied value; all messages must belong to this folder.
1069         * This method may be overridden by subclasses that can optimize the setting
1070         * of flags on multiple messages at once; the default implementation simply calls
1071         * {@link Message#setFlags(Flags, boolean)} for each supplied messages.
1072         * 
1073         * @param messages whose flags should be set
1074         * @param flags    the set of flags to modify
1075         * @param set      Indicates whether the flags should be set or cleared.
1076         * 
1077         * @throws MessagingException
1078         *                if there was a problem accessing the store
1079         */
1080        public void setFlags(Message[] messages, Flags flags, boolean set) throws MessagingException {
1081            // this is a list of messages for the change broadcast after the update 
1082            List updatedMessages = new ArrayList(); 
1083            
1084            synchronized(this) {
1085                // the folder must be open and writeable.
1086                checkOpenReadWrite();
1087    
1088                // now make sure these are settable flags.
1089                if (!availableFlags.contains(flags))
1090                {
1091                    throw new MessagingException("The IMAP server does not support changing of this flag set");
1092                }
1093    
1094                // turn this into a set of message numbers
1095                String messageSet = generateMessageSet(messages);
1096                // if all of the messages have been expunged, nothing to do.
1097                if (messageSet == null) {
1098                    return;
1099                }
1100                // ask the store to kindly hook us up with a connection.
1101                IMAPConnection connection = getConnection(); 
1102    
1103                try {
1104                    // and have the connection set this
1105                    List responses = connection.setFlags(messageSet, flags, set);
1106                    // retrieve each of the messages from our cache, and do the flag update.
1107                    // we need to keep the list so we can broadcast a change update event 
1108                    // when we're finished. 
1109                    for (int i = 0; i < responses.size(); i++) {
1110                        IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
1111    
1112                        // get the updated message and update the internal state. 
1113                        Message message = getMessage(response.sequenceNumber); 
1114                        // this shouldn't happen, but it might have been expunged too. 
1115                        if (message != null) {
1116                            ((IMAPMessage)message).updateMessageInformation(response); 
1117                            updatedMessages.add(message); 
1118                        }     
1119                    }
1120                } finally {
1121                    releaseConnection(connection); 
1122                }
1123            }
1124            
1125            // ok, we're no longer holding the lock.  Now go broadcast the update for each 
1126            // of the affected messages. 
1127            for (int i = 0; i < updatedMessages.size(); i++) {
1128                Message message = (Message)updatedMessages.get(i); 
1129                notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
1130            }
1131        }
1132    
1133        
1134        /**
1135         * Set flags on a range of messages to the supplied value.
1136         * This method may be overridden by subclasses that can optimize the setting
1137         * of flags on multiple messages at once; the default implementation simply
1138         * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
1139         * 
1140         * @param start  first message end set
1141         * @param end    last message end set
1142         * @param flags  the set of flags end modify
1143         * @param value  Indicates whether the flags should be set or cleared.
1144         * 
1145         * @throws MessagingException
1146         *                if there was a problem accessing the store
1147         */
1148        public synchronized void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException {
1149            Message[] msgs = new Message[end - start + 1]; 
1150            
1151            for (int i = start; i <= end; i++) {
1152                msgs[i] = getMessage(i);
1153            }
1154            // go do a bulk set operation on these messages 
1155            setFlags(msgs, flags, value); 
1156        }
1157    
1158        /**
1159         * Set flags on a set of messages to the supplied value.
1160         * This method may be overridden by subclasses that can optimize the setting
1161         * of flags on multiple messages at once; the default implementation simply
1162         * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
1163         * 
1164         * @param ids    the indexes of the messages to set
1165         * @param flags  the set of flags end modify
1166         * @param value  Indicates whether the flags should be set or cleared.
1167         * 
1168         * @throws MessagingException
1169         *                if there was a problem accessing the store
1170         */
1171        public synchronized void setFlags(int ids[], Flags flags, boolean value) throws MessagingException {
1172            Message[] msgs = new Message[ids.length]; 
1173            
1174            for (int i = 0; i <ids.length; i++) {
1175                msgs[i] = getMessage(ids[i]);
1176            }
1177            // go do a bulk set operation on these messages 
1178            setFlags(msgs, flags, value); 
1179        }
1180    
1181        
1182        /**
1183         * Copy the specified messages to another folder.
1184         * The default implementation simply appends the supplied messages to the
1185         * target folder using {@link #appendMessages(Message[])}.
1186         * @param messages the messages to copy
1187         * @param folder the folder to copy to
1188         * @throws MessagingException if there was a problem accessing the store
1189         */
1190        public synchronized void copyMessages(Message[] messages, Folder folder) throws MessagingException {
1191            // the default implementation just appends the messages to the target.  If 
1192            // we're copying between two folders of the same store, we can get the server to 
1193            // do most of the work for us without needing to fetch all of the message data.  
1194            // If we're dealing with two different Store instances, we need to do this the 
1195            // hardway.
1196            if (getStore() != folder.getStore()) {
1197                super.copyMessages(messages, folder); 
1198                return; 
1199            }
1200    
1201            // turn this into a set of message numbers
1202            String messageSet = generateMessageSet(messages);
1203            // if all of the messages have been expunged, nothing to do.
1204            if (messageSet == null) {
1205                return;
1206            }
1207            // ask the store to kindly hook us up with a connection.
1208            IMAPConnection connection = getConnection(); 
1209    
1210            try {
1211                // ask the server to copy this information over to the other mailbox. 
1212                connection.copyMessages(messageSet, folder.getFullName()); 
1213            } finally {
1214                releaseConnection(connection); 
1215            }
1216        }
1217    
1218        
1219    
1220        /**
1221         * Permanently delete all supplied messages that have the DELETED flag set from the Store.
1222         * The original message indices of all messages actually deleted are returned and a
1223         * {@link MessageCountEvent} event is sent to all listeners with this folder. The expunge
1224         * may cause the indices of all messaged that remain in the folder to change.
1225         *
1226         * @return the original indices of messages that were actually deleted
1227         * @throws MessagingException if there was a problem accessing the store
1228         */
1229            public synchronized Message[] expunge() throws MessagingException {
1230            // must be open to do this.
1231            checkOpen();
1232            // and changes need to be allowed
1233            checkReadWrite();
1234    
1235            // ask the store to kindly hook us up with a connection.
1236            IMAPConnection connection = getConnection(); 
1237            List expunges = null;
1238    
1239            try {
1240                // send the expunge notification.  This operation results in "nn EXPUNGE" responses getting returned
1241                // for each expunged messages.  These will be dispatched to our response handler, which will process
1242                // the expunge operation.  We could process this directly, but we may have received asynchronous
1243                // expunge messages that also marked messages as expunged.
1244                expunges = connection.expungeMailbox();
1245            } finally {
1246                releaseConnection(connection); 
1247            }
1248    
1249            // we get one EXPUNGE message for each message that's expunged.  They MUST be processed in 
1250            // order, as the message sequence numbers represent a relative position that takes into account 
1251            // previous expunge operations.  For example, if message sequence numbers 5, 6, and 7 are 
1252            // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged. 
1253            Message[] messages = new Message[expunges.size()]; 
1254    
1255            // now we need to protect the internal structures
1256            synchronized (this) {
1257                // expunge all of the messages from the message cache.  This keeps the sequence 
1258                // numbers up to-date. 
1259                for (int i = 0; i < expunges.size(); i++) {
1260                    IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i); 
1261                    messages[i] = expungeMessage(response.getSize()); 
1262                }
1263            }
1264            // if we have messages that have been removed, broadcast the notification.
1265            if (messages.length > 0) {
1266                notifyMessageRemovedListeners(true, messages);
1267            }
1268    
1269            // note, we're expected to return an array in all cases, even if the expunged count was zero.
1270            return messages;
1271            }
1272    
1273    
1274    
1275        /**
1276         * Search the supplied messages for those that match the supplied criteria;
1277         * messages must belong to this folder.
1278         * The default implementation iterates through the messages, returning those
1279         * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
1280         * subclasses may provide a more efficient implementation.
1281         *
1282         * @param term the search criteria
1283         * @param messages the messages to search
1284         * @return an array containing messages that match the criteria
1285         * @throws MessagingException if there was a problem accessing the store
1286         */
1287        public synchronized Message[] search(SearchTerm term) throws MessagingException {
1288            // only allowed on open folders
1289            checkOpen();
1290    
1291            // ask the store to kindly hook us up with a connection.
1292            IMAPConnection connection = getConnection(); 
1293    
1294            try {
1295                // just search everything
1296                int[] messageNumbers = connection.searchMailbox(term);
1297                return resolveMessages(messageNumbers);
1298            } finally {
1299                releaseConnection(connection); 
1300            }
1301        }
1302    
1303    
1304    
1305        /**
1306         * Search the supplied messages for those that match the supplied criteria;
1307         * messages must belong to this folder.
1308         * The default implementation iterates through the messages, returning those
1309         * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
1310         * subclasses may provide a more efficient implementation.
1311         *
1312         * @param term the search criteria
1313         * @param messages the messages to search
1314         * @return an array containing messages that match the criteria
1315         * @throws MessagingException if there was a problem accessing the store
1316         */
1317        public synchronized Message[] search(SearchTerm term, Message[] messages) throws MessagingException {
1318            // only allowed on open folders
1319            checkOpen();
1320    
1321            // turn this into a string specifier for these messages.  We'll weed out the expunged messages first.
1322            String messageSet = generateMessageSet(messages);
1323    
1324            // If we have no "live" messages to search, just return now.  We're required to return a non-null
1325            // value, so give an empy array back.
1326            if (messageSet == null) {
1327                return new Message[0];
1328            }
1329    
1330            // ask the store to kindly hook us up with a connection.
1331            IMAPConnection connection = getConnection(); 
1332    
1333            try {
1334    
1335                // now go do the search.
1336                int[] messageNumbers = connection.searchMailbox(messageSet, term);
1337                return resolveMessages(messageNumbers);
1338            } finally {
1339                releaseConnection(connection); 
1340            }
1341        }
1342    
1343        /**
1344         * Get the UID validity value for this Folder.
1345         * 
1346         * @return The current UID validity value, as a long. 
1347         * @exception MessagingException
1348         */
1349        public synchronized long getUIDValidity() throws MessagingException
1350        {
1351            // get the latest status to make sure we have the  
1352            // most current. 
1353            refreshStatus(true); 
1354            return uidValidity; 
1355        }
1356    
1357        /**
1358         * Retrieve a message using the UID rather than the 
1359         * message sequence number.  Returns null if the message
1360         * doesn't exist.
1361         * 
1362         * @param uid    The target UID.
1363         * 
1364         * @return the Message object.  Returns null if the message does
1365         *         not exist.
1366         * @exception MessagingException
1367         */
1368        public synchronized Message getMessageByUID(long uid) throws MessagingException
1369        {
1370            // only allowed on open folders
1371            checkOpen();
1372            
1373            Long key = new Long(uid); 
1374            // first check to see if we have a cached value for this 
1375            synchronized(messages) {
1376                Message msg = (Message)uidCache.get(key); 
1377                if (msg != null) {
1378                    return msg; 
1379                }
1380            }
1381    
1382            // ask the store to kindly hook us up with a connection.
1383            IMAPConnection connection = getConnection(); 
1384    
1385            try {
1386                // locate the message identifier 
1387                IMAPUid imapuid = connection.getSequenceNumberForUid(uid);
1388                // if nothing is returned, the message doesn't exist 
1389                if (imapuid == null) {
1390                    return null; 
1391                }
1392                
1393                
1394                // retrieve the actual message object and place this in the UID cache
1395                return retrieveMessageByUid(key, imapuid.messageNumber); 
1396            } finally {
1397                releaseConnection(connection); 
1398            }
1399        }
1400    
1401        /**
1402         * Get a series of messages using a UID range.  The 
1403         * special value LASTUID can be used to mark the 
1404         * last available message.
1405         * 
1406         * @param start  The start of the UID range.
1407         * @param end    The end of the UID range.  The special value
1408         *               LASTUID can be used to request all messages up
1409         *               to the last UID.
1410         * 
1411         * @return An array containing all of the messages in the 
1412         *         range.
1413         * @exception MessagingException
1414         */
1415        public synchronized Message[] getMessagesByUID(long start, long end) throws MessagingException
1416        {
1417            // only allowed on open folders
1418            checkOpen();
1419            // ask the store to kindly hook us up with a connection.
1420            IMAPConnection connection = getConnection(); 
1421    
1422            try {
1423                // locate the message identifier 
1424                List uids = connection.getSequenceNumbersForUids(start, end);
1425                Message[] msgs = new Message[uids.size()];
1426                
1427                // fill in each of the messages based on the returned value 
1428                for (int i = 0; i < msgs.length; i++) {
1429                    IMAPUid uid = (IMAPUid)uids.get(i); 
1430                    msgs[i] = retrieveMessageByUid(new Long(uid.uid), uid.messageNumber);
1431                }
1432                
1433                return msgs; 
1434            } finally {
1435                releaseConnection(connection); 
1436            }
1437            
1438            
1439        }
1440    
1441        /**
1442         * Retrieve a set of messages by explicit UIDs.  If 
1443         * any message in the list does not exist, null 
1444         * will be returned for the corresponding item.
1445         * 
1446         * @param ids    An array of UID values to be retrieved.
1447         * 
1448         * @return An array of Message items the same size as the ids
1449         *         argument array.  This array will contain null
1450         *         entries for any UIDs that do not exist.
1451         * @exception MessagingException
1452         */
1453        public synchronized Message[] getMessagesByUID(long[] ids) throws MessagingException
1454        {
1455            // only allowed on open folders
1456            checkOpen();
1457            
1458            Message[] msgs = new Message[ids.length];
1459            
1460            for (int i = 0; i < msgs.length; i++) {
1461                msgs[i] = getMessageByUID(ids[i]); 
1462            }
1463            
1464            return msgs; 
1465        }
1466    
1467        /**
1468         * Retrieve the UID for a message from this Folder.
1469         * The argument Message MUST belong to this Folder
1470         * instance, otherwise a NoSuchElementException will 
1471         * be thrown.
1472         * 
1473         * @param message The target message.
1474         * 
1475         * @return The UID associated with this message.
1476         * @exception MessagingException
1477         */
1478        public synchronized long getUID(Message message) throws MessagingException
1479        {
1480            // verify this actually is in this folder. 
1481            checkMessageFolder(message); 
1482            IMAPMessage msg = (IMAPMessage)message; 
1483            
1484            // we might already know this bit of information 
1485            if (msg.getUID() != -1) {
1486                return msg.getUID(); 
1487            }
1488    
1489            // ask the store to kindly hook us up with a connection.
1490            IMAPConnection connection = getConnection(); 
1491    
1492            try {
1493                // locate the message identifier 
1494                IMAPUid imapuid = connection.getUidForSequenceNumber(msg.getMessageNumber());
1495                // if nothing is returned, the message doesn't exist 
1496                if (imapuid == null) {
1497                    return -1;
1498                }
1499                // cache this information now that we've gotten it. 
1500                addToUidCache(new Long(imapuid.uid), getMessage(imapuid.messageNumber));
1501                // return the UID information. 
1502                return imapuid.uid; 
1503            } finally {
1504                releaseConnection(connection); 
1505            }
1506        }
1507        
1508        /**
1509         * Retrieve a message from a UID/message mapping.
1510         * 
1511         * @param key       The UID key used for the mapping.
1512         * @param msgNumber The message sequence number.
1513         * 
1514         * @return The Message object corresponding to the message.
1515         * @exception MessagingException
1516         */
1517        protected synchronized Message retrieveMessageByUid(Long key, int msgNumber) throws MessagingException
1518        {
1519            synchronized (messages) {
1520                // first check the cache...this might have already been added. 
1521                Message msg = (Message)uidCache.get(key); 
1522                if (msg != null) {
1523                    return msg; 
1524                }
1525                
1526                // retrieve the message by sequence number 
1527                msg = getMessage(msgNumber); 
1528                // add this to our UID mapping cache. 
1529                addToUidCache(key, msg); 
1530                return msg; 
1531            }
1532        }
1533        
1534        
1535        /**
1536         * Add a message to the UID mapping cache, ensuring that 
1537         * the UID value is updated.
1538         * 
1539         * @param key    The UID key.
1540         * @param msg    The message to add.
1541         */
1542        protected void addToUidCache(Long key, Message msg) {  
1543            synchronized (messages) {
1544                ((IMAPMessage)msg).setUID(key.longValue());  
1545                uidCache.put(key, msg); 
1546            }
1547        }
1548        
1549        
1550        /**
1551         * Append a single message to the IMAP Folder.
1552         * 
1553         * @param msg    The message to append.
1554         * 
1555         * @exception MessagingException
1556         */
1557        protected synchronized void appendMessage(Message msg) throws MessagingException 
1558        {
1559            // sort out the dates.  If no received date, use the sent date. 
1560            Date date = msg.getReceivedDate(); 
1561            if (date == null) {
1562                date = msg.getSentDate(); 
1563            }
1564            
1565            Flags flags = msg.getFlags(); 
1566            
1567            // convert the message into an array of bytes we can attach as a literal. 
1568            ByteArrayOutputStream out = new ByteArrayOutputStream(); 
1569            
1570            try {
1571                msg.writeTo(out); 
1572            } catch (IOException e) {
1573            }
1574            
1575            // now issue the append command 
1576            IMAPConnection connection = getConnection(); 
1577            try {
1578                connection.appendMessage(getFullName(), date, flags, out.toByteArray()); 
1579            } finally {
1580                releaseConnection(connection); 
1581            }
1582        }
1583    
1584        
1585        /**
1586         * Retrieve the list of matching groups from the IMAP server using the LIST
1587         * or LSUB command. The server does the wildcard matching for us.
1588         *
1589         * @param pattern
1590         *            The pattern string (in wildmat format) used to match.
1591         *
1592         * @return An array of folders for the matching groups.
1593         */
1594        protected synchronized Folder[] filterFolders(String pattern, boolean subscribed) throws MessagingException {
1595            IMAPConnection connection = getConnection(); 
1596            // this is used to filter out our own folder from the search 
1597            String root = fullname + getSeparator();
1598            
1599            List responses = null;
1600            try {             
1601    
1602    
1603                if (subscribed) {
1604                    // get the lsub response for this folder.
1605                    responses = connection.listSubscribed(root, pattern);
1606                }
1607                else {
1608                    // grab using the LIST command.
1609                    responses = connection.list(root, pattern);
1610                }
1611            } finally {
1612                releaseConnection(connection); 
1613            }
1614    
1615            List folders = new ArrayList();
1616    
1617            for (int i = 0; i < responses.size(); i++) {
1618                IMAPListResponse response = (IMAPListResponse)responses.get(i);
1619                // if a full wildcard is specified, the root folder can be returned too.  Make sure we
1620                // filter that one out.
1621                if (!response.mailboxName.equals(root)) {
1622                    IMAPFolder folder = new IMAPFolder((IMAPStore)store, response.mailboxName, response.separator);
1623                    folders.add(folder);
1624                }
1625            }
1626    
1627            // convert into an array and return
1628            return (Folder[])folders.toArray(new Folder[folders.size()]);
1629        }
1630    
1631    
1632        /**
1633         * Test if a folder can hold sub folders.
1634         *
1635         * @return True if the folder is allowed to have subfolders.
1636         */
1637        protected synchronized boolean holdsFolders() throws MessagingException {
1638            checkFolderValidity();
1639            return (folderType & HOLDS_FOLDERS) != 0;
1640        }
1641    
1642    
1643        /**
1644         * Validate that a target message number is considered valid
1645         * by the IMAP server.  If outside of the range we currently
1646         * are a ware of, we'll ping the IMAP server to see if there
1647         * have been any updates.
1648         *
1649         * @param messageNumber
1650         *               The message number we're checking.
1651         *
1652         * @exception MessagingException
1653         */
1654        protected void checkMessageValidity(int messageNumber) throws MessagingException {
1655            // lower range for a message is 1.
1656            if (messageNumber < 1) {
1657                throw new MessagingException("Invalid message number for IMAP folder: " + messageNumber);
1658            }
1659            // if within our current known range, we'll accept this
1660            if (messageNumber <= maxSequenceNumber) {
1661                return;
1662            }
1663            
1664            IMAPConnection connection = getConnection(); 
1665    
1666            synchronized (this) {
1667                try {
1668                    // ping the server to see if there's any updates to process.  The updates are handled
1669                    // by the response handlers.
1670                    connection.updateMailboxStatus();
1671                } finally {
1672                    releaseConnection(connection); 
1673                }
1674            }
1675    
1676            // still out of range?
1677            if (messageNumber > maxSequenceNumber) {
1678                throw new MessagingException("Message " + messageNumber + " does not exist on server");
1679            }
1680        }
1681    
1682    
1683            /**
1684             * Below is a list of convenience methods that avoid repeated checking for a
1685             * value and throwing an exception
1686             */
1687    
1688        /**
1689         * Ensure the folder is open.  Throws a MessagingException
1690         * if not in the correct state for the operation.
1691         * 
1692         * @exception IllegalStateException
1693         */
1694        protected void checkOpen() throws IllegalStateException {
1695                    if (!folderOpen){
1696                        throw new IllegalStateException("Folder is not Open");
1697                    }
1698        }
1699    
1700        /**
1701         * Ensure the folder is not open for operations
1702         * that require the folder to be closed.
1703         * 
1704         * @exception IllegalStateException
1705         */
1706        protected void checkClosed() throws IllegalStateException {
1707                    if (folderOpen){
1708                        throw new IllegalStateException("Folder is Open");
1709                    }
1710        }
1711    
1712        /**
1713         * Ensure that the folder is open for read/write mode before doing
1714         * an operation that would make a change.
1715         *
1716         * @exception IllegalStateException
1717         */
1718        protected void checkReadWrite() throws IllegalStateException {
1719            if (mode != READ_WRITE) {
1720                        throw new IllegalStateException("Folder is opened READY_ONLY");
1721            }
1722        }
1723    
1724    
1725        /**
1726         * Check that the folder is open and in read/write mode.
1727         *
1728         * @exception IllegalStateException
1729         */
1730        protected void checkOpenReadWrite() throws IllegalStateException {
1731            checkOpen();
1732            checkReadWrite();
1733        }
1734    
1735    
1736    
1737        /**
1738         * Notify the message changed listeners that a 
1739         * message contained in the folder has been updated.
1740         * 
1741         * @param type   The type of update made to the message.
1742         * @param m      The message that was updated.
1743         * 
1744         * @see javax.mail.Folder#notifyMessageChangedListeners(int, javax.mail.Message)
1745         */
1746        public void notifyMessageChangedListeners(int type, Message m) {
1747            super.notifyMessageChangedListeners(type, m);
1748        }
1749    
1750        
1751        /**
1752         * Retrieve the connection attached to this folder.  Throws an
1753         * exception if we don't have an active connection.
1754         *
1755         * @return The current connection object.
1756         * @exception MessagingException
1757         */
1758        protected synchronized IMAPConnection getConnection() throws MessagingException {
1759            // don't have an open connection yet?  Just request a pool connection. 
1760            if (currentConnection == null) {
1761                // request a connection from the central store. 
1762                IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);  
1763                // we need to make ourselves a handler of unsolicited responses 
1764                connection.addResponseHandler(this); 
1765                return connection;
1766            }
1767            // we have a connection for our use.  Just return it. 
1768            return currentConnection; 
1769        }
1770        
1771        
1772        /**
1773         * Release our connection back to the Store.
1774         * 
1775         * @param connection The connection to release.
1776         * 
1777         * @exception MessagingException
1778         */
1779        protected void releaseConnection(IMAPConnection connection) throws MessagingException {
1780            // This is a bit of a pain.  We need to delay processing of the 
1781            // unsolicited responses until after each user of the connection has 
1782            // finished processing the expected responses.  We need to do this because 
1783            // the unsolicited responses may include EXPUNGED messages.  The EXPUNGED 
1784            // messages will alter the message sequence numbers for the messages in the 
1785            // cache.  Processing the EXPUNGED messages too early will result in 
1786            // updates getting applied to the wrong message instances.  So, as a result, 
1787            // we delay that stage of the processing until all expected responses have 
1788            // been handled.  
1789            
1790            // process any pending messages before returning. 
1791            connection.processPendingResponses(); 
1792            // if no cached connection or this is somehow different from the cached one, just 
1793            // return it. 
1794            if (currentConnection == null || connection != currentConnection) {
1795                connection.removeResponseHandler(this); 
1796                ((IMAPStore)store).releaseFolderConnection(this, connection);  
1797            }
1798            // if we're open, then we don't have to worry about returning this connection 
1799            // to the Store.  This is set up perfectly for our use right now. 
1800        }
1801        
1802        
1803        /**
1804         * Obtain a connection object for a Message attached to this Folder.  This 
1805         * will be the Folder's connection, which is only available if the Folder 
1806         * is currently open.
1807         * 
1808         * @return The connection object for the Message instance to use. 
1809         * @exception MessagingException
1810         */
1811        synchronized IMAPConnection getMessageConnection() throws MessagingException {
1812            // if we're not open, the messages can't communicate either
1813            if (currentConnection == null) {
1814                throw new FolderClosedException(this, "No Folder connections available"); 
1815            }
1816            // return the current Folder connection.  At this point, we'll be sharing the 
1817            // connection between the Folder and the Message (and potentially, other messages).  The 
1818            // command operations on the connection are synchronized so only a single command can be 
1819            // issued at one time. 
1820            return currentConnection; 
1821        }
1822        
1823        
1824        /**
1825         * Release the connection object back to the Folder instance.  
1826         * 
1827         * @param connection The connection being released.
1828         * 
1829         * @exception MessagingException
1830         */
1831        void releaseMessageConnection(IMAPConnection connection) throws MessagingException {
1832            // release it back to ourselves...this will drive unsolicited message processing. 
1833            releaseConnection(connection); 
1834        }
1835    
1836    
1837        /**
1838         * Refresh the status information on this folder.
1839         * 
1840         * @param force  Force a status refresh always.
1841         * 
1842         * @exception MessagingException
1843         */
1844        protected void refreshStatus(boolean force) throws MessagingException {
1845            // first check that any cached status we've received has gotten a little moldy.
1846            if (cachedStatus != null) {
1847                // if not forcing, check the time out. 
1848                if (!force) {
1849                    if (statusCacheTimeout > 0) {
1850                        long age = System.currentTimeMillis() - lastStatusTimeStamp;
1851                        if (age < statusCacheTimeout) {
1852                            return;                 
1853                        }
1854                    }
1855                }
1856                // make sure the stale information is cleared out.
1857                cachedStatus = null;
1858            }
1859            
1860            IMAPConnection connection = getConnection(); 
1861            try {
1862                // ping the server for the list information for this folder
1863                cachedStatus = connection.getMailboxStatus(fullname);
1864                // mark when we got this
1865                lastStatusTimeStamp = System.currentTimeMillis();
1866            } finally {
1867                releaseConnection(connection); 
1868            }
1869    
1870            // refresh the internal state from the message information
1871            maxSequenceNumber = cachedStatus.messages;
1872            recentMessages = cachedStatus.recentMessages;
1873            unseenMessages = cachedStatus.unseenMessages;
1874            uidValidity = cachedStatus.uidValidity;
1875        }
1876    
1877    
1878        /**
1879         * Process an EXPUNGE response for a message, removing the
1880         * message from the message cache.
1881         * 
1882         * @param sequenceNumber
1883         *               The sequence number for the expunged message.
1884         * 
1885         * @return The Message object corresponding to this expunged
1886         *         message.
1887         * @exception MessagingException
1888         */
1889        protected synchronized Message expungeMessage(int sequenceNumber) throws MessagingException {
1890            // we need to remove this from our message list.  Deletion of a message by 
1891            // sequence number shifts the sequence numbers of every message after 
1892            // the target.  So we, need to remove the message, update its status, then 
1893            // update the sequence numbers of every message after that.  This is a serious 
1894            // pain!
1895            
1896            Message expungedMessage = (Message)messages.remove(sequenceNumber - 1); 
1897            // mark the message as expunged. 
1898            ((IMAPMessage)expungedMessage).setExpunged(true); 
1899            
1900            // update the message sequence numbers for every message after the 
1901            // expunged one.  NB.  The sequence number is the cache index + 1
1902            for (int i = sequenceNumber - 1; i < messages.size(); i++) {
1903                IMAPMessage message = (IMAPMessage)messages.get(i); 
1904                message.setSequenceNumber(i + 1); 
1905            }
1906            
1907            // have we retrieved a UID for this message?  If we have, then it's in the UID cache and
1908            // needs removal from there also
1909            long uid = ((IMAPMessage)expungedMessage).getUID();
1910            if (uid >= 0) {
1911                uidCache.remove(new Long(uid));
1912            }
1913    
1914            // adjust the message count downward
1915            maxSequenceNumber--;
1916            return expungedMessage; 
1917        }
1918    
1919    
1920        /**
1921         * Resolve an array of message numbers into an array of the
1922         * referenced messages.
1923         *
1924         * @param messageNumbers
1925         *               The array of message numbers (can be null).
1926         *
1927         * @return An array of Message[] containing the resolved messages from
1928         *         the list.  Returns a zero-length array if there are no
1929         *         messages to resolve.
1930         * @exception MessagingException
1931         */
1932        protected Message[] resolveMessages(int[] messageNumbers) throws MessagingException {
1933            // the connection search returns a null pointer if nothing was found, just convert this into a
1934            // null array.
1935            if (messageNumbers == null) {
1936                return new Message[0];
1937            }
1938    
1939            Message[] messages = new Message[messageNumbers.length];
1940    
1941            // retrieve each of the message numbers in turn.
1942            for (int i = 0; i < messageNumbers.length; i++) {
1943                messages[i] = getMessage(messageNumbers[i]);
1944            }
1945    
1946            return messages;
1947        }
1948        
1949        /**
1950         * Generate a message set string from a List of messages rather than an 
1951         * array.
1952         * 
1953         * @param messages The List of messages.
1954         * 
1955         * @return The evaluated message set string. 
1956         * @exception MessagingException
1957         */
1958        protected String generateMessageSet(List messages) throws MessagingException {
1959            Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]); 
1960            return generateMessageSet(msgs); 
1961        }
1962    
1963    
1964        /**
1965         * Take an array of messages and generate a String <message set>
1966         * argument as specified by RFC 2060.  The message set argument
1967         * is a comma-separated list of message number ranges.  A
1968         * single element range is just one number.  A longer range is
1969         * a pair of numbers separated by a ":".  The generated string
1970         * should not have any blanks.  This will attempt to locate
1971         * consequetive ranges of message numbers, but will only do this
1972         * for messages that are already ordered in the array (i.e., we
1973         * don't try to sort).  Expunged messages are excluded from the
1974         * search, since they don't exist anymore.  A valid search string
1975         * will look something like this:
1976         *
1977         *    "3,6:10,15,21:35"
1978         *
1979         * @param messages The array of messages we generate from.
1980         *
1981         * @return A string formatted version of these message identifiers that
1982         *         can be used on an IMAP command.
1983         */
1984        protected String generateMessageSet(Message[] messages) throws MessagingException {
1985            StringBuffer set = new StringBuffer();
1986    
1987            for (int i = 0; i < messages.length; i++) {
1988                // first scan the list looking for a "live" message.
1989                IMAPMessage start = (IMAPMessage)messages[i];
1990                if (!start.isExpunged()) {
1991    
1992                    // we can go ahead and add this to the list now.  If we find this is the start of a
1993                    // range, we'll tack on the ":end" bit once we find the last message in the range.
1994                    if (set.length() != 0) {
1995                        // only append the comma if not the first element of the list
1996                        set.append(',');
1997                    }
1998    
1999                    // append the first number.  NOTE:  We append this directly rather than 
2000                    // use appendInteger(), which appends it using atom rules. 
2001                    set.append(Integer.toString(start.getSequenceNumber()));
2002    
2003                    // ok, we have a live one.  Now scan the list from here looking for the end of
2004                    // a range of consequetive messages.
2005                    int endIndex = -1; ;
2006                    // get the number we're checking against.
2007                    int previousSequence = start.getSequenceNumber();
2008                    for (int j = i + 1; j < messages.length; j++) {
2009                        IMAPMessage message = (IMAPMessage)messages[j];
2010                        if (!message.isExpunged()) {
2011                            // still consequetive?
2012                            if (message.getSequenceNumber() == previousSequence + 1) {
2013                                // step this for the next check.
2014                                previousSequence++;
2015                                // record this as the current end of the range.
2016                                endIndex = j;
2017                            }
2018                            else {
2019                                // found a non-consequetive one, stop here
2020                                break;
2021                            }
2022                        }
2023                    }
2024    
2025                    // have a range end point?  Add the range specifier and step the loop index point
2026                    // to skip over this
2027                    if (endIndex != -1) {
2028                        // pick up the scan at the next location
2029                        i = endIndex;
2030    
2031                        set.append(':');
2032                        set.append(Integer.toString(((IMAPMessage)messages[endIndex]).getSequenceNumber()));
2033                    }
2034                }
2035            }
2036    
2037            // return null for an empty list. This is possible because either an empty array has been handed to
2038            // us or all of the messages in the array have been expunged.
2039            if (set.length() == 0) {
2040                return null;
2041            }
2042            return set.toString();
2043        }
2044        
2045        /**
2046         * Verify that this folder exists on the server before 
2047         * performning an operation that requires a valid 
2048         * Folder instance. 
2049         * 
2050         * @exception MessagingException
2051         */
2052        protected void checkFolderValidity() throws MessagingException {
2053            // if we are holding a current listinfo response, then 
2054            // we have chached existance information.  In that case, 
2055            // all of our status is presumed up-to-date and we can go 
2056            // with that.  If we don't have the information, then we 
2057            // ping the server for it. 
2058            if (listInfo == null && !exists()) {
2059                throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server"); 
2060            }
2061        }
2062        
2063        
2064        /**
2065         * Check if a Message is properly within the target 
2066         * folder. 
2067         * 
2068         * @param msg    The message we're checking.
2069         * 
2070         * @exception MessagingException
2071         */
2072        protected void checkMessageFolder(Message msg) throws MessagingException {
2073            if (msg.getFolder() != this) {
2074                throw new NoSuchElementException("Message is not within the target Folder"); 
2075            }
2076        }
2077    
2078    
2079        /**
2080         * Search a list of LIST responses for one containing information
2081         * for a particular mailbox name.
2082         *
2083         * @param responses The list of responses.
2084         * @param name      The desired mailbox name.
2085         *
2086         * @return The IMAPListResponse information for the requested name.
2087         */
2088        protected IMAPListResponse findListResponse(List responses, String name) {
2089            for (int i = 0; i < responses.size(); i++) {
2090                IMAPListResponse response = (IMAPListResponse)responses.get(i);
2091                if (response.mailboxName.equals(name)) {
2092                    return response;
2093                }
2094            }
2095            return null;
2096        }
2097        
2098        
2099        /**
2100         * Protected class intended for subclass overrides.  For normal folders, 
2101         * the mailbox name is fullname.  For Namespace root folders, the mailbox
2102         * name is the prefix + separator.  
2103         * 
2104         * @return The string name to use as the mailbox name for exists() and issubscribed() 
2105         *         calls.
2106         */
2107        protected String getMailBoxName() {
2108            return fullname; 
2109        }
2110        
2111        /**
2112         * Handle an unsolicited response from the server.  Most unsolicited responses 
2113         * are replies to specific commands sent to the server.  The remainder must 
2114         * be handled by the Store or the Folder using the connection.  These are 
2115         * critical to handle, as events such as expunged messages will alter the 
2116         * sequence numbers of the live messages.  We need to keep things in sync.
2117         * 
2118         * @param response The UntaggedResponse to process.
2119         * 
2120         * @return true if we handled this response and no further handling is required.  false
2121         *         means this one wasn't one of ours.
2122         */
2123        public boolean handleResponse(IMAPUntaggedResponse response) {
2124            // "you've got mail".  The message count has been updated.  There 
2125            // are two posibilities.  Either there really are new messages, or 
2126            // this is an update following an expunge.  If there are new messages, 
2127            // we need to update the message cache and broadcast the change to 
2128            // any listeners.
2129            if (response.isKeyword("EXISTS")) {
2130                // we need to update our cache, and also retrieve the new messages and 
2131                // send them out in a broadcast update. 
2132                int oldCount = maxSequenceNumber; 
2133                maxSequenceNumber = ((IMAPSizeResponse)response).getSize(); 
2134                populateMessageCache();  
2135                // has the size grown?  We have to send the "you've got mail" announcement. 
2136                if (oldCount < maxSequenceNumber) {
2137                    try {
2138                        Message[] messages = getMessages(oldCount + 1, maxSequenceNumber); 
2139                        notifyMessageAddedListeners(messages); 
2140                    } catch (MessagingException e) {
2141                        // should never happen in this context 
2142                    }
2143                }
2144                return true; 
2145            }
2146            // "you had mail".  A message was expunged from the server.  This MUST 
2147            // be processed immediately, as any subsequent expunge messages will 
2148            // shift the message numbers as a result of previous messages getting 
2149            // removed.  We need to keep our internal cache in sync with the server. 
2150            else if (response.isKeyword("EXPUNGE")) {
2151                int messageNumber = ((IMAPSizeResponse)response).getSize(); 
2152                try {
2153                    Message message = expungeMessage(messageNumber); 
2154    
2155                    // broadcast the message update. 
2156                    notifyMessageRemovedListeners(false, new Message[] {message}); 
2157                } catch (MessagingException e) {
2158                }
2159                // we handled this one. 
2160                return true; 
2161            }
2162            // just an update of recently arrived stuff?  Just update the field. 
2163            else if (response.isKeyword("RECENT")) {
2164                recentMessages = ((IMAPSizeResponse)response).getSize();  
2165                return true; 
2166            }
2167            // The spec is not particularly clear what types of unsolicited 
2168            // FETCH response can be sent.  The only one that is specifically 
2169            // spelled out is flag updates.  If this is one of those, then 
2170            // handle it. 
2171            else if (response.isKeyword("FETCH")) {
2172                IMAPFetchResponse fetch = (IMAPFetchResponse)response;            
2173                IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS); 
2174                // if this is a flags response, get the message and update 
2175                if (flags != null) {
2176                    try {
2177                        // get the updated message and update the internal state. 
2178                        IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber); 
2179                        // this shouldn't happen, but it might have been expunged too. 
2180                        if (message != null) {
2181                            message.updateMessageInformation(fetch); 
2182                        }
2183                        notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
2184                    } catch (MessagingException e) {
2185                    }
2186                    return true; 
2187                }
2188            }
2189            // this is a BYE response on our connection.  This forces us to close, but 
2190            // when we return the connection, the pool needs to get rid of it. 
2191            else if (response.isKeyword("BYE")) {
2192                // this is essentially a close event.  We need to clean everything up 
2193                // and make sure our connection is not returned to the general pool. 
2194                try {
2195                    cleanupFolder(false, true); 
2196                } catch (MessagingException e) {
2197                }
2198                return true; 
2199            }
2200            
2201            // not a response the folder knows how to deal with. 
2202            return false; 
2203        }
2204        
2205    // The following set of methods are extensions that exist in the Sun implementation.  They     
2206    // match the Sun version in intent, but are not 100% compatible because the Sun implementation     
2207    // uses com.sun.* class instances as opposed to the org.apache.geronimo.* classes.     
2208        
2209        
2210        
2211        /**
2212         *   Remove an entry from the access control list for this folder.
2213         * 
2214         * @param acl    The ACL element to remove.
2215         * 
2216         * @exception MessagingException
2217         */
2218        public synchronized void removeACL(ACL acl) throws MessagingException {
2219            // ask the store to kindly hook us up with a connection.
2220            IMAPConnection connection = getConnection(); 
2221    
2222            try {
2223                // the connection does the heavy lifting 
2224                connection.removeACLRights(fullname, acl); 
2225            } finally {
2226                releaseConnection(connection); 
2227            }
2228        }
2229        
2230        
2231        /**
2232         *   Add an entry to the access control list for this folder.
2233         * 
2234         * @param acl    The new ACL to add.
2235         */
2236        public synchronized void addACL(ACL acl) throws MessagingException {
2237            // ask the store to kindly hook us up with a connection.
2238            IMAPConnection connection = getConnection(); 
2239    
2240            try {
2241                // the connection does the heavy lifting 
2242                connection.setACLRights(fullname, acl); 
2243            } finally {
2244                releaseConnection(connection); 
2245            }
2246        }
2247        
2248        
2249        /**
2250         * Add Rights to a given ACL entry.
2251         * 
2252         * @param acl    The target ACL to update.
2253         * 
2254         * @exception MessagingException
2255         */
2256        public synchronized void addRights(ACL acl) throws MessagingException {
2257            // ask the store to kindly hook us up with a connection.
2258            IMAPConnection connection = getConnection(); 
2259    
2260            try {
2261                // the connection does the heavy lifting 
2262                connection.addACLRights(fullname, acl); 
2263            } finally {
2264                releaseConnection(connection); 
2265            }
2266        }
2267        
2268        
2269        /**
2270         * Remove ACL Rights from a folder.
2271         * 
2272         * @param acl    The ACL describing the Rights to remove.
2273         * 
2274         * @exception MessagingException
2275         */
2276        public synchronized void removeRights(ACL acl) throws MessagingException {
2277            // ask the store to kindly hook us up with a connection.
2278            IMAPConnection connection = getConnection(); 
2279    
2280            try {
2281                // the connection does the heavy lifting 
2282                connection.removeACLRights(fullname, acl); 
2283            } finally {
2284                releaseConnection(connection); 
2285            }
2286        }
2287        
2288        
2289        /**
2290         *   List the rights associated with a given name.
2291         * 
2292         * @param name   The user name for the Rights.
2293         * 
2294         * @return The set of Rights associated with the user name.
2295         * @exception MessagingException
2296         */
2297        public synchronized Rights[] listRights(String name) throws MessagingException {
2298            // ask the store to kindly hook us up with a connection.
2299            IMAPConnection connection = getConnection(); 
2300    
2301            try {
2302                // the connection does the heavy lifting 
2303                return connection.listACLRights(fullname, name); 
2304            } finally {
2305                releaseConnection(connection); 
2306            }
2307        }
2308        
2309        
2310        /**
2311         *   List the rights for the currently authenticated user.
2312         * 
2313         * @return The set of Rights for the current user.
2314         * @exception MessagingException
2315         */
2316        public synchronized Rights myRights() throws MessagingException {
2317            // ask the store to kindly hook us up with a connection.
2318            IMAPConnection connection = getConnection(); 
2319    
2320            try {
2321                // the connection does the heavy lifting 
2322                return connection.getMyRights(fullname); 
2323            } finally {
2324                releaseConnection(connection); 
2325            }
2326        }
2327        
2328        /**
2329         * Get the quota values assigned to the current folder.
2330         * 
2331         * @return The Quota information for the folder.
2332         * @exception MessagingException
2333         */
2334        public synchronized Quota[] getQuota() throws MessagingException {
2335            // ask the store to kindly hook us up with a connection.
2336            IMAPConnection connection = getConnection(); 
2337    
2338            try {
2339                // the connection does the heavy lifting 
2340                return connection.fetchQuotaRoot(fullname); 
2341            } finally {
2342                releaseConnection(connection); 
2343            }
2344        }
2345        
2346        /**
2347         * Set the quota value for a quota root
2348         * 
2349         * @param quota  The new quota information to set.
2350         * 
2351         * @exception MessagingException
2352         */
2353        public synchronized void setQuota(Quota quota) throws MessagingException {
2354            // ask the store to kindly hook us up with a connection.
2355            IMAPConnection connection = getConnection(); 
2356    
2357            try {
2358                // the connection does the heavy lifting 
2359                connection.setQuota(quota); 
2360            } finally {
2361                releaseConnection(connection); 
2362            }
2363        }
2364        
2365        /**
2366         * Get the set of attributes defined for the folder
2367         * as the set of capabilities returned when the folder 
2368         * was opened.
2369         * 
2370         * @return The set of attributes associated with the folder.
2371         * @exception MessagingException
2372         */
2373        public synchronized String[] getAttributes() throws MessagingException {
2374            // if we don't have the LIST command information for this folder yet, 
2375            // call exists() to force this to be updated so we can return. 
2376            if (listInfo == null) {
2377                // return a null reference if this is not valid. 
2378                if (!exists()) {
2379                    return null; 
2380                }
2381            }
2382            // return a copy of the attributes array. 
2383            return (String[])listInfo.attributes.clone(); 
2384        }
2385    }
2386