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         if (force) {
970                     if (debug)
971                         out.println("DEBUG: forcing folder " + fullName +
972                     " to close");
973             if (protocol != null)
974             protocol.disconnect();
975                 } else if (((IMAPStore)store).isConnectionPoolFull()) {
976             // If the connection pool is full, logout the connection
977
if (debug)
978                         out.println("DEBUG: pool is full, not adding " +
979                             "an Authenticated connection");
980
981             // If the expunge flag is set, close the folder first.
982
if (expunge)
983             protocol.close();
984
985             if (protocol != null)
986             protocol.logout();
987                 } else {
988             // If the expunge flag is set or we're open read-only we
989
// can just close the folder, otherwise open it read-only
990
// before closing.
991
if (!expunge && mode == READ_WRITE) {
992                         try {
993                             MailboxInfo mi = protocol.examine(fullName);
994                         } catch (ProtocolException pex2) {
995                             if (protocol != null)
996                 protocol.disconnect();
997                         }
998                     }
999             if (protocol != null)
1000            protocol.close();
1001                }
1002        } catch (ProtocolException pex) {
1003        throw new MessagingException(pex.getMessage(), pex);
1004        } finally {
1005        // cleanup if we haven't already
1006
if (opened)
1007            cleanup(true);
1008        }
1009    }
1010    }
1011
1012    // NOTE: this method can currently be invoked from close() or
1013
// from handleResponses(). Both invocations are conditional,
1014
// based on the "opened" flag, so we are sure that multiple
1015
// Connection.CLOSED events are not generated. Also both
1016
// invocations are from within messageCacheLock-ed areas.
1017
private void cleanup(boolean returnToPool) {
1018        releaseProtocol(returnToPool);
1019        protocol = null;
1020    messageCache = null;
1021    uidTable = null;
1022    exists = false; // to force a recheck in exists().
1023
opened = false;
1024    notifyConnectionListeners(ConnectionEvent.CLOSED);
1025    }
1026
1027    /**
1028     * Check whether this connection is really open.
1029     */

1030    public synchronized boolean isOpen() {
1031    synchronized(messageCacheLock) {
1032        // Probe the connection to make sure its really open.
1033
if (opened) {
1034        try {
1035            keepConnectionAlive(false);
1036        } catch (ProtocolException pex) { }
1037        }
1038    }
1039
1040    return opened;
1041    }
1042
1043    /**
1044     * Return the permanent flags supported by the server.
1045     */

1046    public Flags getPermanentFlags() {
1047    return permanentFlags;
1048    }
1049
1050    /**
1051     * Get the total message count.
1052     */

1053    public synchronized int getMessageCount() throws MessagingException {
1054    checkExists();
1055    if (!opened) {
1056        // If this folder is not yet open, we use STATUS to
1057
// get the total message count
1058
try {
1059        Status status = getStatus();
1060        return status.total;
1061        } catch (BadCommandException bex) {
1062        // doesn't support STATUS, probably vanilla IMAP4 ..
1063
// lets try EXAMINE
1064
IMAPProtocol p = null;
1065
1066            try {
1067                p = getStoreProtocol(); // XXX
1068
MailboxInfo minfo = p.examine(fullName);
1069            p.close();
1070            return minfo.total;
1071        } catch (ProtocolException pex) {
1072            // Give up.
1073
throw new MessagingException(pex.getMessage(), pex);
1074        } finally {
1075                    releaseStoreProtocol(p);
1076                }
1077        } catch (ConnectionException cex) {
1078                throw new StoreClosedException(store, cex.getMessage());
1079        } catch (ProtocolException pex) {
1080        throw new MessagingException(pex.getMessage(), pex);
1081        }
1082    }
1083
1084    // Folder is open, we know what the total message count is ..
1085
synchronized(messageCacheLock) {
1086        // tickle the folder and store connections.
1087
try {
1088        keepConnectionAlive(true);
1089        return total;
1090        } catch (ConnectionException cex) {
1091        throw new FolderClosedException(this, cex.getMessage());
1092        } catch (ProtocolException pex) {
1093        throw new MessagingException(pex.getMessage(), pex);
1094        }
1095    }
1096    }
1097
1098    /**
1099     * Get the new message count.
1100     */

1101    public synchronized int getNewMessageCount()
1102            throws MessagingException {
1103    checkExists();
1104    if (!opened) {
1105        // If this folder is not yet open, we use STATUS to
1106
// get the new message count
1107
try {
1108        Status status = getStatus();
1109        return status.recent;
1110        } catch (BadCommandException bex) {
1111        // doesn't support STATUS, probably vanilla IMAP4 ..
1112
// lets try EXAMINE
1113
IMAPProtocol p = null;
1114
1115            try {
1116                p = getStoreProtocol(); // XXX
1117
MailboxInfo minfo = p.examine(fullName);
1118            p.close();
1119            return minfo.recent;
1120        } catch (ProtocolException pex) {
1121            // Give up.
1122
throw new MessagingException(pex.getMessage(), pex);
1123        } finally {
1124                    releaseStoreProtocol(p);
1125                }
1126        } catch (ConnectionException cex) {
1127        throw new StoreClosedException(store, cex.getMessage());
1128        } catch (ProtocolException pex) {
1129        throw new MessagingException(pex.getMessage(), pex);
1130        }
1131    }
1132
1133    // Folder is open, we know what the new message count is ..
1134
synchronized(messageCacheLock) {
1135        // tickle the folder and store connections.
1136
try {
1137        keepConnectionAlive(true);
1138        return recent;
1139        } catch (ConnectionException cex) {
1140        throw new FolderClosedException(this, cex.getMessage());
1141        } catch (ProtocolException pex) {
1142        throw new MessagingException(pex.getMessage(), pex);
1143        }
1144    }
1145    }
1146
1147    /**
1148     * Get the unread message count.
1149     */

1150    public synchronized int getUnreadMessageCount()
1151            throws MessagingException {
1152    checkExists();
1153    if (!opened) {
1154        // If this folder is not yet open, we use STATUS to
1155
// get the unseen message count
1156
try {
1157        Status status = getStatus();
1158        return status.unseen;
1159        } catch (BadCommandException bex) {
1160        // doesn't support STATUS, probably vanilla IMAP4 ..
1161
// Could EXAMINE, SEARCH for UNREAD messages and
1162
// return the count .. bah, not worth it.
1163
return -1;
1164        } catch (ConnectionException cex) {
1165        throw new StoreClosedException(store, cex.getMessage());
1166        } catch (ProtocolException pex) {
1167        throw new MessagingException(pex.getMessage(), pex);
1168        }
1169    }
1170
1171    // if opened, issue server-side search for messages that do
1172
// *not* have the SEEN flag.
1173
Flags f = new Flags();
1174    f.add(Flags.Flag.SEEN);
1175    try {
1176        synchronized(messageCacheLock) {
1177        int[] matches = protocol.search(new FlagTerm(f, false));
1178        return matches.length; // NOTE: 'matches' is never null
1179
}
1180    } catch (ConnectionException cex) {
1181        throw new FolderClosedException(this, cex.getMessage());
1182    } catch (ProtocolException pex) {
1183        // Shouldn't happen
1184
throw new MessagingException(pex.getMessage(), pex);
1185    }
1186    }
1187
1188    /**
1189     * Get the deleted message count.
1190     */

