KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > mail > imap > IMAPFolder


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21
22 /*
23  * @(#)IMAPFolder.java 1.74 05/11/17
24  *
25  * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
26  */

27
28 package com.sun.mail.imap;
29
30 import java.util.Date JavaDoc;
31 import java.util.Vector JavaDoc;
32 import java.util.Hashtable JavaDoc;
33 import java.util.NoSuchElementException JavaDoc;
34 import java.io.*;
35
36 import javax.mail.*;
37 import javax.mail.event.*;
38 import javax.mail.internet.*;
39 import javax.mail.search.*;
40
41 import com.sun.mail.util.*;
42 import com.sun.mail.iap.*;
43 import com.sun.mail.imap.protocol.*;
44
45 /**
46  * This class implements an IMAP folder. <p>
47  *
48  * A closed IMAPFolder object shares a protocol connection with its IMAPStore
49  * object. When the folder is opened, it gets its own protocol connection. <p>
50  *
51  * Applications that need to make use of IMAP-specific features may cast
52  * a <code>Folder</code> object to an <code>IMAPFolder</code> object and
53  * use the methods on this class. The {@link #getQuota getQuota} and
54  * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
55  * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
56  * for more information. <p>
57  *
58  * The {@link #getACL getACL}, {@link #addACL addACL},
59  * {@link #removeACL removeACL}, {@link #addRights addRights},
60  * {@link #removeRights removeRights}, {@link #listRights listRights}, and
61  * {@link #myRights myRights} methods support the IMAP ACL extension.
62  * Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A>
63  * for more information. <p>
64  *
65  * The {@link #doCommand doCommand} method and
66  * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand}
67  * interface support use of arbitrary IMAP protocol commands. <p>
68  *
69  * See the <a HREF="package-summary.html">com.sun.mail.imap</a> package
70  * documentation for further information on the IMAP protocol provider. <p>
71  *
72  * <strong>WARNING:</strong> The APIs unique to this class should be
73  * considered <strong>EXPERIMENTAL</strong>. They may be changed in the
74  * future in ways that are incompatible with applications using the
75  * current APIs.
76  *
77  * @version 1.74, 05/11/17
78  * @author John Mani
79  * @author Bill Shannon
80  * @author Jim Glennon
81  */

82
83 /*
84  * The folder object itself serves as a lock for the folder's state
85  * EXCEPT for the message cache (see below), typically by using
86  * synchronized methods. When checking that a folder is open or
87  * closed, the folder's lock must be held. It's important that the
88  * folder's lock is acquired before the messageCacheLock (see below).
89  * Thus, the locking hierarchy is that the folder lock, while optional,
90  * must be acquired before the messageCacheLock, if it's acquired at
91  * all. Be especially careful of callbacks that occur while holding
92  * the messageCacheLock into (e.g.) superclass Folder methods that are
93  * synchronized. Note that methods in IMAPMessage will acquire the
94  * messageCacheLock without acquiring the folder lock. <p>
95  *
96  * When a folder is opened, it creates a messageCache (a Vector) of
97  * empty IMAPMessage objects. Each Message has a messageNumber - which
98  * is its index into the messageCache, and a sequenceNumber - which is
99  * its IMAP sequence-number. All operations on a Message which involve
100  * communication with the server, use the message's sequenceNumber. <p>
101  *
102  * The most important thing to note here is that the server can send
103  * unsolicited EXPUNGE notifications as part of the responses for "most"
104  * commands. Refer RFC2060, sections 5.3 & 5.5 for gory details. Also,
105  * the server sends these notifications AFTER the message has been
106  * expunged. And once a message is expunged, the sequence-numbers of
107  * those messages after the expunged one are renumbered. This essentially
108  * means that the mapping between *any* Message and its sequence-number
109  * can change in the period when a IMAP command is issued and its responses
110  * are processed. Hence we impose a strict locking model as follows: <p>
111  *
112  * We define one mutex per folder - this is just a Java Object (named
113  * messageCacheLock). Any time a command is to be issued to the IMAP
114  * server (i.e., anytime the corresponding IMAPProtocol method is
115  * invoked), follow the below style:
116  *
117  * synchronized (messageCacheLock) { // ACQUIRE LOCK
118  * issue command ()
119  *
120  * // The response processing is typically done within
121  * // the handleResponse() callback. A few commands (Fetch,
122  * // Expunge) return *all* responses and hence their
123  * // processing is done here itself. Now, as part of the
124  * // processing unsolicited EXPUNGE responses, we renumber
125  * // the necessary sequence-numbers. Thus the renumbering
126  * // happens within this critical-region, surrounded by
127  * // locks.
128  * process responses ()
129  * } // RELEASE LOCK
130  *
131  * This technique is used both by methods in IMAPFolder and by methods
132  * in IMAPMessage and other classes that operate on data in the folder.
133  * Note that holding the messageCacheLock has the side effect of
134  * preventing the folder from being closed, and thus ensuring that the
135  * folder's protocol object is still valid. The protocol object should
136  * only be accessed while holding the messageCacheLock (except for calls
137  * to IMAPProtocol.isREV1(), which don't need to be protected because it
138  * doesn't access the server).
139  *
140  * Note that interactions with the Store's protocol connection do
141  * not have to be protected as above, since the Store's protocol is
142  * never in a "meaningful" SELECT-ed state.
143  */

