View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.geronimo.javamail.store.imap;
19  
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.LinkedList;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.NoSuchElementException;
30  import java.util.Vector;
31  
32  import javax.mail.*;
33  import javax.mail.event.ConnectionEvent;
34  import javax.mail.event.FolderEvent;
35  import javax.mail.event.MessageChangedEvent;
36  import javax.mail.search.FlagTerm;
37  import javax.mail.search.SearchTerm;
38  
39  import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
40  import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem;
41  import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse;
42  import org.apache.geronimo.javamail.store.imap.connection.IMAPFlags;
43  import org.apache.geronimo.javamail.store.imap.connection.IMAPListResponse;
44  import org.apache.geronimo.javamail.store.imap.connection.IMAPMailboxStatus;
45  import org.apache.geronimo.javamail.store.imap.connection.IMAPSizeResponse;
46  import org.apache.geronimo.javamail.store.imap.connection.IMAPUid;
47  import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponse;
48  import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponseHandler;
49  
50  /**
51   * The base IMAP implementation of the javax.mail.Folder
52   * This is a base class for both the Root IMAP server and each IMAP group folder.
53   * @see javax.mail.Folder
54   *
55   * @version $Rev: 594520 $
56   */
57  public class IMAPFolder extends Folder implements UIDFolder, IMAPUntaggedResponseHandler {
58  
59      /**
60       * Special profile item used for fetching SIZE and HEADER information.
61       * These items are extensions that Sun has added to their IMAPFolder immplementation. 
62       * We're supporting the same set. 
63       */
64      public static class FetchProfileItem extends FetchProfile.Item {
65          public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS");
66          public static final FetchProfileItem SIZE = new FetchProfileItem("SIZE");
67  
68          protected FetchProfileItem(String name) {
69              super(name);
70          }
71      }
72      
73      // marker that we don't know the separator yet for this folder. 
74      // This occurs when we obtain a folder reference from the 
75      // default folder.  At that point, we've not queried the 
76      // server for specifics yet. 
77      static final protected char UNDETERMINED = 0; 
78      
79      // our attached session
80      protected Session session;
81      // retrieved messages, mapped by sequence number.
82      protected LinkedList messages;
83      // mappings of UIDs to retrieved messages.
84      protected Map uidCache;
85  
86      // the separator the server indicates is used as the hierarchy separator
87      protected char separator;
88      // the "full" name of the folder.  This is the fully qualified path name for the folder returned by
89      // the IMAP server.  Elements of the hierarchy are delimited by "separator" characters.
90      protected String fullname;
91      // the name of this folder.  The is the last element of the fully qualified name.
92      protected String name;
93      // the folder open state
94  	protected boolean folderOpen = false;
95      // the type information on what the folder can hold
96      protected int folderType;
97      // the subscription status
98      protected boolean subscribed = false;
99  
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