1191    public synchronized int getDeletedMessageCount()
1192            throws MessagingException {
1193    checkExists();
1194    if (!opened) {
1195        // no way to do this on closed folders
1196
return -1;
1197    }
1198
1199    // if opened, issue server-side search for messages that do
1200
// have the DELETED flag.
1201
Flags f = new Flags();
1202    f.add(Flags.Flag.DELETED);
1203    try {
1204        synchronized(messageCacheLock) {
1205        int[] matches = protocol.search(new FlagTerm(f, true));
1206        return matches.length; // NOTE: 'matches' is never null
1207
}
1208    } catch (ConnectionException cex) {
1209        throw new FolderClosedException(this, cex.getMessage());
1210    } catch (ProtocolException pex) {
1211        // Shouldn't happen
1212
throw new MessagingException(pex.getMessage(), pex);
1213    }
1214    }
1215
1216    /*
1217     * Get results of STATUS command for this folder, checking cache first.
1218     * ASSERT: Must be called with this folder's synchronization lock held.
1219     * ASSERT: The folder must be closed.
1220     */

1221    private Status getStatus() throws ProtocolException {
1222    int statusCacheTimeout = ((IMAPStore)store).getStatusCacheTimeout();
1223
1224    // if allowed to cache and our cache is still valid, return it
1225
if (statusCacheTimeout > 0 && cachedStatus != null &&
1226        System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout)
1227        return cachedStatus;
1228
1229        IMAPProtocol p = null;
1230
1231    try {
1232        p = getStoreProtocol(); // XXX
1233
Status s = p.status(fullName, null);
1234        // if allowed to cache, do so
1235
if (statusCacheTimeout > 0) {
1236        cachedStatus = s;
1237        cachedStatusTime = System.currentTimeMillis();
1238        }
1239        return s;
1240        } finally {
1241            releaseStoreProtocol(p);
1242        }
1243    }
1244
1245    /**
1246     * Get the specified message.
1247     */

1248    public synchronized Message getMessage(int msgnum)
1249        throws MessagingException {
1250    checkOpened();
1251    checkRange(msgnum);
1252
1253    return (Message)messageCache.elementAt(msgnum-1);
1254    }
1255
1256    /**
1257     * Append the given messages into this folder.
1258     */

1259    public void appendMessages(Message[] msgs) throws MessagingException {
1260    checkExists(); // verify that self exists
1261

1262    // XXX - have to verify that messages are in a different
1263
// store (if any) than target folder, otherwise could
1264
// deadlock trying to fetch messages on the same connection
1265
// we're using for the append.
1266

1267    int maxsize = ((IMAPStore)store).getAppendBufferSize();
1268
1269    for (int i = 0; i < msgs.length; i++) {
1270        final Message m = msgs[i];
1271        final MessageLiteral mos;
1272
1273        try {
1274        // if we know the message is too big, don't buffer any of it
1275
mos = new MessageLiteral(m,
1276                m.getSize() > maxsize ? 0 : maxsize);
1277        } catch (IOException ex) {
1278        throw new MessagingException(
1279                "IOException while appending messages", ex);
1280        } catch (MessageRemovedException mrex) {
1281        continue; // just skip this expunged message
1282
}
1283
1284        Date JavaDoc d = m.getReceivedDate(); // retain dates
1285
if (d == null)
1286        d = m.getSentDate();
1287        final Date JavaDoc dd = d;
1288        final Flags f = m.getFlags();
1289        doCommand(new ProtocolCommand() {
1290        public Object JavaDoc doCommand(IMAPProtocol p)
1291            throws ProtocolException {
1292            p.append(fullName, f, dd, mos);
1293            return null;
1294        }
1295        });
1296    }
1297    }
1298
1299    /**
1300     * Append the given messages into this folder.
1301     * Return array of AppendUID objects containing
1302     * UIDs of these messages in the destination folder.
1303     * Each element of the returned array corresponds to
1304     * an element of the <code>msgs</code> array. A null
1305     * element means the server didn't return UID information
1306     * for the appended message. <p>
1307     *
1308     * Depends on the APPENDUID response code defined by the
1309     * UIDPLUS extension -
1310     * <A HREF="http://www.ietf.org/rfc/rfc2359.txt">RFC 2359</A>.
1311     */

1312    public AppendUID[] appendUIDMessages(Message[] msgs)
1313                throws MessagingException {
1314    checkExists(); // verify that self exists
1315

1316    // XXX - have to verify that messages are in a different
1317
// store (if any) than target folder, otherwise could
1318
// deadlock trying to fetch messages on the same connection
1319
// we're using for the append.
1320

1321    int maxsize = ((IMAPStore)store).getAppendBufferSize();
1322
1323    AppendUID[] uids = new AppendUID[msgs.length];
1324    for (int i = 0; i < msgs.length; i++) {
1325        final Message m = msgs[i];
1326        final MessageLiteral mos;
1327
1328        try {
1329        // if we know the message is too big, don't buffer any of it
1330
mos = new MessageLiteral(m,
1331                m.getSize() > maxsize ? 0 : maxsize);
1332        } catch (IOException ex) {
1333        throw new MessagingException(
1334                "IOException while appending messages", ex);
1335        } catch (MessageRemovedException mrex) {
1336        continue; // just skip this expunged message
1337
}
1338
1339        Date JavaDoc d = m.getReceivedDate(); // retain dates
1340
if (d == null)
1341        d = m.getSentDate();
1342        final Date JavaDoc dd = d;
1343        final Flags f = m.getFlags();
1344        AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() {
1345        public Object JavaDoc doCommand(IMAPProtocol p)
1346            throws ProtocolException {
1347            return p.appenduid(fullName, f, dd, mos);
1348        }
1349        });
1350        uids[i] = auid;
1351    }
1352    return uids;
1353    }
1354
1355    /**
1356     * Append the given messages into this folder.
1357     * Return array of Message objects representing
1358     * the messages in the destination folder. Note
1359     * that the folder must be open.
1360     * Each element of the returned array corresponds to
1361     * an element of the <code>msgs</code> array. A null
1362     * element means the server didn't return UID information
1363     * for the appended message. <p>
1364     *
1365     * Depends on the APPENDUID response code defined by the
1366     * UIDPLUS extension -
1367     * <A HREF="http://www.ietf.org/rfc/rfc2359.txt">RFC 2359</A>.
1368     */

1369    public Message[] addMessages(Message[] msgs) throws MessagingException {
1370    checkOpened();
1371    Message[] rmsgs = new MimeMessage[msgs.length];
1372    AppendUID[] uids = appendUIDMessages(msgs);
1373    for (int i = 0; i < uids.length; i++) {
1374        AppendUID auid = uids[i];
1375        if (auid != null) {
1376        if (auid.uidvalidity == uidvalidity) {
1377            try {
1378            rmsgs[i] = getMessageByUID(auid.uid);
1379            } catch (MessagingException mex) {
1380            // ignore errors at this stage
1381
}
1382        }
1383        }
1384    }
1385    return rmsgs;
1386    }
1387
1388    /**
1389     * Copy the specified messages from this folder, to the
1390     * specified destination.
1391     */