144
145 public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
146     
147     protected String JavaDoc fullName; // full name
148
protected String JavaDoc name; // name
149
protected int type; // folder type.
150
protected char separator; // separator
151
protected Flags availableFlags; // available flags
152
protected Flags permanentFlags; // permanent flags
153
protected boolean exists = false; // whether this folder really exists ?
154
protected boolean isNamespace = false; // folder is a namespace name
155
protected String JavaDoc[] attributes; // name attributes from LIST response
156

157     protected IMAPProtocol protocol; // this folder's own protocol object
158
protected Vector JavaDoc messageCache; // message cache
159
protected Object JavaDoc messageCacheLock; // accessor lock for message cache
160

161     protected Hashtable JavaDoc uidTable; // UID->Message hashtable
162

163     /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL).
164      * We use '?' (a non 7bit character) to indicate that we havent
165      * yet determined what the separator character is.
166      * We use '' (NUL) to indicate that no separator character
167      * exists, i.e., a flat hierarchy
168      */

169     static final protected char UNKNOWN_SEPARATOR = '\uffff';
170
171     private boolean opened = false; // is this folder opened ?
172

173     /* This field tracks the state of this folder. If the folder is closed
174      * due to external causes (i.e, not thru the close() method), then
175      * this field will remain false. If the folder is closed thru the
176      * close() method, then this field is set to true.
177      *
178      * If reallyClosed is false, then a FolderClosedException is
179      * generated when a method is invoked on any Messaging object
180      * owned by this folder. If reallyClosed is true, then the
181      * IllegalStateException runtime exception is thrown.
182      */

183     private boolean reallyClosed = true;
184
185     private int total = -1; // total number of messages in the
186
// message cache
187
private int recent = -1; // number of recent messages
188
private int realTotal = -1; // total number of messages on
189
// the server
190
private int uidvalidity = -1; // UIDValidity
191
private int uidnext = -1; // UIDNext
192
private boolean doExpungeNotification = true; // used in expunge handler
193

194     private Status cachedStatus = null;
195     private long cachedStatusTime = 0;
196
197     private boolean debug = false;
198     private PrintStream out; // debug output stream
199

200     private boolean connectionPoolDebug;
201
202     /**
203      * A fetch profile item for fetching headers.
204      * This inner class extends the <code>FetchProfile.Item</code>
205      * class to add new FetchProfile item types, specific to IMAPFolders.
206      *
207      * @see FetchProfile
208      */

209     public static class FetchProfileItem extends FetchProfile.Item {
210     protected FetchProfileItem(String JavaDoc name) {
211         super(name);
212     }
213
214     /**
215      * HEADERS is a fetch profile item that can be included in a
216      * <code>FetchProfile</code> during a fetch request to a Folder.
217      * This item indicates that the headers for messages in the specified
218      * range are desired to be prefetched. <p>
219      *
220      * An example of how a client uses this is below: <p>
221      * <blockquote><pre>
222      *
223      * FetchProfile fp = new FetchProfile();
224      * fp.add(IMAPFolder.FetchProfileItem.HEADERS);
225      * folder.fetch(msgs, fp);
226      *
227      * </pre></blockquote><p>
228      */

229     public static final FetchProfileItem HEADERS =
230         new FetchProfileItem("HEADERS");
231
232     /**
233      * SIZE is a fetch profile item that can be included in a
234      * <code>FetchProfile</code> during a fetch request to a Folder.
235      * This item indicates that the sizes of the messages in the specified
236      * range are desired to be prefetched. <p>
237      *
238      * SIZE should move to FetchProfile.Item in JavaMail 1.3.
239      */

240     public static final FetchProfileItem SIZE =
241         new FetchProfileItem("SIZE");
242     }
243
244     /**
245      * Constructor used to create a possibly non-existent folder.
246      *
247      * @param fullName fullname of this folder
248      * @param separator the default separator character for this
249      * folder's namespace
250      * @param store the Store
251      */

252     protected IMAPFolder(String JavaDoc fullName, char separator, IMAPStore store) {
253     this(fullName, separator, store, false);
254     }
255
256     /**
257      * Constructor used to create a possibly non-existent folder.
258      *
259      * @param fullName fullname of this folder
260      * @param separator the default separator character for this
261      * folder's namespace
262      * @param store the Store
263      */

264     protected IMAPFolder(String JavaDoc fullName, char separator, IMAPStore store,
265                 boolean isNamespace) {
266     super(store);
267     if (fullName == null)
268         throw new NullPointerException JavaDoc("Folder name is null");
269     this.fullName = fullName;
270     this.separator = separator;
271     this.isNamespace = isNamespace;
272     messageCacheLock = new Object JavaDoc();
273         debug = store.getSession().getDebug();
274         connectionPoolDebug = ((IMAPStore)store).getConnectionPoolDebug();
275     out = store.getSession().getDebugOut();
276     if (out == null) // should never happen
277
out = System.out;
278     }
279
280     /**
281      * Constructor used to create an existing folder.
282      */

283     protected IMAPFolder(ListInfo li, IMAPStore store) {
284     this(li.name, li.separator, store);
285
286     if (li.hasInferiors)
287         type |= HOLDS_FOLDERS;
288     if (li.canOpen)
289         type |= HOLDS_MESSAGES;
290     exists = true;
291     attributes = li.attrs;
292     }
293     
294     /* Ensure that this folder exists. If 'exists' has been set to true,
295      * we don't attempt to validate it with the server again. Note that
296      * this can result in a possible loss of sync with the server.
297      */

298     private void checkExists() throws MessagingException {
299     // If the boolean field 'exists' is false, check with the
300
// server by invoking exists() ..
301
if (!exists && !exists())
302         throw new FolderNotFoundException(
303         this, fullName + " not found");
304     }
305
306     /*
307      * Ensure the folder is closed.
308      * ASSERT: Must be called with this folder's synchronization lock held.
309      */

310     private void checkClosed() {
311     if (opened)
312         throw new IllegalStateException JavaDoc(
313         "This operation is not allowed on an open folder"
314         );
315     }
316
317     /*
318      * Ensure the folder is open.
319      * ASSERT: Must be called with this folder's synchronization lock held.
320      */

321     private void checkOpened() throws FolderClosedException {
322     if (!opened) {
323         if (reallyClosed)
324         throw new IllegalStateException JavaDoc(
325             "This operation is not allowed on a closed folder"
326             );
327         else // Folder was closed "implicitly"
328
throw new FolderClosedException(this,
329             "Lost folder connection to server"
330         );
331     }
332     }
333
334     /* Check that the given message number is within the range
335      * of messages present in this folder. If the message
336      * number is out of range, we ping the server to obtain any
337      * pending new message notifications from the server.
338      */