1392    public synchronized void copyMessages(Message[] msgs, Folder folder)
1393            throws MessagingException {
1394    checkOpened();
1395
1396    if (msgs.length == 0) // boundary condition
1397
return;
1398
1399    // If the destination belongs to our same store, optimize
1400
if (folder.getStore() == store) {
1401        synchronized(messageCacheLock) {
1402        try {
1403            MessageSet[] ms = Utility.toMessageSet(msgs, null);
1404            if (ms == null)
1405            throw new MessageRemovedException(
1406                    "Messages have been removed");
1407            protocol.copy(ms, folder.getFullName());
1408        } catch (CommandFailedException cfx) {
1409            if (cfx.getMessage().indexOf("TRYCREATE") != -1)
1410            throw new FolderNotFoundException(
1411                            folder,
1412                folder.getFullName() + " does not exist"
1413               );
1414            else
1415            throw new MessagingException(cfx.getMessage(), cfx);
1416        } catch (ConnectionException cex) {
1417            throw new FolderClosedException(this, cex.getMessage());
1418        } catch (ProtocolException pex) {
1419            throw new MessagingException(pex.getMessage(), pex);
1420            }
1421        }
1422    } else // destination is a different store.
1423
super.copyMessages(msgs, folder);
1424    }
1425
1426    /**
1427     * Expunge all messages marked as DELETED.
1428     */

1429    public synchronized Message[] expunge() throws MessagingException {
1430    return expunge(null);
1431    }
1432
1433    /**
1434     * Expunge the indicated messages, which must have been marked as DELETED.
1435     */

1436    public synchronized Message[] expunge(Message[] msgs)
1437                throws MessagingException {
1438    checkOpened();
1439
1440    Vector JavaDoc v = new Vector JavaDoc(); // to collect expunged messages
1441

1442    if (msgs != null) {
1443        // call fetch to make sure we have all the UIDs
1444
FetchProfile fp = new FetchProfile();
1445        fp.add(UIDFolder.FetchProfileItem.UID);
1446        fetch(msgs, fp);
1447    }
1448
1449    synchronized(messageCacheLock) {
1450        doExpungeNotification = false; // We do this ourselves later
1451
try {
1452        if (msgs != null)
1453            protocol.uidexpunge(Utility.toUIDSet(msgs));
1454        else
1455            protocol.expunge();
1456        } catch (CommandFailedException cfx) {
1457        // expunge not allowed, perhaps due to a permission problem?
1458
if (mode != READ_WRITE)
1459            throw new IllegalStateException JavaDoc(
1460            "Cannot expunge READ_ONLY folder: " + fullName);
1461        else
1462            throw new MessagingException(cfx.getMessage(), cfx);
1463        } catch (ConnectionException cex) {
1464        throw new FolderClosedException(this, cex.getMessage());
1465        } catch (ProtocolException pex) {
1466        // Bad bad server ..
1467
throw new MessagingException(pex.getMessage(), pex);
1468        } finally {
1469        doExpungeNotification = true;
1470        }
1471
1472        // Cleanup expunged messages and sync messageCache with
1473
// reality.
1474
for (int i = 0; i < messageCache.size(); ) {
1475        IMAPMessage m = (IMAPMessage)messageCache.elementAt(i);
1476        if (m.isExpunged()) {
1477            v.addElement(m); // add into vector of expunged messages
1478

1479            /* remove this message from the messageCache.
1480             *
1481             * Note that this also causes all succeeding messages
1482             * in the cache to shifted downward in the vector,
1483             * therby decrementing the vector's size. (and hence
1484             * we need to do messageCache.size() at the top of
1485             * this loop.
1486             */

1487            messageCache.removeElementAt(i);
1488
1489            /* remove this message from the UIDTable */
1490            if (uidTable != null) {
1491            long uid = m.getUID();
1492            if (uid != -1)
1493                uidTable.remove(new Long JavaDoc(uid));
1494            }
1495        } else {
1496            /* Valid message, sync its message number with
1497             * its sequence number.
1498             */

1499            m.setMessageNumber(m.getSequenceNumber());
1500            i++; // done; increment index, go check next message
1501
}
1502        }
1503    }
1504
1505    // Update 'total'
1506
total = messageCache.size();
1507
1508    // Notify listeners. This time its for real, guys.
1509
Message[] rmsgs = new Message[v.size()];
1510    v.copyInto(rmsgs);
1511    if (rmsgs.length > 0)
1512        notifyMessageRemovedListeners(true, rmsgs);
1513    return rmsgs;
1514    }
1515
1516    /**
1517     * Search whole folder for messages matching the given term.
1518     */

1519    public synchronized Message[] search(SearchTerm term)
1520                throws MessagingException {
1521    checkOpened();
1522
1523    try {
1524        Message[] matchMsgs = null;
1525
1526        synchronized(messageCacheLock) {
1527        int[] matches = protocol.search(term);
1528        if (matches != null) {
1529            matchMsgs = new IMAPMessage[matches.length];
1530            // Map seq-numbers into actual Messages.
1531
for (int i = 0; i < matches.length; i++)
1532            matchMsgs[i] = getMessageBySeqNumber(matches[i]);
1533        }
1534        }
1535        return matchMsgs;
1536
1537    } catch (CommandFailedException cfx) {
1538        // unsupported charset or search criterion
1539
return super.search(term);
1540    } catch (SearchException sex) {
1541        // too complex for IMAP
1542
return super.search(term);
1543    } catch (ConnectionException cex) {
1544        throw new FolderClosedException(this, cex.getMessage());
1545    } catch (ProtocolException pex) {
1546        // bug in our IMAP layer ?
1547
throw new MessagingException(pex.getMessage(), pex);
1548    }
1549    }
1550
1551    /**
1552     * Search the folder for messages matching the given term. Returns
1553     * array of matching messages. Returns an empty array if no matching
1554     * messages are found.
1555     */

1556    public synchronized Message[] search(SearchTerm term, Message[] msgs)
1557            throws MessagingException {
1558    checkOpened();
1559
1560    if (msgs.length == 0)
1561        // need to return an empty array (not null!)
1562
return msgs;
1563
1564    try {
1565        Message[] matchMsgs = null;
1566
1567        synchronized(messageCacheLock) {
1568        MessageSet[] ms = Utility.toMessageSet(msgs, null);
1569        if (ms == null)
1570            throw new MessageRemovedException(
1571                    "Messages have been removed");
1572        int[] matches = protocol.search(ms, term);
1573        if (matches != null) {
1574            matchMsgs = new IMAPMessage[matches.length];
1575            for (int i = 0; i < matches.length; i++)
1576            matchMsgs[i] = getMessageBySeqNumber(matches[i]);
1577        }
1578        }
1579        return matchMsgs;
1580
1581    } catch (CommandFailedException cfx) {
1582        // unsupported charset or search criterion
1583
return super.search(term, msgs);
1584    } catch (SearchException sex) {
1585        // too complex for IMAP
1586
return super.search(term, msgs);
1587    } catch (ConnectionException cex) {
1588        throw new FolderClosedException(this, cex.getMessage());
1589    } catch (ProtocolException pex) {
1590        // bug in our IMAP layer ?
1591
throw new MessagingException(pex.getMessage(), pex);
1592    }
1593    }
1594
1595    /***********************************************************
1596     * UIDFolder interface methods
1597     **********************************************************/