339     private void checkRange(int msgno) throws MessagingException {
340     if (msgno < 1) // message-numbers start at 1
341
throw new IndexOutOfBoundsException JavaDoc();
342
343     if (msgno <= total)
344         return;
345
346     // Out of range, let's ping the server and see if
347
// the server has more messages for us.
348

349     synchronized(messageCacheLock) { // Acquire lock
350
try {
351         keepConnectionAlive(false);
352         } catch (ConnectionException cex) {
353         // Oops, lost connection
354
throw new FolderClosedException(this, cex.getMessage());
355         } catch (ProtocolException pex) {
356         throw new MessagingException(pex.getMessage(), pex);
357         }
358     } // Release lock
359

360     if (msgno > total) // Still out of range ? Throw up ...
361
throw new IndexOutOfBoundsException JavaDoc();
362     }
363
364     /* Check whether the given flags are supported by this server,
365      * and also verify that the folder allows setting flags.
366      */

367     private void checkFlags(Flags flags) throws MessagingException {
368     if (mode != READ_WRITE)
369         throw new IllegalStateException JavaDoc(
370         "Cannot change flags on READ_ONLY folder: " + fullName
371         );
372     if (!availableFlags.contains(flags))
373         throw new MessagingException(
374         "These flags are not supported by this implementation"
375         );
376     }
377
378     /**
379      * Get the name of this folder.
380      */

381     public String JavaDoc getName() {
382     /* Return the last component of this Folder's full name.
383      * Folder components are delimited by the separator character.
384      */

385     if (name == null) {
386         try {
387         name = fullName.substring(
388                 fullName.lastIndexOf(getSeparator()) + 1
389             );
390         } catch (MessagingException mex) { }
391     }
392     return name;
393     }
394
395     /**
396      * Get the fullname of this folder.
397      */

398     public String JavaDoc getFullName() {
399     return fullName;
400     }
401
402     /**
403      * Get this folder's parent.
404      */

405     public Folder getParent() throws MessagingException {
406     char c = getSeparator();
407     int index;
408     if ((index = fullName.lastIndexOf(c)) != -1)
409         return new IMAPFolder(fullName.substring(0, index),
410                   c, (IMAPStore)store);
411     else
412         return new DefaultFolder((IMAPStore)store);
413     }
414
415     /**
416      * Check whether this folder really exists on the server.
417      */

418     public boolean exists() throws MessagingException {
419     // Check whether this folder exists ..
420
ListInfo[] li = null;
421     final String JavaDoc lname;
422     if (isNamespace && separator != '\0')
423         lname = fullName + separator;
424     else
425         lname = fullName;
426
427     li = (ListInfo[])doCommand(new ProtocolCommand() {
428         public Object JavaDoc doCommand(IMAPProtocol p) throws ProtocolException {
429         return p.list("", lname);
430         }
431     });
432
433     if (li != null) {
434         int i = findName(li, lname);
435         fullName = li[i].name;
436         separator = li[i].separator;
437         int len = fullName.length();
438         if (separator != '\0' && len > 0 &&
439             fullName.charAt(len - 1) == separator) {
440         fullName = fullName.substring(0, len - 1);
441         }
442         type = 0;
443         if (li[i].hasInferiors)
444         type |= HOLDS_FOLDERS;
445         if (li[i].canOpen)
446         type |= HOLDS_MESSAGES;
447         exists = true;
448         attributes = li[i].attrs;
449     } else
450         exists = false;
451
452     return exists;
453     }
454
455     /**
456      * Which entry in <code>li</code> matches <code>lname</code>?
457      * If the name contains wildcards, more than one entry may be
458      * returned.
459      */