1598
1599    /**
1600     * Returns the UIDValidity for this folder.
1601     */

1602    public synchronized long getUIDValidity() throws MessagingException {
1603    if (opened) // we already have this information
1604
return uidvalidity;
1605
1606        IMAPProtocol p = null;
1607        Status status = null;
1608
1609    try {
1610        p = getStoreProtocol(); // XXX
1611
String JavaDoc[] item = { "UIDVALIDITY" };
1612        status = p.status(fullName, item);
1613    } catch (BadCommandException bex) {
1614        // Probably a RFC1730 server
1615
throw new MessagingException("Cannot obtain UIDValidity", bex);
1616    } catch (ConnectionException cex) {
1617            // Oops, the store or folder died on us.
1618
throwClosedException(cex);
1619    } catch (ProtocolException pex) {
1620        throw new MessagingException(pex.getMessage(), pex);
1621    } finally {
1622            releaseStoreProtocol(p);
1623        }
1624
1625    return status.uidvalidity;
1626    }
1627
1628    /**
1629     * Returns the predicted UID that will be assigned to the
1630     * next message that is appended to this folder.
1631     * If the folder is closed, the STATUS command is used to
1632     * retrieve this value. If the folder is open, the value
1633     * returned from the SELECT or EXAMINE command is returned.
1634     * Note that messages may have been appended to the folder
1635     * while it was open and thus this value may be out of
1636     * date. <p>
1637     *
1638     * Servers implementing RFC2060 likely won't return this value
1639     * when a folder is opened. Servers implementing RFC3501
1640     * should return this value when a folder is opened. <p>
1641     *
1642     * @return the UIDNEXT value, or -1 if unknown
1643     * @since JavaMail 1.3.3
1644     */

1645    // Not a UIDFolder method, but still useful
1646
public synchronized long getUIDNext() throws MessagingException {
1647    if (opened) // we already have this information
1648
return uidnext;
1649
1650        IMAPProtocol p = null;
1651        Status status = null;
1652
1653    try {
1654        p = getStoreProtocol(); // XXX
1655
String JavaDoc[] item = { "UIDNEXT" };
1656        status = p.status(fullName, item);
1657    } catch (BadCommandException bex) {
1658        // Probably a RFC1730 server
1659
throw new MessagingException("Cannot obtain UIDNext", bex);
1660    } catch (ConnectionException cex) {
1661            // Oops, the store or folder died on us.
1662
throwClosedException(cex);
1663    } catch (ProtocolException pex) {
1664        throw new MessagingException(pex.getMessage(), pex);
1665    } finally {
1666            releaseStoreProtocol(p);
1667        }
1668
1669    return status.uidnext;
1670    }
1671
1672    /**
1673     * Get the Message corresponding to the given UID.
1674     * If no such message exists, <code> null </code> is returned.
1675     */

1676    public synchronized Message getMessageByUID(long uid)
1677            throws MessagingException {
1678    checkOpened(); // insure folder is open
1679

1680    Long JavaDoc l = new Long JavaDoc(uid);
1681    IMAPMessage m = null;
1682
1683    if (uidTable != null) {
1684        // Check in uidTable
1685
m = (IMAPMessage)uidTable.get(l);
1686        if (m != null) // found it
1687
return m;
1688    } else
1689        uidTable = new Hashtable JavaDoc();
1690    
1691
1692    // Check with the server
1693
try {
1694        synchronized(messageCacheLock) {
1695        // Issue UID FETCH command
1696
UID u = protocol.fetchSequenceNumber(uid);
1697
1698        if (u != null && u.msgno <= total) { // Valid UID
1699
m = (IMAPMessage)messageCache.elementAt(u.msgno-1);
1700            m.setUID(u.uid); // set this message's UID ..
1701
// .. and put this into the hashtable
1702
uidTable.put(l, m);
1703        }
1704        }
1705    } catch(ConnectionException cex) {
1706        throw new FolderClosedException(this, cex.getMessage());
1707    } catch (ProtocolException pex) {
1708        throw new MessagingException(pex.getMessage(), pex);
1709    }
1710
1711    return m;
1712    }
1713
1714    /**
1715     * Get the Messages specified by the given range. <p>
1716     * Returns Message objects for all valid messages in this range.
1717     * Returns an empty array if no messages are found.
1718     */

1719    public synchronized Message[] getMessagesByUID(long start, long end)
1720            throws MessagingException {
1721    checkOpened(); // insure that folder is open
1722

1723    if (uidTable == null)
1724        uidTable = new Hashtable JavaDoc();
1725
1726    Message[] msgs; // array of messages to be returned
1727
try {
1728        synchronized(messageCacheLock) {
1729        // Issue UID FETCH for given range
1730
UID[] ua = protocol.fetchSequenceNumbers(start, end);
1731
1732        msgs = new Message[ua.length];
1733        IMAPMessage m;
1734        // NOTE: Below must be within messageCacheLock region
1735
for (int i = 0; i < ua.length; i++) {
1736            m = (IMAPMessage)messageCache.elementAt(ua[i].msgno-1);
1737            m.setUID(ua[i].uid);
1738            msgs[i] = m;
1739            uidTable.put(new Long JavaDoc(ua[i].uid), m);
1740        }
1741        }
1742    } catch(ConnectionException cex) {
1743        throw new FolderClosedException(this, cex.getMessage());
1744    } catch (ProtocolException pex) {
1745        throw new MessagingException(pex.getMessage(), pex);
1746    }
1747
1748    return msgs;
1749    }
1750
1751    /**
1752     * Get the Messages specified by the given array. <p>
1753     *
1754     * <code>uids.length()</code> elements are returned.
1755     * If any UID in the array is invalid, a <code>null</code> entry
1756     * is returned for that element.
1757     */