460     private int findName(ListInfo[] li, String JavaDoc lname) {
461     int i;
462     // if the name contains a wildcard, there might be more than one
463
for (i = 0; i < li.length; i++) {
464         if (li[i].name.equals(lname))
465         break;
466     }
467     if (i >= li.length) { // nothing matched exactly
468
// XXX - possibly should fail? But what if server
469
// is case insensitive and returns the preferred
470
// case of the name here?
471
i = 0; // use first one
472
}
473     return i;
474     }
475
476     /**
477      * List all subfolders matching the specified pattern.
478      */

479     public Folder[] list(String JavaDoc pattern) throws MessagingException {
480     return doList(pattern, false);
481     }
482
483     /**
484      * List all subscribed subfolders matching the specified pattern.
485      */

486     public Folder[] listSubscribed(String JavaDoc pattern) throws MessagingException {
487     return doList(pattern, true);
488     }
489
490     private Folder[] doList(final String JavaDoc pattern, final boolean subscribed)
491         throws MessagingException {
492     checkExists(); // insure that this folder does exist.
493

494     if (!isDirectory()) // Why waste a roundtrip to the server ?
495
return new Folder[0];
496
497     final char c = getSeparator();
498
499     ListInfo[] li = (ListInfo[])doCommandIgnoreFailure(
500         new ProtocolCommand() {
501         public Object JavaDoc doCommand(IMAPProtocol p)
502             throws ProtocolException {
503             if (subscribed)
504             return p.lsub("", fullName + c + pattern);
505             else
506             return p.list("", fullName + c + pattern);
507         }
508         });
509
510     if (li == null)
511         return new Folder[0];
512
513     /*
514      * The UW based IMAP4 servers (e.g. SIMS2.0) include
515      * current folder (terminated with the separator), when
516      * the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%>
517      * returns "mail/" as the first LIST response.
518      *
519      * Doesn't make sense to include the current folder in this
520      * case, so we filter it out. Note that I'm assuming that
521      * the offending response is the *first* one, my experiments
522      * with the UW & SIMS2.0 servers indicate that ..
523      */

524     int start = 0;
525     // Check the first LIST response.
526
if (li.length > 0 && li[0].name.equals(fullName + c))
527         start = 1; // start from index = 1
528

529     IMAPFolder[] folders = new IMAPFolder[li.length - start];
530     for (int i = start; i < li.length; i++)
531         folders[i-start] = new IMAPFolder(li[i], (IMAPStore)store);
532     return folders;
533     }
534
535     /**
536      * Get the separator character.
537      */

538     public synchronized char getSeparator() throws MessagingException {
539     if (separator == UNKNOWN_SEPARATOR) {
540         ListInfo[] li = null;
541
542         li = (ListInfo[])doCommand(new ProtocolCommand() {
543         public Object JavaDoc doCommand(IMAPProtocol p)
544             throws ProtocolException {
545             // REV1 allows the following LIST format to obtain
546
// the hierarchy delimiter of non-existent folders
547
if (p.isREV1()) // IMAP4rev1
548
return p.list(fullName, "");
549             else // IMAP4, note that this folder must exist for this
550
// to work :(
551
return p.list("", fullName);
552         }
553         });
554
555         if (li != null)
556         separator = li[0].separator;
557         else
558         separator = '/'; // punt !
559
}
560     return separator;
561     }
562
563     /**
564      * Get the type of this folder.
565      */

566     public int getType() throws MessagingException {
567     checkExists();
568     return type;
569     }
570     
571     /**
572      * Check whether this folder is subscribed. <p>
573      */

574     public boolean isSubscribed() {
575     ListInfo[] li = null;
576     final String JavaDoc lname;
577     if (isNamespace && separator != '\0')
578         lname = fullName + separator;
579     else
580         lname = fullName;
581
582     try {
583         li = (ListInfo[])doProtocolCommand(new ProtocolCommand() {
584         public Object JavaDoc doCommand(IMAPProtocol p)
585             throws ProtocolException {
586             return p.lsub("", lname);
587         }
588         });
589     } catch (ProtocolException pex) {
590         }
591
592     if (li != null) {
593         int i = findName(li, lname);
594         return li[i].canOpen;
595     } else
596         return false;
597     }
598
599     /**
600      * Subscribe/Unsubscribe this folder.
601      */