1758    public synchronized Message[] getMessagesByUID(long[] uids)
1759            throws MessagingException {
1760    checkOpened(); // insure that folder is open
1761
long[] unavailUids = uids;
1762
1763    if (uidTable != null) {
1764        Vector JavaDoc v = new Vector JavaDoc(); // to collect unavailable UIDs
1765
Long JavaDoc l;
1766        for (int i = 0; i < uids.length; i++) {
1767        if (!uidTable.containsKey(l = new Long JavaDoc(uids[i])))
1768            // This UID has not been loaded yet.
1769
v.addElement(l);
1770        }
1771
1772        int vsize = v.size();
1773        unavailUids = new long[vsize];
1774        for (int i = 0; i < vsize; i++)
1775        unavailUids[i] = ((Long JavaDoc)v.elementAt(i)).longValue();
1776    } else
1777        uidTable = new Hashtable JavaDoc();
1778
1779    if (unavailUids.length > 0) {
1780        try {
1781        synchronized(messageCacheLock) {
1782            // Issue UID FETCH request for given uids
1783
UID[] ua = protocol.fetchSequenceNumbers(unavailUids);
1784            IMAPMessage m;
1785            for (int i = 0; i < ua.length; i++) {
1786            m = (IMAPMessage)messageCache.elementAt(ua[i].msgno-1);
1787            m.setUID(ua[i].uid);
1788            uidTable.put(new Long JavaDoc(ua[i].uid), m);
1789            }
1790        }
1791        } catch(ConnectionException cex) {
1792        throw new FolderClosedException(this, cex.getMessage());
1793        } catch (ProtocolException pex) {
1794        throw new MessagingException(pex.getMessage(), pex);
1795        }
1796    }
1797
1798    // Return array of size = uids.length
1799
Message[] msgs = new Message[uids.length];
1800    for (int i = 0; i < uids.length; i++)
1801        msgs[i] = (Message)uidTable.get(new Long JavaDoc(uids[i]));
1802    return msgs;
1803    }
1804
1805    /**
1806     * Get the UID for the specified message.
1807     */

1808    public synchronized long getUID(Message message)
1809            throws MessagingException {
1810    if (message.getFolder() != this)
1811        throw new NoSuchElementException JavaDoc(
1812        "Message does not belong to this folder");
1813
1814    checkOpened(); // insure that folder is open
1815

1816    IMAPMessage m = (IMAPMessage)message;
1817    // If the message already knows its UID, great ..
1818
long uid;
1819    if ((uid = m.getUID()) != -1)
1820        return uid;
1821
1822    UID u = null;
1823    synchronized(messageCacheLock) { // Acquire Lock
1824
m.checkExpunged(); // insure that message is not expunged
1825
try {
1826        u = protocol.fetchUID(m.getSequenceNumber());
1827        } catch (ConnectionException cex) {
1828        throw new FolderClosedException(this, cex.getMessage());
1829        } catch (ProtocolException pex) {
1830        throw new MessagingException(pex.getMessage(), pex);
1831        }
1832    }
1833
1834    if (u != null) {
1835        uid = u.uid;
1836        m.setUID(uid); // set message's UID
1837

1838        // insert this message into uidTable
1839
if (uidTable == null)
1840        uidTable = new Hashtable JavaDoc();
1841        uidTable.put(new Long JavaDoc(uid), m);
1842    }
1843
1844    return uid;
1845    }
1846
1847    /**
1848     * Get the quotas for the quotaroot associated with this
1849     * folder. Note that many folders may have the same quotaroot.
1850     * Quotas are controlled on the basis of a quotaroot, not
1851     * (necessarily) a folder. The relationship between folders
1852     * and quotaroots depends on the IMAP server. Some servers
1853     * might implement a single quotaroot for all folders owned by
1854     * a user. Other servers might implement a separate quotaroot
1855     * for each folder. A single folder can even have multiple
1856     * quotaroots, perhaps controlling quotas for different
1857     * resources.
1858     *
1859     * @return array of Quota objects for the quotaroots associated with
1860     * this folder
1861     * @exception MessagingException if the server doesn't support the
1862     * QUOTA extension
1863     */

1864    public Quota[] getQuota() throws MessagingException {
1865    return (Quota[])doOptionalCommand("QUOTA not supported",
1866        new ProtocolCommand() {
1867        public Object JavaDoc doCommand(IMAPProtocol p)
1868            throws ProtocolException {
1869            return p.getQuotaRoot(fullName);
1870        }
1871        });
1872    }
1873
1874    /**
1875     * Set the quotas for the quotaroot specified in the quota argument.
1876     * Typically this will be one of the quotaroots associated with this
1877     * folder, as obtained from the <code>getQuota</code> method, but it
1878     * need not be.
1879     *
1880     * @param quota the quota to set
1881     * @exception MessagingException if the server doesn't support the
1882     * QUOTA extension
1883     */

1884    public void setQuota(final Quota quota) throws MessagingException {
1885    doOptionalCommand("QUOTA not supported",
1886        new ProtocolCommand() {
1887        public Object JavaDoc doCommand(IMAPProtocol p)
1888            throws ProtocolException {
1889            p.setQuota(quota);
1890            return null;
1891        }
1892        });
1893    }
1894
1895    /**
1896     * Get the access control list entries for this folder.
1897     *
1898     * @return array of access control list entries
1899     * @exception MessagingException if the server doesn't support the
1900     * ACL extension
1901     */

1902    public ACL[] getACL() throws MessagingException {
1903    return (ACL[])doOptionalCommand("ACL not supported",
1904        new ProtocolCommand() {
1905        public Object JavaDoc doCommand(IMAPProtocol p)
1906            throws ProtocolException {
1907            return p.getACL(fullName);
1908        }
1909        });
1910    }
1911
1912    /**
1913     * Add an access control list entry to the access control list
1914     * for this folder.
1915     *
1916     * @param acl the access control list entry to add
1917     * @exception MessagingException if the server doesn't support the
1918     * ACL extension
1919     */

1920    public void addACL(ACL acl) throws MessagingException {
1921    setACL(acl, '\0');
1922    }
1923
1924    /**
1925     * Remove any access control list entry for the given identifier
1926     * from the access control list for this folder.
1927     *
1928     * @param name the identifier for which to remove all ACL entries
1929     * @exception MessagingException if the server doesn't support the
1930     * ACL extension
1931     */

1932    public void removeACL(final String JavaDoc name) throws MessagingException {
1933    doOptionalCommand("ACL not supported",
1934        new ProtocolCommand() {
1935        public Object JavaDoc doCommand(IMAPProtocol p)
1936            throws ProtocolException {
1937            p.deleteACL(fullName, name);
1938            return null;
1939        }
1940        });
1941    }
1942
1943    /**
1944     * Add the rights specified in the ACL to the entry for the
1945     * identifier specified in the ACL. If an entry for the identifier
1946     * doesn't already exist, add one.
1947     *
1948     * @param acl the identifer and rights to add
1949     * @exception MessagingException if the server doesn't support the
1950     * ACL extension
1951     */

1952    public void addRights(ACL acl) throws MessagingException {
1953    setACL(acl, '+');
1954    }
1955
1956    /**
1957     * Remove the rights specified in the ACL from the entry for the
1958     * identifier specified in the ACL.
1959     *
1960     * @param acl the identifer and rights to remove
1961     * @exception MessagingException if the server doesn't support the
1962     * ACL extension
1963     */

1964    public void removeRights(ACL acl) throws MessagingException {
1965    setACL(acl, '-');
1966    }
1967
1968    /**
1969     * Get all the rights that may be allowed to the given identifier.
1970     * Rights are grouped per RFC 2086 and each group is returned as an
1971     * element of the array. The first element of the array is the set
1972     * of rights that are always granted to the identifier. Later
1973     * elements are rights that may be optionally granted to the
1974     * identifier. <p>
1975     *
1976     * Note that this method lists the rights that it is possible to
1977     * assign to the given identifier, <em>not</em> the rights that are
1978     * actually granted to the given identifier. For the latter, see
1979     * the <code>getACL</code> method.
1980     *
1981     * @param name the identifier to list rights for
1982     * @return array of Rights objects representing possible
1983     * rights for the identifier
1984     * @exception MessagingException if the server doesn't support the
1985     * ACL extension
1986     */

1987    public Rights[] listRights(final String JavaDoc name) throws MessagingException {
1988    return (Rights[])doOptionalCommand("ACL not supported",
1989        new ProtocolCommand() {
1990        public Object JavaDoc doCommand(IMAPProtocol p)
1991            throws ProtocolException {
1992            return p.listRights(fullName, name);
1993        }
1994        });
1995    }
1996
1997    /**
1998     * Get the rights allowed to the currently authenticated user.
1999     *
2000     * @return the rights granted to the current user
2001     * @exception MessagingException if the server doesn't support the
2002     * ACL extension
2003     */

2004    public Rights myRights() throws MessagingException {
2005    return (Rights)doOptionalCommand("ACL not supported",
2006        new ProtocolCommand() {
2007        public Object JavaDoc doCommand(IMAPProtocol p)
2008            throws ProtocolException {
2009            return p.myRights(fullName);
2010        }
2011        });
2012    }
2013
2014    private void setACL(final ACL acl, final char mod)
2015                throws MessagingException {
2016    doOptionalCommand("ACL not supported",
2017        new ProtocolCommand() {
2018        public Object JavaDoc doCommand(IMAPProtocol p)
2019            throws ProtocolException {
2020            p.setACL(fullName, mod, acl);
2021            return null;
2022        }
2023        });
2024    }
2025
2026    /**
2027     * Get the attributes that the IMAP server returns with the
2028     * LIST response.
2029     *
2030     * @since JavaMail 1.3.3
2031     */

2032    public String JavaDoc[] getAttributes() throws MessagingException {
2033    if (attributes == null)
2034        exists(); // do a LIST to set the attributes
2035
return (String JavaDoc[])(attributes.clone());
2036    }
2037
2038    /**
2039     * The response handler. This is the callback routine that is
2040     * invoked by the protocol layer.
2041     */

2042    /*
2043     * ASSERT: This method must be called only when holding the
2044     * messageCacheLock.
2045     * ASSERT: This method must *not* invoke any other method that
2046     * might grab the 'folder' lock or 'message' lock (i.e., any
2047     * synchronized methods on IMAPFolder or IMAPMessage)
2048     * since that will result in violating the locking hierarchy.
2049     */

2050    public void handleResponse(Response r) {
2051
2052    /*
2053     * First, delegate possible ALERT or notification to the Store.
2054     */

2055    if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
2056        ((IMAPStore)store).handleResponseCode(r);
2057
2058    /*
2059     * Now check whether this is a BYE or OK response and
2060     * handle appropriately.
2061     */

2062    if (r.isBYE()) {
2063        if (opened)
2064        cleanup(false);
2065        return;
2066    } else if (r.isOK()) {
2067        return;
2068    } else if (!r.isUnTagged()) {
2069        return; // XXX - should never happen
2070
}
2071
2072    /* Now check whether this is an IMAP specific response */
2073    if (!(r instanceof IMAPResponse)) {
2074        // Probably a bug in our code !
2075
// XXX - should be an assert
2076
out.println("UNEXPECTED RESPONSE : " + r.toString());
2077        out.println("CONTACT javamail@sun.com");
2078        return;
2079    }
2080
2081    IMAPResponse ir = (IMAPResponse)r;
2082
2083    if (ir.keyEquals("EXISTS")) { // EXISTS
2084
int exists = ir.getNumber();
2085        if (exists <= realTotal)
2086        // Could be the EXISTS following EXPUNGE, ignore 'em
2087
return;
2088    
2089        int count = exists - realTotal; // number of new messages
2090
Message[] msgs = new Message[count];
2091
2092        // Add 'count' new IMAPMessage objects into the messageCache
2093
for (int i = 0; i < count; i++) {
2094        // Note that as a side-effect, we also increment
2095
// total & realTotal
2096
IMAPMessage msg = new IMAPMessage(this, ++total, ++realTotal);
2097        msgs[i] = msg;
2098        messageCache.addElement(msg);
2099        }
2100
2101        // Notify listeners.
2102
notifyMessageAddedListeners(msgs);
2103
2104    } else if (ir.keyEquals("EXPUNGE")) {
2105        // EXPUNGE response.
2106

2107        IMAPMessage msg = getMessageBySeqNumber(ir.getNumber());
2108        msg.setExpunged(true); // mark this message expunged.
2109

2110        // Renumber the cache, starting from just beyond
2111
// the expunged message.
2112
for (int i = msg.getMessageNumber(); i < total; i++) {
2113        // Note that 'i' actually indexes the message
2114
// beyond the expunged message.
2115
IMAPMessage m = (IMAPMessage)messageCache.elementAt(i);
2116        if (m.isExpunged()) // an expunged message, skip
2117
continue;
2118
2119        // Decrement this message's seqnum
2120
m.setSequenceNumber(m.getSequenceNumber() - 1);
2121        } // Whew, done.
2122

2123        // decrement 'realTotal'; but leave 'total' unchanged
2124
realTotal--;
2125
2126        if (doExpungeNotification) {
2127        // Do the notification here.
2128
Message[] msgs = {msg};
2129        notifyMessageRemovedListeners(false, msgs);
2130        }
2131
2132    } else if (ir.keyEquals("FETCH")) {
2133        // The only unsolicited FETCH response that makes sense
2134
// to me (for now) is FLAGS updates. Ignore any other junk.
2135
FetchResponse f = (FetchResponse)ir;
2136        // Get FLAGS response, if present
2137
Flags flags = (Flags)f.getItem(Flags.class);
2138
2139        if (flags != null) {
2140        IMAPMessage msg = getMessageBySeqNumber(f.getNumber());
2141        if (msg != null) { // should always be true
2142
msg._setFlags(flags);
2143            notifyMessageChangedListeners(
2144                MessageChangedEvent.FLAGS_CHANGED, msg);
2145        }
2146        }
2147
2148    } else if (ir.keyEquals("RECENT")) {
2149        // update 'recent'
2150
recent = ir.getNumber();
2151    }
2152    }
2153
2154    /**
2155     * Handle the given array of Responses.
2156     *
2157     * ASSERT: This method must be called only when holding the
2158     * messageCacheLock
2159     */

2160    void handleResponses(Response[] r) {
2161    for (int i = 0; i < r.length; i++) {
2162        if (r[i] != null)
2163        handleResponse(r[i]);
2164    }
2165    }
2166
2167    /**
2168     * Get this folder's Store's protocol connection.
2169     *
2170     * When acquiring a store protocol object, it is important to
2171     * use the following steps:
2172     *
2173     * IMAPProtocol p = null;
2174     * try {
2175     * p = getStoreProtocol();
2176     * // perform the command
2177     * } catch (WhateverException ex) {
2178     * // handle it
2179     * } finally {
2180     * releaseStoreProtocol(p);
2181     * }
2182     */