602     public void setSubscribed(final boolean subscribe)
603             throws MessagingException {
604     doCommandIgnoreFailure(new ProtocolCommand() {
605         public Object JavaDoc doCommand(IMAPProtocol p) throws ProtocolException {
606         if (subscribe)
607             p.subscribe(fullName);
608         else
609             p.unsubscribe(fullName);
610         return null;
611         }
612     });
613     }
614     
615     /**
616      * Create this folder, with the specified type.
617      */

618     public synchronized boolean create(final int type)
619                 throws MessagingException {
620
621     char c = 0;
622     if ((type & HOLDS_MESSAGES) == 0) // only holds folders
623
c = getSeparator();
624     final char sep = c;
625     Object JavaDoc ret = doCommandIgnoreFailure(new ProtocolCommand() {
626         public Object JavaDoc doCommand(IMAPProtocol p)
627             throws ProtocolException {
628             if ((type & HOLDS_MESSAGES) == 0) // only holds folders
629
p.create(fullName + sep);
630             else {
631             p.create(fullName);
632
633             // Certain IMAP servers do not allow creation of folders
634
// that can contain messages *and* subfolders. So, if we
635
// were asked to create such a folder, we should verify
636
// that we could indeed do so.
637
if ((type & HOLDS_FOLDERS) != 0) {
638                 // we want to hold subfolders and messages. Check
639
// whether we could create such a folder.
640
ListInfo[] li = p.list("", fullName);
641                 if (li != null && !li[0].hasInferiors) {
642                 // Hmm ..the new folder
643
// doesn't support Inferiors ? Fail
644
p.delete(fullName);
645                 throw new ProtocolException("Unsupported type");
646                 }
647             }
648             }
649             return Boolean.TRUE;
650         }
651         });
652
653     if (ret == null)
654         return false; // CREATE failure, maybe this
655
// folder already exists ?
656

657     // exists = true;
658
// this.type = type;
659
boolean retb = exists(); // set exists, type, and attributes
660
// Notify listeners on self and our Store
661
notifyFolderListeners(FolderEvent.CREATED);
662     return retb;
663     }
664
665     /**
666      * Check whether this folder has new messages.
667      */

668     public synchronized boolean hasNewMessages() throws MessagingException {
669     checkExists();
670
671     if (opened) { // If we are open, we already have this information
672
// Folder is open, make sure information is up to date
673
synchronized(messageCacheLock) {
674         // tickle the folder and store connections.
675
try {
676             keepConnectionAlive(true);
677         } catch (ConnectionException cex) {
678             throw new FolderClosedException(this, cex.getMessage());
679         } catch (ProtocolException pex) {
680             throw new MessagingException(pex.getMessage(), pex);
681         }
682         }
683         return recent > 0 ? true : false;
684     }
685
686     // First, the cheap way - use LIST and look for the \Marked
687
// or \Unmarked tag
688

689     Boolean JavaDoc b = (Boolean JavaDoc)doCommandIgnoreFailure(new ProtocolCommand() {
690         public Object JavaDoc doCommand(IMAPProtocol p) throws ProtocolException {
691         ListInfo[] li = p.list("", fullName);
692         if (li != null) {
693             if (li[0].changeState == ListInfo.CHANGED)
694             return Boolean.TRUE;
695             else if (li[0].changeState == ListInfo.UNCHANGED)
696             return Boolean.FALSE;
697         }
698
699         // LIST didn't work. Try the hard way, using STATUS
700
Status status = getStatus();
701         if (status.recent > 0)
702             return Boolean.TRUE;
703         else
704             return Boolean.FALSE;
705         }
706     });
707     if (b == null)
708         // Probably doesn't support STATUS, tough luck.
709
return false;
710     return b.booleanValue();
711     }
712
713     /**
714      * Get the named subfolder. <p>
715      */