2183    protected synchronized IMAPProtocol getStoreProtocol()
2184            throws ProtocolException {
2185    if (connectionPoolDebug) {
2186        out.println("DEBUG: getStoreProtocol() - " +
2187        "borrowing a connection");
2188    }
2189    return ((IMAPStore)store).getStoreProtocol();
2190    }
2191
2192    /**
2193     * Throw the appropriate 'closed' exception.
2194     */

2195    private synchronized void throwClosedException(ConnectionException cex)
2196            throws FolderClosedException, StoreClosedException {
2197    // If it's the folder's protocol object, throw a FolderClosedException;
2198
// otherwise, throw a StoreClosedException.
2199
// If a command has failed because the connection is closed,
2200
// the folder will have already been forced closed by the
2201
// time we get here and our protocol object will have been
2202
// released, so if we no longer have a protocol object we base
2203
// this decision on whether we *think* the folder is open.
2204
if ((protocol != null && cex.getProtocol() == protocol) ||
2205        (protocol == null && !reallyClosed))
2206            throw new FolderClosedException(this, cex.getMessage());
2207        else
2208            throw new StoreClosedException(store, cex.getMessage());
2209    }
2210
2211    /**
2212     * Return the IMAPProtocol object for this folder. <p>
2213     *
2214     * <strong>NOTE:</strong>
2215     * This method was intended to allow experimentation with
2216     * simple extension commands that can use the low level
2217     * Protocol object APIs to send commands and process
2218     * responses. <p>
2219     *
2220     * <strong>NOTE:</strong> Using this protocol object is completely
2221     * <strong>UNSAFE</strong> because there's no way to aquire
2222     * the required locks. See the <code>doCommand</code> method
2223     * for a safe alternative. <p>
2224     *
2225     * @return the IMAPProtocol object used when the folder is open
2226     * @see #doCommand
2227     */

2228    public IMAPProtocol getProtocol() {
2229        return protocol;
2230    }
2231
2232    /**
2233     * A simple interface for user-defined IMAP protocol commands.
2234     */

2235    public static interface ProtocolCommand {
2236    /**
2237     * Execute the user-defined command using the supplied IMAPProtocol
2238     * object.
2239     */

2240    public Object JavaDoc doCommand(IMAPProtocol protocol) throws ProtocolException;
2241    }
2242
2243    /**
2244     * Execute a user-supplied IMAP command. The command is executed
2245     * in the appropriate context with the necessary locks held and
2246     * using the appropriate <code>IMAPProtocol</code> object. <p>
2247     *
2248     * This method returns whatever the <code>ProtocolCommand</code>
2249     * object's <code>doCommand</code> method returns. If the
2250     * <code>doCommand</code> method throws a <code>ConnectionException</code>
2251     * it is translated into a <code>StoreClosedException</code> or
2252     * <code>FolderClosedException</code> as appropriate. If the
2253     * <code>doCommand</code> method throws a <code>ProtocolException</code>
2254     * it is translated into a <code>MessagingException</code>. <p>
2255     *
2256     * The following example shows how to execute the IMAP NOOP command.
2257     * Executing more complex IMAP commands requires intimate knowledge
2258     * of the <code>com.sun.mail.iap</code> and
2259     * <code>com.sun.mail.imap.protocol</code> packages, best acquired by
2260     * reading the source code. <p>
2261     *
2262     * <blockquote><pre>
2263     * import com.sun.mail.iap.*;
2264     * import com.sun.mail.imap.*;
2265     * import com.sun.mail.imap.protocol.*;
2266     *
2267     * ...
2268     *
2269     * IMAPFolder f = (IMAPFolder)folder;
2270     * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
2271     * public Object doCommand(IMAPProtocol p)
2272     * throws ProtocolException {
2273     * p.simpleCommand("NOOP", null);
2274     * return null;
2275     * }
2276     * });
2277     * </pre></blockquote>
2278     * <p>
2279     *
2280     * Here's a more complex example showing how to use the proposed
2281     * IMAP SORT extension: <p>
2282     *
2283     * <pre><blockquote>
2284     * import com.sun.mail.iap.*;
2285     * import com.sun.mail.imap.*;
2286     * import com.sun.mail.imap.protocol.*;
2287     *
2288     * ...
2289     *
2290     * IMAPFolder f = (IMAPFolder)folder;
2291     * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
2292     * public Object doCommand(IMAPProtocol p)
2293     * throws ProtocolException {
2294     * // Issue command
2295     * Argument args = new Argument();
2296     * Argument list = new Argument();
2297     * list.writeString("SUBJECT");
2298     * args.writeArgument(list);
2299     * args.writeString("UTF-8");
2300     * args.writeString("ALL");
2301     * Response[] r = p.command("SORT", args);
2302     * Response response = r[r.length-1];
2303     *
2304     * // Grab response
2305     * Vector v = new Vector();
2306     * if (response.isOK()) { // command succesful
2307     * for (int i = 0, len = r.length; i < len; i++) {
2308     * if (!(r[i] instanceof IMAPResponse))
2309     * continue;
2310     *
2311     * IMAPResponse ir = (IMAPResponse)r[i];
2312     * if (ir.keyEquals("SORT")) {
2313     * String num;
2314     * while ((num = ir.readAtomString()) != null)
2315     * System.out.println(num);
2316     * r[i] = null;
2317     * }
2318     * }
2319     * }
2320     *
2321     * // dispatch remaining untagged responses
2322     * p.notifyResponseHandlers(r);
2323     * p.handleResult(response);
2324     *
2325     * return null;
2326     * }
2327     * });
2328     * </pre></blockquote>
2329     */

2330    public Object JavaDoc doCommand(ProtocolCommand cmd) throws MessagingException {
2331    try {
2332        return doProtocolCommand(cmd);
2333    } catch (ConnectionException cex) {
2334            // Oops, the store or folder died on us.
2335
throwClosedException(cex);
2336    } catch (ProtocolException pex) {
2337        throw new MessagingException(pex.getMessage(), pex);
2338    }
2339    return null;
2340    }
2341
2342    public Object JavaDoc doOptionalCommand(String JavaDoc err, ProtocolCommand cmd)
2343                throws MessagingException {
2344    try {
2345        return doProtocolCommand(cmd);
2346    } catch (BadCommandException bex) {
2347        throw new MessagingException(err, bex);
2348    } catch (ConnectionException cex) {
2349            // Oops, the store or folder died on us.
2350
throwClosedException(cex);
2351    } catch (ProtocolException pex) {
2352        throw new MessagingException(pex.getMessage(), pex);
2353    }
2354    return null;
2355    }
2356
2357    public Object JavaDoc doCommandIgnoreFailure(ProtocolCommand cmd)
2358                throws MessagingException {
2359    try {
2360        return doProtocolCommand(cmd);
2361    } catch (CommandFailedException cfx) {
2362        return null;
2363    } catch (ConnectionException cex) {
2364            // Oops, the store or folder died on us.
2365
throwClosedException(cex);
2366    } catch (ProtocolException pex) {
2367        throw new MessagingException(pex.getMessage(), pex);
2368    }
2369    return null;
2370    }
2371
2372    protected Object JavaDoc doProtocolCommand(ProtocolCommand cmd)
2373                throws ProtocolException {
2374    synchronized (this) {
2375        if (opened && !((IMAPStore)store).hasSeparateStoreConnection()) {
2376        synchronized (messageCacheLock) {
2377            return cmd.doCommand(getProtocol());
2378        }
2379        }
2380    }
2381
2382    // only get here if using store's connection
2383
IMAPProtocol p = null;
2384
2385    try {
2386            p = getStoreProtocol();
2387        return cmd.doCommand(p);
2388    } finally {
2389        releaseStoreProtocol(p);
2390    }
2391    }
2392
2393    /**
2394     * Release the store protocol object. If we borrowed a protocol
2395     * object from the connection pool, give it back. If we used our
2396     * own protocol object, nothing to do.
2397     */