716     public Folder getFolder(String JavaDoc name) throws MessagingException {
717     // If we know that this folder is *not* a directory, don't
718
// send the request to the server at all ...
719
if (exists && !isDirectory())
720         throw new MessagingException("Cannot contain subfolders");
721
722     char c = getSeparator();
723     return new IMAPFolder(fullName + c + name, c, (IMAPStore)store);
724     }
725
726     /**
727      * Delete this folder.
728      */

729     public synchronized boolean delete(boolean recurse)
730             throws MessagingException {
731     checkClosed(); // insure that this folder is closed.
732

733     if (recurse) {
734         // Delete all subfolders.
735
Folder[] f = list();
736         for (int i = 0; i < f.length; i++)
737         f[i].delete(recurse); // ignore intermediate failures
738
}
739
740     // Attempt to delete this folder
741

742     Object JavaDoc ret = doCommandIgnoreFailure(new ProtocolCommand() {
743         public Object JavaDoc doCommand(IMAPProtocol p) throws ProtocolException {
744         p.delete(fullName);
745         return Boolean.TRUE;
746         }
747     });
748
749     if (ret == null)
750         // Non-existent folder/No permission ??
751
return false;
752
753     // DELETE succeeded.
754
exists = false;
755
756     // Notify listeners on self and our Store
757
notifyFolderListeners(FolderEvent.DELETED);
758     return true;
759     }
760
761     /**
762      * Rename this folder. <p>
763      */

764     public synchronized boolean renameTo(final Folder f)
765                 throws MessagingException {
766     checkClosed(); // insure that we are closed.
767
checkExists();
768     if (f.getStore() != store)
769         throw new MessagingException("Can't rename across Stores");
770
771
772     Object JavaDoc ret = doCommandIgnoreFailure(new ProtocolCommand() {
773         public Object JavaDoc doCommand(IMAPProtocol p) throws ProtocolException {
774         p.rename(fullName, f.getFullName());
775         return Boolean.TRUE;
776         }
777     });
778
779     if (ret == null)
780         return false;
781
782     exists = false;
783     notifyFolderRenamedListeners(f);
784     return true;
785     }
786
787     /**
788      * Open this folder in the given mode.
789      */