2398    protected synchronized void releaseStoreProtocol(IMAPProtocol p) {
2399        if (p != protocol)
2400            ((IMAPStore)store).releaseStoreProtocol(p);
2401    }
2402
2403    /**
2404     * Release the protocol object.
2405     *
2406     * ASSERT: This method must be called only when holding the
2407     * messageCacheLock
2408     */

2409    private void releaseProtocol(boolean returnToPool) {
2410        if (protocol != null) {
2411            protocol.removeResponseHandler(this);
2412
2413            if (returnToPool)
2414                ((IMAPStore)store).releaseProtocol(this, protocol);
2415            else
2416                ((IMAPStore)store).releaseProtocol(this, null);
2417        }
2418    }
2419    
2420    /**
2421     * Issue a noop command for the connection if the connection has not been
2422     * used in more than a second. If <code>keepStoreAlive</code> is true,
2423     * also issue a noop over the store's connection.
2424     */

2425    private void keepConnectionAlive(boolean keepStoreAlive)
2426                    throws ProtocolException {
2427
2428        if (System.currentTimeMillis() - protocol.getTimestamp() > 1000)
2429            protocol.noop();
2430
2431        if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection()) {
2432            IMAPProtocol p = null;
2433        try {
2434        p = ((IMAPStore)store).getStoreProtocol();
2435        if (System.currentTimeMillis() - p.getTimestamp() > 1000)
2436            p.noop();
2437        } finally {
2438        ((IMAPStore)store).releaseStoreProtocol(p);
2439        }
2440        }
2441    }
2442
2443    /**
2444     * Get the message object for the given sequence number. If
2445     * none found, null is returned.
2446     *
2447     * ASSERT: This method must be called only when holding the
2448     * messageCacheLock
2449     */

2450    IMAPMessage getMessageBySeqNumber(int seqnum) {
2451    /* Check messageCache for message matching the given
2452     * sequence number. We start the search from position (seqnum-1)
2453     * and continue down the vector till we get a match.
2454     */

2455    for (int i = seqnum-1; i < total; i++) {
2456        IMAPMessage msg = (IMAPMessage)messageCache.elementAt(i);
2457        if (msg.getSequenceNumber() == seqnum)
2458        return msg;
2459    }
2460    return null;
2461    }
2462
2463    private boolean isDirectory() {
2464    return ((type & HOLDS_FOLDERS) != 0);
2465    }
2466}
2467
2468/**
2469 * An object that holds a Message object
2470 * and reports its size and writes it to another OutputStream
2471 * on demand. Used by appendMessages to avoid the need to
2472 * buffer the entire message in memory in a single byte array
2473 * before sending it to the server.
2474 */

2475class MessageLiteral implements Literal {
2476    private Message msg;
2477    private int msgSize = -1;
2478    private byte[] buf; // the buffered message, if not null
2479

2480    public MessageLiteral(Message msg, int maxsize)
2481                throws MessagingException, IOException {
2482    this.msg = msg;
2483    // compute the size here so exceptions can be returned immediately
2484
LengthCounter lc = new LengthCounter(maxsize);
2485    OutputStream os = new CRLFOutputStream(lc);
2486    msg.writeTo(os);
2487    os.flush();
2488    msgSize = lc.getSize();
2489    buf = lc.getBytes();
2490    }
2491
2492    public int size() {
2493    return msgSize;
2494    }
2495
2496    public void writeTo(OutputStream os) throws IOException {
2497    // the message should not change between the constructor and this call
2498
try {
2499        if (buf != null)
2500        os.write(buf, 0, msgSize);
2501        else {
2502        os = new CRLFOutputStream(os);
2503        msg.writeTo(os);
2504        }
2505    } catch (MessagingException mex) {
2506        // exceptions here are bad, "should" never happen
2507
throw new IOException("MessagingException while appending message: "
2508                    + mex);
2509    }
2510    }
2511}
2512
2513/**
2514 * Count the number of bytes written to the stream.
2515 * Also, save a copy of small messages to avoid having to process
2516 * the data again.
2517 */

2518class LengthCounter extends OutputStream {
2519    private int size = 0;
2520    private byte[] buf;
2521    private int maxsize;
2522
2523    public LengthCounter(int maxsize) {
2524    buf = new byte[8192];
2525    this.maxsize = maxsize;
2526    }
2527
2528    public void write(int b) {
2529    int newsize = size + 1;
2530    if (buf != null) {
2531        if (newsize > maxsize && maxsize >= 0) {
2532        buf = null;
2533        } else if (newsize > buf.length) {
2534        byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
2535        System.arraycopy(buf, 0, newbuf, 0, size);
2536        buf = newbuf;
2537        buf[size] = (byte)b;
2538        } else {
2539        buf[size] = (byte)b;
2540        }
2541    }
2542    size = newsize;
2543    }
2544
2545    public void write(byte b[], int off, int len) {
2546    if ((off < 0) || (off > b.length) || (len < 0) ||
2547            ((off + len) > b.length) || ((off + len) < 0)) {
2548        throw new IndexOutOfBoundsException JavaDoc();
2549    } else if (len == 0) {
2550        return;
2551    }
2552        int newsize = size + len;
2553    if (buf != null) {
2554        if (newsize > maxsize && maxsize >= 0) {
2555        buf = null;
2556        } else if (newsize > buf.length) {
2557        byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
2558        System.arraycopy(buf, 0, newbuf, 0, size);
2559        buf = newbuf;
2560        System.arraycopy(b, off, buf, size, len);
2561        } else {
2562        System.arraycopy(b, off, buf, size, len);
2563        }
2564    }
2565        size = newsize;
2566    }
2567
2568    public void write(byte[] b) throws IOException {
2569    write(b, 0, b.length);
2570    }
2571
2572    public int getSize() {
2573    return size;
2574    }
2575
2576    public byte[] getBytes() {
2577    return buf;
2578    }
2579}
2580
Popular Tags