790     public synchronized void open(int mode) throws MessagingException {
791     checkClosed(); // insure that we are not already open
792

793     MailboxInfo mi = null;
794     // Request store for our own protocol connection.
795
protocol = ((IMAPStore)store).getProtocol(this);
796
797     CommandFailedException exc = null;
798     lock:
799     synchronized(messageCacheLock) { // Acquire messageCacheLock
800

801         /*
802          * Add response handler right away so we get any alerts or
803          * notifications that occur during the SELECT or EXAMINE.
804          * Have to be sure to remove it if we fail to open the
805          * folder.
806          */

807         protocol.addResponseHandler(this);
808
809         try {
810         if (mode == READ_ONLY)
811             mi = protocol.examine(fullName);
812         else
813             mi = protocol.select(fullName);
814         } catch (CommandFailedException cex) {
815         // got a NO; connection still good, return it
816
releaseProtocol(true);
817         protocol = null;
818         exc = cex;
819         break lock;
820         } catch (ProtocolException pex) {
821         // got a BAD or a BYE; connection may be bad, close it
822
try {
823             protocol.logout();
824         } catch (ProtocolException pex2) {
825             // ignore
826
} finally {
827             releaseProtocol(false);
828             protocol = null;
829             throw new MessagingException(pex.getMessage(), pex);
830         }
831         }
832
833         if (mi.mode != mode) {
834         if (mode == READ_WRITE && mi.mode == READ_ONLY &&
835             ((IMAPStore)store).allowReadOnlySelect()) {
836             ; // all ok, allow it
837
} else { // otherwise, it's an error
838
try {
839             // close mailbox and return connection
840
protocol.close();
841             releaseProtocol(true);
842             } catch (ProtocolException pex) {
843             // something went wrong, close connection
844
try {
845                 protocol.logout();
846             } catch (ProtocolException pex2) {
847                 // ignore
848
} finally {
849                 releaseProtocol(false);
850             }
851             } finally {
852             protocol = null;
853             throw new ReadOnlyFolderException(this,
854                       "Cannot open in desired mode");
855             }
856
857         }
858             }
859     
860         // Initialize stuff.
861
opened = true;
862         reallyClosed = false;
863         this.mode = mi.mode;
864         availableFlags = mi.availableFlags;
865         permanentFlags = mi.permanentFlags;
866         total = realTotal = mi.total;
867         recent = mi.recent;
868         uidvalidity = mi.uidvalidity;
869         uidnext = mi.uidnext;
870
871         // Create the message cache vector of appropriate size
872
messageCache = new Vector JavaDoc(total);
873         // Fill up the cache with light-weight IMAPMessage objects
874
for (int i = 0; i < total; i++)
875         messageCache.addElement(new IMAPMessage(this, i+1, i+1));
876
877     } // Release lock
878

879     /*
880      * Handle SELECT or EXAMINE failure after lock is released.
881      * Try to figure out why the operation failed so we can
882      * report a more reasonable exception.
883      */

884     if (exc != null) {
885         checkExists(); // throw exception if folder doesn't exist
886

887         if ((type & HOLDS_MESSAGES) == 0)
888         throw new MessagingException("folder cannot contain messages");
889         throw new MessagingException(exc.getMessage(), exc);
890     }
891
892     // notify listeners
893
notifyConnectionListeners(ConnectionEvent.OPENED);
894     }
895
896     /**
897      * Prefetch attributes, based on the given FetchProfile.
898      */

899     public synchronized void fetch(Message[] msgs, FetchProfile fp)
900             throws MessagingException {
901     checkOpened();
902     IMAPMessage.fetch(this, msgs, fp);
903     }
904
905     /**
906      * Set the specified flags for the given array of messages.
907      */

908     public synchronized void setFlags(Message[] msgs, Flags flag, boolean value)
909             throws MessagingException {
910     checkOpened();
911     checkFlags(flag); // validate flags
912

913     if (msgs.length == 0) // boundary condition
914
return;
915
916     synchronized(messageCacheLock) {
917         try {
918         MessageSet[] ms = Utility.toMessageSet(msgs, null);
919         if (ms == null)
920             throw new MessageRemovedException(
921                     "Messages have been removed");
922         protocol.storeFlags(ms, flag, value);
923         } catch (ConnectionException cex) {
924         throw new FolderClosedException(this, cex.getMessage());
925         } catch (ProtocolException pex) {
926         throw new MessagingException(pex.getMessage(), pex);
927         }
928     }
929     }
930
931     /**
932      * Close this folder.
933      */

934     public synchronized void close(boolean expunge) throws MessagingException {
935     close(expunge, false);
936     }
937
938     /**
939      * Close this folder without waiting for the server.
940      */

941     public synchronized void forceClose() throws MessagingException {
942     close(false, true);
943     }
944
945     private void close(boolean expunge, boolean force)
946                 throws MessagingException {
947     synchronized(messageCacheLock) {
948         /*
949          * If we already know we're closed, this is illegal.
950          * Can't use checkOpened() because if we were forcibly
951          * closed asynchronously we just want to complete the
952          * closing here.
953          */

954         if (!opened && reallyClosed)
955         throw new IllegalStateException JavaDoc(
956             "This operation is not allowed on a closed folder"
957         );
958
959         reallyClosed = true; // Ok, lets reset
960

961         // Maybe this folder is already closed, or maybe another
962
// thread which had the messageCacheLock earlier, found
963
// that our server connection is dead and cleaned up
964
// everything ..
965
if (!opened)
966         return;
967
968         try {
969