Skip to content

Package: IMAPFolder$18

IMAPFolder$18

nameinstructionbranchcomplexitylinemethod
doCommand(IMAPProtocol)
M: 11 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
{...}
M: 12 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.imap;
18:
19: import java.util.Date;
20: import java.util.Hashtable;
21: import java.util.List;
22: import java.util.ArrayList;
23: import java.util.Map;
24: import java.util.NoSuchElementException;
25: import java.util.logging.Level;
26: import java.io.*;
27: import java.net.SocketTimeoutException;
28: import java.nio.channels.SocketChannel;
29:
30: import jakarta.mail.*;
31: import jakarta.mail.event.*;
32: import jakarta.mail.internet.*;
33: import jakarta.mail.search.*;
34:
35: import org.eclipse.angus.mail.imap.protocol.FetchItem;
36: import org.eclipse.angus.mail.imap.protocol.FetchResponse;
37: import org.eclipse.angus.mail.imap.protocol.IMAPProtocol;
38: import org.eclipse.angus.mail.imap.protocol.IMAPResponse;
39: import org.eclipse.angus.mail.imap.protocol.Item;
40: import org.eclipse.angus.mail.imap.protocol.ListInfo;
41: import org.eclipse.angus.mail.imap.protocol.MODSEQ;
42: import org.eclipse.angus.mail.imap.protocol.MailboxInfo;
43: import org.eclipse.angus.mail.imap.protocol.MessageSet;
44: import org.eclipse.angus.mail.imap.protocol.Status;
45: import org.eclipse.angus.mail.imap.protocol.UID;
46: import org.eclipse.angus.mail.imap.protocol.UIDSet;
47: import org.eclipse.angus.mail.util.CRLFOutputStream;
48: import org.eclipse.angus.mail.util.MailLogger;
49: import org.eclipse.angus.mail.iap.*;
50: import org.eclipse.angus.mail.imap.protocol.*;
51: import org.eclipse.angus.mail.iap.BadCommandException;
52: import org.eclipse.angus.mail.iap.CommandFailedException;
53: import org.eclipse.angus.mail.iap.ConnectionException;
54: import org.eclipse.angus.mail.iap.Literal;
55: import org.eclipse.angus.mail.iap.ProtocolException;
56: import org.eclipse.angus.mail.iap.Response;
57: import org.eclipse.angus.mail.iap.ResponseHandler;
58: import org.eclipse.angus.mail.imap.protocol.FLAGS;
59:
60: /**
61: * This class implements an IMAP folder. <p>
62: *
63: * A closed IMAPFolder object shares a protocol connection with its IMAPStore
64: * object. When the folder is opened, it gets its own protocol connection. <p>
65: *
66: * Applications that need to make use of IMAP-specific features may cast
67: * a <code>Folder</code> object to an <code>IMAPFolder</code> object and
68: * use the methods on this class. <p>
69: *
70: * The {@link #getQuota getQuota} and
71: * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
72: * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
73: * for more information. <p>
74: *
75: * The {@link #getACL getACL}, {@link #addACL addACL},
76: * {@link #removeACL removeACL}, {@link #addRights addRights},
77: * {@link #removeRights removeRights}, {@link #listRights listRights}, and
78: * {@link #myRights myRights} methods support the IMAP ACL extension.
79: * Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A>
80: * for more information. <p>
81: *
82: * The {@link #getSortedMessages getSortedMessages}
83: * methods support the IMAP SORT extension.
84: * Refer to <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>
85: * for more information. <p>
86: *
87: * The {@link #open(int, ResyncData) open(int,ResyncData)}
88: * method and {@link ResyncData ResyncData} class supports
89: * the IMAP CONDSTORE and QRESYNC extensions.
90: * Refer to <A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>
91: * and <A HREF="http://www.ietf.org/rfc/rfc5162.txt">RFC 5162</A>
92: * for more information. <p>
93: *
94: * The {@link #doCommand doCommand} method and
95: * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand}
96: * interface support use of arbitrary IMAP protocol commands. <p>
97: *
98: * See the <a href="package-summary.html">org.eclipse.angus.mail.imap</a> package
99: * documentation for further information on the IMAP protocol provider. <p>
100: *
101: * <strong>WARNING:</strong> The APIs unique to this class should be
102: * considered <strong>EXPERIMENTAL</strong>. They may be changed in the
103: * future in ways that are incompatible with applications using the
104: * current APIs.
105: *
106: * @author John Mani
107: * @author Bill Shannon
108: * @author Jim Glennon
109: */
110:
111: /*
112: * The folder object itself serves as a lock for the folder's state
113: * EXCEPT for the message cache (see below), typically by using
114: * synchronized methods. When checking that a folder is open or
115: * closed, the folder's lock must be held. It's important that the
116: * folder's lock is acquired before the messageCacheLock (see below).
117: * Thus, the locking hierarchy is that the folder lock, while optional,
118: * must be acquired before the messageCacheLock, if it's acquired at
119: * all. Be especially careful of callbacks that occur while holding
120: * the messageCacheLock into (e.g.) superclass Folder methods that are
121: * synchronized. Note that methods in IMAPMessage will acquire the
122: * messageCacheLock without acquiring the folder lock. <p>
123: *
124: * When a folder is opened, it creates a messageCache (a Vector) of
125: * empty IMAPMessage objects. Each Message has a messageNumber - which
126: * is its index into the messageCache, and a sequenceNumber - which is
127: * its IMAP sequence-number. All operations on a Message which involve
128: * communication with the server, use the message's sequenceNumber. <p>
129: *
130: * The most important thing to note here is that the server can send
131: * unsolicited EXPUNGE notifications as part of the responses for "most"
132: * commands. Refer RFC 3501, sections 5.3 & 5.5 for gory details. Also,
133: * the server sends these notifications AFTER the message has been
134: * expunged. And once a message is expunged, the sequence-numbers of
135: * those messages after the expunged one are renumbered. This essentially
136: * means that the mapping between *any* Message and its sequence-number
137: * can change in the period when a IMAP command is issued and its responses
138: * are processed. Hence we impose a strict locking model as follows: <p>
139: *
140: * We define one mutex per folder - this is just a Java Object (named
141: * messageCacheLock). Any time a command is to be issued to the IMAP
142: * server (i.e., anytime the corresponding IMAPProtocol method is
143: * invoked), follow the below style:
144: *                
145: *        synchronized (messageCacheLock) { // ACQUIRE LOCK
146: *         issue command ()
147: *        
148: *         // The response processing is typically done within
149: *         // the handleResponse() callback. A few commands (Fetch,
150: *         // Expunge) return *all* responses and hence their
151: *         // processing is done here itself. Now, as part of the
152: *         // processing unsolicited EXPUNGE responses, we renumber
153: *         // the necessary sequence-numbers. Thus the renumbering
154: *         // happens within this critical-region, surrounded by
155: *         // locks.
156: *         process responses ()
157: *        } // RELEASE LOCK
158: *
159: * This technique is used both by methods in IMAPFolder and by methods
160: * in IMAPMessage and other classes that operate on data in the folder.
161: * Note that holding the messageCacheLock has the side effect of
162: * preventing the folder from being closed, and thus ensuring that the
163: * folder's protocol object is still valid. The protocol object should
164: * only be accessed while holding the messageCacheLock (except for calls
165: * to IMAPProtocol.isREV1(), which don't need to be protected because it
166: * doesn't access the server).
167: *        
168: * Note that interactions with the Store's protocol connection do
169: * not have to be protected as above, since the Store's protocol is
170: * never in a "meaningful" SELECT-ed state.
171: */
172:
173: public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
174:
175: protected volatile String fullName;        // full name
176: protected String name;                // name
177: protected int type;                        // folder type.
178: protected char separator;                // separator
179: protected Flags availableFlags;         // available flags
180: protected Flags permanentFlags;         // permanent flags
181: protected volatile boolean exists;        // whether this folder really exists ?
182: protected boolean isNamespace = false; // folder is a namespace name
183: protected volatile String[] attributes;// name attributes from LIST response
184:
185: protected volatile IMAPProtocol protocol; // this folder's protocol object
186: protected MessageCache messageCache;// message cache
187: // accessor lock for message cache
188: protected final Object messageCacheLock = new Object();
189:
190: protected Hashtable<Long, IMAPMessage> uidTable; // UID->Message hashtable
191:
192: /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL).
193: * We use '\uffff' (a non 7bit character) to indicate that we havent
194: * yet determined what the separator character is.
195: * We use '\u0000' (NUL) to indicate that no separator character
196: * exists, i.e., a flat hierarchy
197: */
198: static final protected char UNKNOWN_SEPARATOR = '\uffff';
199:
200: private volatile boolean opened = false;         // is this folder opened ?
201:
202: /* This field tracks the state of this folder. If the folder is closed
203: * due to external causes (i.e, not thru the close() method), then
204: * this field will remain false. If the folder is closed thru the
205: * close() method, then this field is set to true.
206: *
207: * If reallyClosed is false, then a FolderClosedException is
208: * generated when a method is invoked on any Messaging object
209: * owned by this folder. If reallyClosed is true, then the
210: * IllegalStateException runtime exception is thrown.
211: */
212: private boolean reallyClosed = true;
213:
214: /*
215: * The idleState field supports the IDLE command.
216: * Normally when executing an IMAP command we hold the
217: * messageCacheLock and often the folder lock (see above).
218: * While executing the IDLE command we can't hold either
219: * of these locks or it would prevent other threads from
220: * entering Folder methods even far enough to check whether
221: * an IDLE command is in progress. We need to check before
222: * issuing another command so that we can abort the IDLE
223: * command.
224: *
225: * The idleState field is protected by the messageCacheLock.
226: * The RUNNING state is the normal state and means no IDLE
227: * command is in progress. The IDLE state means we've issued
228: * an IDLE command and are reading responses. The ABORTING
229: * state means we've sent the DONE continuation command and
230: * are waiting for the thread running the IDLE command to
231: * break out of its read loop.
232: *
233: * When an IDLE command is in progress, the thread calling
234: * the idle method will be reading from the IMAP connection
235: * while holding neither the folder lock nor the messageCacheLock.
236: * It's obviously critical that no other thread try to send a
237: * command or read from the connection while in this state.
238: * However, other threads can send the DONE continuation
239: * command that will cause the server to break out of the IDLE
240: * loop and send the ending tag response to the IDLE command.
241: * The thread in the idle method that's reading the responses
242: * from the IDLE command will see this ending response and
243: * complete the idle method, setting the idleState field back
244: * to RUNNING, and notifying any threads waiting to use the
245: * connection.
246: *
247: * All uses of the IMAP connection (IMAPProtocol object) must
248: * be done while holding the messageCacheLock and must be
249: * preceeded by a check to make sure an IDLE command is not
250: * running, and abort the IDLE command if necessary. While
251: * waiting for the IDLE command to complete, these other threads
252: * will give up the messageCacheLock, but might still be holding
253: * the folder lock. This check is done by the getProtocol()
254: * method, resulting in a typical usage pattern of:
255: *
256: *         synchronized (messageCacheLock) {
257: *                IMAPProtocol p = getProtocol();        // may block waiting for IDLE
258: *                // ... use protocol
259: *         }
260: */
261: private static final int RUNNING = 0;        // not doing IDLE command
262: private static final int IDLE = 1;                // IDLE command in effect
263: private static final int ABORTING = 2;        // IDLE command aborting
264: private int idleState = RUNNING;
265: private IdleManager idleManager;
266:
267: private volatile int total = -1;        // total number of messages in the
268:                                         // message cache
269: private volatile int recent = -1;        // number of recent messages
270: private int realTotal = -1;                // total number of messages on
271:                                         // the server
272: private long uidvalidity = -1;        // UIDValidity
273: private long uidnext = -1;                // UIDNext
274: private boolean uidNotSticky = false;        // RFC 4315
275: private volatile long highestmodseq = -1;        // RFC 4551 - CONDSTORE
276: private boolean doExpungeNotification = true; // used in expunge handler
277:
278: private Status cachedStatus = null;
279: private long cachedStatusTime = 0;
280:
281: private boolean hasMessageCountListener = false;        // optimize notification
282:
283: protected MailLogger logger;
284: private MailLogger connectionPoolLogger;
285:
286: /**
287: * A fetch profile item for fetching headers.
288: * This inner class extends the <code>FetchProfile.Item</code>
289: * class to add new FetchProfile item types, specific to IMAPFolders.
290: *
291: * @see FetchProfile
292: */
293: public static class FetchProfileItem extends FetchProfile.Item {
294:         protected FetchProfileItem(String name) {
295:          super(name);
296:         }
297:
298:         /**
299:          * HEADERS is a fetch profile item that can be included in a
300:          * <code>FetchProfile</code> during a fetch request to a Folder.
301:          * This item indicates that the headers for messages in the specified
302:          * range are desired to be prefetched. <p>
303:          *
304:          * An example of how a client uses this is below:
305:          * <blockquote><pre>
306:          *
307:          *         FetchProfile fp = new FetchProfile();
308:          *        fp.add(IMAPFolder.FetchProfileItem.HEADERS);
309:          *        folder.fetch(msgs, fp);
310:          *
311:          * </pre></blockquote>
312:          */
313:         public static final FetchProfileItem HEADERS =
314:                 new FetchProfileItem("HEADERS");
315:
316:         /**
317:          * SIZE is a fetch profile item that can be included in a
318:          * <code>FetchProfile</code> during a fetch request to a Folder.
319:          * This item indicates that the sizes of the messages in the specified
320:          * range are desired to be prefetched. <p>
321:          *
322:          * SIZE was moved to FetchProfile.Item in JavaMail 1.5.
323:          *
324:          * @deprecated
325:          */
326:         @Deprecated
327:         public static final FetchProfileItem SIZE =
328:                 new FetchProfileItem("SIZE");
329:
330:         /**
331:          * MESSAGE is a fetch profile item that can be included in a
332:          * <code>FetchProfile</code> during a fetch request to a Folder.
333:          * This item indicates that the entire messages (headers and body,
334:          * including all "attachments") in the specified
335:          * range are desired to be prefetched. Note that the entire message
336:          * content is cached in memory while the Folder is open. The cached
337:          * message will be parsed locally to return header information and
338:          * message content. <p>
339:          *
340:          * An example of how a client uses this is below:
341:          * <blockquote><pre>
342:          *
343:          *         FetchProfile fp = new FetchProfile();
344:          *        fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
345:          *        folder.fetch(msgs, fp);
346:          *
347:          * </pre></blockquote>
348:          *
349:          * @since        JavaMail 1.5.2
350:          */
351:         public static final FetchProfileItem MESSAGE =
352:                 new FetchProfileItem("MESSAGE");
353:
354:         /**
355:          * INTERNALDATE is a fetch profile item that can be included in a
356:          * <code>FetchProfile</code> during a fetch request to a Folder.
357:          * This item indicates that the IMAP INTERNALDATE values
358:          * (received date) of the messages in the specified
359:          * range are desired to be prefetched. <p>
360:          *
361:          * An example of how a client uses this is below:
362:          * <blockquote><pre>
363:          *
364:          *         FetchProfile fp = new FetchProfile();
365:          *        fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
366:          *        folder.fetch(msgs, fp);
367:          *
368:          * </pre></blockquote>
369:          *
370:          * @since        JavaMail 1.5.5
371:          */
372:         public static final FetchProfileItem INTERNALDATE =
373:                 new FetchProfileItem("INTERNALDATE");
374: }
375:
376: /**
377: * Constructor used to create a possibly non-existent folder.
378: *
379: * @param fullName        fullname of this folder
380: * @param separator the default separator character for this
381: *                        folder's namespace
382: * @param store        the Store
383: * @param isNamespace if this folder represents a namespace
384: */
385: protected IMAPFolder(String fullName, char separator, IMAPStore store,
386:                                 Boolean isNamespace) {
387:         super(store);
388:         if (fullName == null)
389:          throw new NullPointerException("Folder name is null");
390:         this.fullName = fullName;
391:         this.separator = separator;
392:         logger = new MailLogger(this.getClass(), "DEBUG IMAP",
393:          store.getSession().getDebug(), store.getSession().getDebugOut());
394:         connectionPoolLogger = store.getConnectionPoolLogger();
395:
396:         /*
397:          * Work around apparent bug in Exchange. Exchange
398:          * will return a name of "Public Folders/" from
399:          * LIST "%".
400:          *
401:          * If name has one separator, and it's at the end,
402:          * assume this is a namespace name and treat it
403:          * accordingly. Usually this will happen as a result
404:          * of the list method, but this also allows getFolder
405:          * to work with namespace names.
406:          */
407:         this.isNamespace = false;
408:         if (separator != UNKNOWN_SEPARATOR && separator != '\0') {
409:          int i = this.fullName.indexOf(separator);
410:          if (i > 0 && i == this.fullName.length() - 1) {
411:                 this.fullName = this.fullName.substring(0, i);
412:                 this.isNamespace = true;
413:          }
414:         }
415:
416:         // if we were given a value, override default chosen above
417:         if (isNamespace != null)
418:          this.isNamespace = isNamespace.booleanValue();
419: }
420:
421: /**
422: * Constructor used to create an existing folder.
423: *
424: * @param        li        the ListInfo for this folder
425: * @param        store        the store containing this folder
426: */
427: protected IMAPFolder(ListInfo li, IMAPStore store) {
428:         this(li.name, li.separator, store, null);
429:
430:         if (li.hasInferiors)
431:          type |= HOLDS_FOLDERS;
432:         if (li.canOpen)
433:          type |= HOLDS_MESSAGES;
434:         exists = true;
435:         attributes = li.attrs;
436: }
437:         
438: /*
439: * Ensure that this folder exists. If 'exists' has been set to true,
440: * we don't attempt to validate it with the server again. Note that
441: * this can result in a possible loss of sync with the server.
442: * ASSERT: Must be called with this folder's synchronization lock held.
443: */
444: protected void checkExists() throws MessagingException {
445:         // If the boolean field 'exists' is false, check with the
446:         // server by invoking exists() ..
447:         if (!exists && !exists())
448:          throw new FolderNotFoundException(
449:                 this, fullName + " not found");
450: }
451:
452: /*
453: * Ensure the folder is closed.
454: * ASSERT: Must be called with this folder's synchronization lock held.
455: */
456: protected void checkClosed() {
457:         if (opened)
458:          throw new IllegalStateException(
459:                 "This operation is not allowed on an open folder"
460:                 );
461: }
462:
463: /*
464: * Ensure the folder is open.
465: * ASSERT: Must be called with this folder's synchronization lock held.
466: */
467: protected void checkOpened() throws FolderClosedException {
468:         assert Thread.holdsLock(this);
469:         if (!opened) {
470:          if (reallyClosed)
471:                 throw new IllegalStateException(
472:                  "This operation is not allowed on a closed folder"
473:                  );
474:          else // Folder was closed "implicitly"
475:                 throw new FolderClosedException(this,
476:                  "Lost folder connection to server"
477:                 );
478:         }
479: }
480:
481: /*
482: * Check that the given message number is within the range
483: * of messages present in this folder. If the message
484: * number is out of range, we ping the server to obtain any
485: * pending new message notifications from the server.
486: */
487: protected void checkRange(int msgno) throws MessagingException {
488:         if (msgno < 1) // message-numbers start at 1
489:          throw new IndexOutOfBoundsException("message number < 1");
490:
491:         if (msgno <= total)
492:          return;
493:
494:         // Out of range, let's ping the server and see if
495:         // the server has more messages for us.
496:
497:         synchronized(messageCacheLock) { // Acquire lock
498:          try {
499:                 keepConnectionAlive(false);
500:          } catch (ConnectionException cex) {
501:                 // Oops, lost connection
502:                 throw new FolderClosedException(this, cex.getMessage());
503:          } catch (ProtocolException pex) {
504:                 throw new MessagingException(pex.getMessage(), pex);
505:          }
506:         } // Release lock
507:
508:         if (msgno > total) // Still out of range ? Throw up ...
509:          throw new IndexOutOfBoundsException(msgno + " > " + total);
510: }
511:
512: /*
513: * Check whether the given flags are supported by this server,
514: * and also verify that the folder allows setting flags.
515: */
516: private void checkFlags(Flags flags) throws MessagingException {
517:         assert Thread.holdsLock(this);
518:         if (mode != READ_WRITE)
519:          throw new IllegalStateException(
520:                 "Cannot change flags on READ_ONLY folder: " + fullName
521:                 );
522:         /*
523:         if (!availableFlags.contains(flags))
524:          throw new MessagingException(
525:                 "These flags are not supported by this implementation"
526:                 );
527:         */
528: }
529:
530: /**
531: * Get the name of this folder.
532: */
533: @Override
534: public synchronized String getName() {
535:         /* Return the last component of this Folder's full name.
536:          * Folder components are delimited by the separator character.
537:          */
538:         if (name == null) {
539:          try {
540:                 name =         fullName.substring(
541:                          fullName.lastIndexOf(getSeparator()) + 1
542:                         );
543:          } catch (MessagingException mex) { }
544:         }
545:         return name;
546: }
547:
548: /**
549: * Get the fullname of this folder.
550: */
551: @Override
552: public String getFullName() {
553:         return fullName;        
554: }
555:
556: /**
557: * Get this folder's parent.
558: */
559: @Override
560: public synchronized Folder getParent() throws MessagingException {
561:         char c = getSeparator();
562:         int index;
563:         if ((index = fullName.lastIndexOf(c)) != -1)
564:          return ((IMAPStore)store).newIMAPFolder(
565:                          fullName.substring(0, index), c);
566:         else
567:          return new DefaultFolder((IMAPStore)store);
568: }
569:
570: /**
571: * Check whether this folder really exists on the server.
572: */
573: @Override
574: public synchronized boolean exists() throws MessagingException {
575:         // Check whether this folder exists ..
576:         ListInfo[] li = null;
577:         final String lname;
578:         if (isNamespace && separator != '\0')
579:          lname = fullName + separator;
580:         else
581:          lname = fullName;
582:
583:         li = (ListInfo[])doCommand(new ProtocolCommand() {
584:          @Override
585:          public Object doCommand(IMAPProtocol p) throws ProtocolException {
586:                 return p.list("", lname);
587:          }
588:         });
589:
590:         if (li != null) {
591:          int i = findName(li, lname);
592:          fullName = li[i].name;
593:          separator = li[i].separator;
594:          int len = fullName.length();
595:          if (separator != '\0' && len > 0 &&
596:                  fullName.charAt(len - 1) == separator) {
597:                 fullName = fullName.substring(0, len - 1);
598:          }
599:          type = 0;
600:          if (li[i].hasInferiors)
601:                 type |= HOLDS_FOLDERS;
602:          if (li[i].canOpen)
603:                 type |= HOLDS_MESSAGES;
604:          exists = true;
605:          attributes = li[i].attrs;
606:         } else {
607:          exists = opened;
608:          attributes = null;
609:         }
610:
611:         return exists;
612: }
613:
614: /**
615: * Which entry in <code>li</code> matches <code>lname</code>?
616: * If the name contains wildcards, more than one entry may be
617: * returned.
618: */
619: private int findName(ListInfo[] li, String lname) {
620:         int i;
621:         // if the name contains a wildcard, there might be more than one
622:         for (i = 0; i < li.length; i++) {
623:          if (li[i].name.equals(lname))
624:                 break;
625:         }
626:         if (i >= li.length) {        // nothing matched exactly
627:          // XXX - possibly should fail? But what if server
628:          // is case insensitive and returns the preferred
629:          // case of the name here?
630:          i = 0;                // use first one
631:         }
632:         return i;
633: }
634:
635: /**
636: * List all subfolders matching the specified pattern.
637: */
638: @Override
639: public Folder[] list(String pattern) throws MessagingException {
640:         return doList(pattern, false);
641: }
642:
643: /**
644: * List all subscribed subfolders matching the specified pattern.
645: */
646: @Override
647: public Folder[] listSubscribed(String pattern) throws MessagingException {
648:         return doList(pattern, true);
649: }
650:
651: private synchronized Folder[] doList(final String pattern,
652:                 final boolean subscribed) throws MessagingException {
653:         checkExists(); // insure that this folder does exist.
654:         
655:         // Why waste a roundtrip to the server?
656:         if (attributes != null && !isDirectory())
657:          return new Folder[0];
658:
659:         final char c = getSeparator();
660:
661:         ListInfo[] li = (ListInfo[])doCommandIgnoreFailure(
662:          new ProtocolCommand() {
663:                 @Override
664:                 public Object doCommand(IMAPProtocol p)
665:                         throws ProtocolException {
666:                  if (subscribed)
667:                         return p.lsub("", fullName + c + pattern);
668:                  else
669:                         return p.list("", fullName + c + pattern);
670:                 }
671:          });
672:
673:         if (li == null)
674:          return new Folder[0];
675:
676:         /*
677:          * The UW based IMAP4 servers (e.g. SIMS2.0) include
678:          * current folder (terminated with the separator), when
679:          * the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%>
680:          * returns "mail/" as the first LIST response.
681:          *
682:          * Doesn't make sense to include the current folder in this
683:          * case, so we filter it out. Note that I'm assuming that
684:          * the offending response is the *first* one, my experiments
685:          * with the UW & SIMS2.0 servers indicate that ..
686:          */
687:         int start = 0;
688:         // Check the first LIST response.
689:         if (li.length > 0 && li[0].name.equals(fullName + c))
690:          start = 1; // start from index = 1
691:
692:         IMAPFolder[] folders = new IMAPFolder[li.length - start];
693:         IMAPStore st = (IMAPStore)store;
694:         for (int i = start; i < li.length; i++)
695:          folders[i-start] = st.newIMAPFolder(li[i]);
696:         return folders;
697: }
698:
699: /**
700: * Get the separator character.
701: */
702: @Override
703: public synchronized char getSeparator() throws MessagingException {
704:         if (separator == UNKNOWN_SEPARATOR) {
705:          ListInfo[] li = null;
706:
707:          li = (ListInfo[])doCommand(new ProtocolCommand() {
708:                 @Override
709:                 public Object doCommand(IMAPProtocol p)
710:                         throws ProtocolException {
711:                  // REV1 allows the following LIST format to obtain
712:                  // the hierarchy delimiter of non-existent folders
713:                  if (p.isREV1()) // IMAP4rev1
714:                  return p.list(fullName, "");
715:                  else // IMAP4, note that this folder must exist for this
716:                  // to work :(
717:                  return p.list("", fullName);
718:                 }
719:          });
720:
721:          if (li != null)
722:                 separator = li[0].separator;
723:          else
724:                 separator = '/'; // punt !
725:         }
726:         return separator;
727: }
728:
729: /**
730: * Get the type of this folder.
731: */
732: @Override
733: public synchronized int getType() throws MessagingException {
734:         if (opened) {
735:          // never throw FolderNotFoundException if folder is open
736:          if (attributes == null)
737:                 exists();        // try to fetch attributes
738:         } else {
739:          checkExists();
740:         }
741:         return type;
742: }
743:
744: /**
745: * Check whether this folder is subscribed.
746: */
747: @Override
748: public synchronized boolean isSubscribed() {
749:         ListInfo[] li = null;
750:         final String lname;
751:         if (isNamespace && separator != '\0')
752:          lname = fullName + separator;
753:         else
754:          lname = fullName;
755:
756:         try {
757:          li = (ListInfo[])doProtocolCommand(new ProtocolCommand() {
758:                 @Override
759:                 public Object doCommand(IMAPProtocol p)
760:                         throws ProtocolException {
761:                  return p.lsub("", lname);
762:                 }
763:          });
764:         } catch (ProtocolException pex) {
765: }
766:
767:         if (li != null) {
768:          int i = findName(li, lname);
769:          return li[i].canOpen;
770:         } else
771:          return false;
772: }
773:
774: /**
775: * Subscribe/Unsubscribe this folder.
776: */
777: @Override
778: public synchronized void setSubscribed(final boolean subscribe)
779:                         throws MessagingException {
780:         doCommandIgnoreFailure(new ProtocolCommand() {
781:          @Override
782:          public Object doCommand(IMAPProtocol p) throws ProtocolException {
783:                 if (subscribe)
784:                  p.subscribe(fullName);
785:                 else
786:                  p.unsubscribe(fullName);
787:                 return null;
788:          }
789:         });
790: }
791:         
792: /**
793: * Create this folder, with the specified type.
794: */
795: @Override
796: public synchronized boolean create(final int type)
797:                                 throws MessagingException {
798:
799:         char c = 0;
800:         if ((type & HOLDS_MESSAGES) == 0)        // only holds folders
801:          c = getSeparator();
802:         final char sep = c;
803:         Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
804:          @Override
805:                 public Object doCommand(IMAPProtocol p)
806:                         throws ProtocolException {
807:                  if ((type & HOLDS_MESSAGES) == 0)        // only holds folders
808:                         p.create(fullName + sep);
809:                  else {
810:                         p.create(fullName);
811:
812:                         // Certain IMAP servers do not allow creation of folders
813:                         // that can contain messages *and* subfolders. So, if we
814:                         // were asked to create such a folder, we should verify
815:                         // that we could indeed do so.
816:                         if ((type & HOLDS_FOLDERS) != 0) {
817:                          // we want to hold subfolders and messages. Check
818:                          // whether we could create such a folder.
819:                          ListInfo[] li = p.list("", fullName);
820:                          if (li != null && !li[0].hasInferiors) {
821:                                 // Hmm ..the new folder
822:                                 // doesn't support Inferiors ? Fail
823:                                 p.delete(fullName);
824:                                 throw new ProtocolException("Unsupported type");
825:                          }
826:                         }
827:                  }
828:                  return Boolean.TRUE;
829:                 }
830:          });
831:
832:         if (ret == null)
833:          return false; // CREATE failure, maybe this
834:                          // folder already exists ?
835:
836:         // exists = true;
837:         // this.type = type;
838:         boolean retb = exists();        // set exists, type, and attributes
839:         if (retb)                // Notify listeners on self and our Store
840:          notifyFolderListeners(FolderEvent.CREATED);
841:         return retb;
842: }
843:
844: /**
845: * Check whether this folder has new messages.
846: */
847: @Override
848: public synchronized boolean hasNewMessages() throws MessagingException {
849:         synchronized (messageCacheLock) {
850:          if (opened) { // If we are open, we already have this information
851:                 // Folder is open, make sure information is up to date
852:                 // tickle the folder and store connections.
853:                 try {
854:                  keepConnectionAlive(true);
855:                 } catch (ConnectionException cex) {
856:                  throw new FolderClosedException(this, cex.getMessage());
857:                 } catch (ProtocolException pex) {
858:                  throw new MessagingException(pex.getMessage(), pex);
859:                 }
860:                 return recent > 0 ? true : false;
861:          }
862:         }
863:
864:         // First, the cheap way - use LIST and look for the \Marked
865:         // or \Unmarked tag
866:
867:         ListInfo[] li = null;
868:         final String lname;
869:         if (isNamespace && separator != '\0')
870:          lname = fullName + separator;
871:         else
872:          lname = fullName;
873:         li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() {
874:          @Override
875:          public Object doCommand(IMAPProtocol p) throws ProtocolException {
876:                 return p.list("", lname);
877:          }
878:         });
879:
880:         // if folder doesn't exist, throw exception
881:         if (li == null)
882:          throw new FolderNotFoundException(this, fullName + " not found");
883:
884:         int i = findName(li, lname);
885:         if (li[i].changeState == ListInfo.CHANGED)
886:          return true;
887:         else if (li[i].changeState == ListInfo.UNCHANGED)
888:          return false;
889:
890:         // LIST didn't work. Try the hard way, using STATUS
891:         try {
892:          Status status = getStatus();
893:          if (status.recent > 0)
894:                 return true;
895:          else
896:                 return false;
897:         } catch (BadCommandException bex) {
898:          // Probably doesn't support STATUS, tough luck.
899:          return false;
900:         } catch (ConnectionException cex) {
901:          throw new StoreClosedException(store, cex.getMessage());
902:         } catch (ProtocolException pex) {
903:          throw new MessagingException(pex.getMessage(), pex);
904:         }
905: }
906:
907: /**
908: * Get the named subfolder.
909: */
910: @Override
911: public synchronized Folder getFolder(String name)
912:                                 throws MessagingException {
913:         // If we know that this folder is *not* a directory, don't
914:         // send the request to the server at all ...
915:         if (attributes != null && !isDirectory())
916:          throw new MessagingException("Cannot contain subfolders");
917:
918:         char c = getSeparator();
919:         return ((IMAPStore)store).newIMAPFolder(fullName + c + name, c);
920: }
921:
922: /**
923: * Delete this folder.
924: */
925: @Override
926: public synchronized boolean delete(boolean recurse)
927:                         throws MessagingException {
928:         checkClosed(); // insure that this folder is closed.
929:
930:         if (recurse) {
931:          // Delete all subfolders.
932:          Folder[] f = list();
933:          for (int i = 0; i < f.length; i++)
934:                 f[i].delete(recurse); // ignore intermediate failures
935:         }
936:
937:         // Attempt to delete this folder
938:
939:         Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
940:          @Override
941:          public Object doCommand(IMAPProtocol p) throws ProtocolException {
942:                 p.delete(fullName);
943:                 return Boolean.TRUE;
944:          }
945:         });
946:
947:         if (ret == null)
948:          // Non-existent folder/No permission ??
949:          return false;
950:
951:         // DELETE succeeded.
952:         exists = false;
953:         attributes = null;
954:
955:         // Notify listeners on self and our Store
956:         notifyFolderListeners(FolderEvent.DELETED);
957:         return true;
958: }
959:
960: /**
961: * Rename this folder.
962: */
963: @Override
964: public synchronized boolean renameTo(final Folder f)
965:                                 throws MessagingException {
966:         checkClosed(); // insure that we are closed.
967:         checkExists();
968:         if (f.getStore() != store)
969:          throw new MessagingException("Can't rename across Stores");
970:
971:
972:         Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
973:          @Override
974:          public Object doCommand(IMAPProtocol p) throws ProtocolException {
975:                 p.rename(fullName, f.getFullName());
976:                 return Boolean.TRUE;
977:          }
978:         });
979:
980:         if (ret == null)
981:          return false;
982:
983:         exists = false;
984:         attributes = null;
985:         notifyFolderRenamedListeners(f);
986:         return true;
987: }
988:
989: /**
990: * Open this folder in the given mode.
991: */
992: @Override
993: public synchronized void open(int mode) throws MessagingException {
994:         open(mode, null);
995: }
996:
997: /**
998: * Open this folder in the given mode, with the given
999: * resynchronization data.
1000: *
1001: * @param        mode        the open mode (Folder.READ_WRITE or Folder.READ_ONLY)
1002: * @param        rd        the ResyncData instance
1003: * @return                a List of MailEvent instances, or null if none
1004: * @exception MessagingException        if the open fails
1005: * @since        JavaMail 1.5.1
1006: */
1007: public synchronized List<MailEvent> open(int mode, ResyncData rd)
1008:                                 throws MessagingException {
1009:         checkClosed(); // insure that we are not already open
1010:         
1011:         MailboxInfo mi = null;
1012:         // Request store for our own protocol connection.
1013:         protocol = ((IMAPStore)store).getProtocol(this);
1014:
1015:         List<MailEvent> openEvents = null;
1016:         synchronized(messageCacheLock) { // Acquire messageCacheLock
1017:
1018:          /*
1019:          * Add response handler right away so we get any alerts or
1020:          * notifications that occur during the SELECT or EXAMINE.
1021:          * Have to be sure to remove it if we fail to open the
1022:          * folder.
1023:          */
1024:          protocol.addResponseHandler(this);
1025:
1026:          try {
1027:                 /*
1028:                  * Enable QRESYNC or CONDSTORE if needed and not enabled.
1029:                  * QRESYNC implies CONDSTORE, but servers that support
1030:                  * QRESYNC are not required to support just CONDSTORE
1031:                  * per RFC 5162.
1032:                  */
1033:                 if (rd != null) {
1034:                  if (rd == ResyncData.CONDSTORE) {
1035:                         if (!protocol.isEnabled("CONDSTORE") &&
1036:                          !protocol.isEnabled("QRESYNC")) {
1037:                          if (protocol.hasCapability("CONDSTORE"))
1038:                                 protocol.enable("CONDSTORE");
1039:                          else
1040:                                 protocol.enable("QRESYNC");
1041:                         }
1042:                  } else {
1043:                         if (!protocol.isEnabled("QRESYNC"))
1044:                          protocol.enable("QRESYNC");
1045:                  }
1046:                 }
1047:
1048:                 if (mode == READ_ONLY)
1049:                  mi = protocol.examine(fullName, rd);
1050:                 else
1051:                  mi = protocol.select(fullName, rd);
1052:          } catch (CommandFailedException cex) {
1053:                 /*
1054:                  * Handle SELECT or EXAMINE failure.
1055:                  * Try to figure out why the operation failed so we can
1056:                  * report a more reasonable exception.
1057:                  *
1058:                  * Will use our existing protocol object.
1059:                  */
1060:                 try {
1061:                  checkExists(); // throw exception if folder doesn't exist
1062:
1063:                  if ((type & HOLDS_MESSAGES) == 0)
1064:                         throw new MessagingException(
1065:                          "folder cannot contain messages");
1066:                  throw new MessagingException(cex.getMessage(), cex);
1067:
1068:                 } finally {
1069:                  // folder not open, don't keep this information
1070:                  exists = false;
1071:                  attributes = null;
1072:                  type = 0;
1073:                  // connection still good, return it
1074:                  releaseProtocol(true);
1075:                 }
1076:                 // NOTREACHED
1077:          } catch (ProtocolException pex) {
1078:                 // got a BAD or a BYE; connection may be bad, close it
1079:                 try {
1080:                  throw logoutAndThrow(pex.getMessage(), pex);
1081:                 } finally {
1082:                  releaseProtocol(false);
1083:                 }
1084:          }
1085:
1086:          if (mi.mode != mode) {
1087:                 if (mode == READ_WRITE && mi.mode == READ_ONLY &&
1088:                         ((IMAPStore)store).allowReadOnlySelect()) {
1089:                  ;                // all ok, allow it
1090:                 } else {        // otherwise, it's an error
1091:                  ReadOnlyFolderException ife = new ReadOnlyFolderException(
1092:                          this, "Cannot open in desired mode");
1093:                  throw cleanupAndThrow(ife);
1094:                 }
1095: }
1096:         
1097:          // Initialize stuff.
1098:          opened = true;
1099:          reallyClosed = false;
1100:          this.mode = mi.mode;
1101:          availableFlags = mi.availableFlags;
1102:          permanentFlags = mi.permanentFlags;
1103:          total = realTotal = mi.total;
1104:          recent = mi.recent;
1105:          uidvalidity = mi.uidvalidity;
1106:          uidnext = mi.uidnext;
1107:          uidNotSticky = mi.uidNotSticky;
1108:          highestmodseq = mi.highestmodseq;
1109:
1110:          // Create the message cache of appropriate size
1111:          messageCache = new MessageCache(this, (IMAPStore)store, total);
1112:
1113:          // process saved responses and return corresponding events
1114:          if (mi.responses != null) {
1115:                 openEvents = new ArrayList<>();
1116:                 for (IMAPResponse ir : mi.responses) {
1117:                  if (ir.keyEquals("VANISHED")) {
1118:                         // "VANISHED" SP ["(EARLIER)"] SP known-uids
1119:                         String[] s = ir.readAtomStringList();
1120:                         // check that it really is "EARLIER"
1121:                         if (s == null || s.length != 1 ||
1122:                                          !s[0].equalsIgnoreCase("EARLIER"))
1123:                          continue;        // it's not, what to do with it here?
1124:                         String uids = ir.readAtom();
1125:                         UIDSet[] uidset = UIDSet.parseUIDSets(uids);
1126:                         long[] luid = UIDSet.toArray(uidset, uidnext);
1127:                         if (luid != null && luid.length > 0)
1128:                          openEvents.add(
1129:                                 new MessageVanishedEvent(this, luid));
1130:                  } else if (ir.keyEquals("FETCH")) {
1131:                         assert ir instanceof FetchResponse :
1132:                                 "!ir instanceof FetchResponse";
1133:                         Message msg = processFetchResponse((FetchResponse)ir);
1134:                         if (msg != null)
1135:                          openEvents.add(new MessageChangedEvent(this,
1136:                                  MessageChangedEvent.FLAGS_CHANGED, msg));
1137:                  }
1138:                 }
1139:          }
1140:         } // Release lock
1141:
1142:         exists = true;                // if we opened it, it must exist
1143:         attributes = null;        // but we don't yet know its attributes
1144:         type = HOLDS_MESSAGES;        // lacking more info, we know at least this much
1145:
1146:         // notify listeners
1147:         notifyConnectionListeners(ConnectionEvent.OPENED);
1148:
1149:         return openEvents;
1150: }
1151:
1152: private MessagingException cleanupAndThrow(MessagingException ife) {
1153:         try {
1154:          try {
1155:                 // close mailbox and return connection
1156:                 protocol.close();
1157:                 releaseProtocol(true);
1158:          } catch (ProtocolException pex) {
1159:                 // something went wrong, close connection
1160:                 try {
1161:                  addSuppressed(ife, logoutAndThrow(pex.getMessage(), pex));
1162:                 } finally {
1163:                  releaseProtocol(false);
1164:                 }
1165:          }
1166:         } catch (Throwable thr) {
1167:          addSuppressed(ife, thr);
1168:         }
1169:         return ife;
1170: }
1171:
1172: private MessagingException logoutAndThrow(String why, ProtocolException t) {
1173:         MessagingException ife = new MessagingException(why, t);
1174:         try {
1175:          protocol.logout();
1176:         } catch (Throwable thr) {
1177:          addSuppressed(ife, thr);
1178:         }
1179:         return ife;
1180: }
1181:
1182: private void addSuppressed(Throwable ife, Throwable thr) {
1183:         if (isRecoverable(thr)) {
1184:          ife.addSuppressed(thr);
1185:         } else {
1186:          thr.addSuppressed(ife);
1187:          if (thr instanceof Error) {
1188:                 throw (Error) thr;
1189:          }
1190:          if (thr instanceof RuntimeException) {
1191:                 throw (RuntimeException) thr;
1192:          }
1193:          throw new RuntimeException("unexpected exception", thr);
1194:         }
1195: }
1196:
1197: private boolean isRecoverable(Throwable t) {
1198:         return (t instanceof Exception) || (t instanceof LinkageError);
1199: }
1200:
1201: /**
1202: * Prefetch attributes, based on the given FetchProfile.
1203: */
1204: @Override
1205: public synchronized void fetch(Message[] msgs, FetchProfile fp)
1206:                         throws MessagingException {
1207:         // cache this information in case connection is closed and
1208:         // protocol is set to null
1209:         boolean isRev1;
1210:         FetchItem[] fitems;
1211: synchronized (messageCacheLock) {
1212:          checkOpened();
1213:          isRev1 = protocol.isREV1();
1214:          fitems = protocol.getFetchItems();
1215:         }
1216:
1217:         StringBuilder command = new StringBuilder();
1218:         boolean first = true;
1219:         boolean allHeaders = false;
1220:
1221:         if (fp.contains(FetchProfile.Item.ENVELOPE)) {
1222:          command.append(getEnvelopeCommand());
1223:          first = false;
1224:         }
1225:         if (fp.contains(FetchProfile.Item.FLAGS)) {
1226:          command.append(first ? "FLAGS" : " FLAGS");
1227:          first = false;
1228:         }
1229:         if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
1230:          command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE");
1231:          first = false;
1232:         }
1233:         if (fp.contains(UIDFolder.FetchProfileItem.UID)) {
1234:          command.append(first ? "UID" : " UID");
1235:          first = false;
1236:         }
1237:         if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) {
1238:          allHeaders = true;
1239:          if (isRev1)
1240:                 command.append(first ?
1241:                          "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]");
1242:          else
1243:                 command.append(first ? "RFC822.HEADER" : " RFC822.HEADER");
1244:          first = false;
1245:         }
1246:         if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE)) {
1247:          allHeaders = true;
1248:          if (isRev1)
1249:                 command.append(first ? "BODY.PEEK[]" : " BODY.PEEK[]");
1250:          else
1251:                 command.append(first ? "RFC822" : " RFC822");
1252:          first = false;
1253:         }
1254:         if (fp.contains(FetchProfile.Item.SIZE) ||
1255:                 fp.contains(IMAPFolder.FetchProfileItem.SIZE)) {
1256:          command.append(first ? "RFC822.SIZE" : " RFC822.SIZE");
1257:          first = false;
1258:         }
1259:         if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE)) {
1260:          command.append(first ? "INTERNALDATE" : " INTERNALDATE");
1261:          first = false;
1262:         }
1263:
1264:         // if we're not fetching all headers, fetch individual headers
1265:         String[] hdrs = null;
1266:         if (!allHeaders) {
1267:          hdrs = fp.getHeaderNames();
1268:          if (hdrs.length > 0) {
1269:                 if (!first)
1270:                  command.append(" ");
1271:                 command.append(createHeaderCommand(hdrs, isRev1));
1272:          }
1273:         }
1274:
1275:         /*
1276:          * Add any additional extension fetch items.
1277:          */
1278:         for (int i = 0; i < fitems.length; i++) {
1279:          if (fp.contains(fitems[i].getFetchProfileItem())) {
1280:                 if (command.length() != 0)
1281:                  command.append(" ");
1282:                 command.append(fitems[i].getName());
1283:          }
1284:         }
1285:
1286:         Utility.Condition condition =
1287:          new IMAPMessage.FetchProfileCondition(fp, fitems);
1288:
1289: // Acquire the Folder's MessageCacheLock.
1290: synchronized (messageCacheLock) {
1291:
1292:          // check again to make sure folder is still open
1293:          checkOpened();
1294:
1295:          // Apply the test, and get the sequence-number set for
1296:          // the messages that need to be prefetched.
1297:          MessageSet[] msgsets = Utility.toMessageSetSorted(msgs, condition);
1298:
1299:          if (msgsets == null)
1300:                 // We already have what we need.
1301:                 return;
1302:
1303:          Response[] r = null;
1304:          // to collect non-FETCH responses & unsolicited FETCH FLAG responses
1305:          List<Response> v = new ArrayList<>();
1306:          try {
1307:                 r = getProtocol().fetch(msgsets, command.toString());
1308:          } catch (ConnectionException cex) {
1309:                 throw new FolderClosedException(this, cex.getMessage());
1310:          } catch (CommandFailedException cfx) {
1311:                 // Ignore these, as per RFC 2180
1312:          } catch (ProtocolException pex) {
1313:                 throw new MessagingException(pex.getMessage(), pex);
1314:          }
1315:
1316:          if (r == null)
1317:                 return;
1318:         
1319:          for (int i = 0; i < r.length; i++) {
1320:                 if (r[i] == null)
1321:                  continue;
1322:                 if (!(r[i] instanceof FetchResponse)) {
1323:                  v.add(r[i]); // Unsolicited Non-FETCH response
1324:                  continue;
1325:                 }
1326:
1327:                 // Got a FetchResponse.
1328:                 FetchResponse f = (FetchResponse)r[i];
1329:                 // Get the corresponding message.
1330:                 IMAPMessage msg = getMessageBySeqNumber(f.getNumber());
1331:
1332:                 int count = f.getItemCount();
1333:                 boolean unsolicitedFlags = false;
1334:
1335:                 for (int j = 0; j < count; j++) {
1336:                  Item item = f.getItem(j);
1337:                  // Check for the FLAGS item
1338:                  if (item instanceof Flags &&
1339:                          (!fp.contains(FetchProfile.Item.FLAGS) ||
1340:                                 msg == null)) {
1341:                         // Ok, Unsolicited FLAGS update.
1342:                         unsolicitedFlags = true;
1343:                  } else if (msg != null)
1344:                         msg.handleFetchItem(item, hdrs, allHeaders);
1345:                 }
1346:                 if (msg != null)
1347:                  msg.handleExtensionFetchItems(f.getExtensionItems());
1348:
1349:                 // If this response contains any unsolicited FLAGS
1350:                 // add it to the unsolicited response vector
1351:                 if (unsolicitedFlags)
1352:                  v.add(f);
1353:          }
1354:
1355:          // Dispatch any unsolicited responses
1356:          if (!v.isEmpty()) {
1357:                 Response[] responses = new Response[v.size()];
1358:                 v.toArray(responses);
1359:                 handleResponses(responses);
1360:          }
1361:
1362:         } // Release messageCacheLock
1363: }
1364:
1365: /**
1366: * Return the IMAP FETCH items to request in order to load
1367: * all the "envelope" data. Subclasses can override this
1368: * method to fetch more data when FetchProfile.Item.ENVELOPE
1369: * is requested.
1370: *
1371: * @return        the IMAP FETCH items to request
1372: * @since JavaMail 1.4.6
1373: */
1374: protected String getEnvelopeCommand() {
1375:         return IMAPMessage.EnvelopeCmd;
1376: }
1377:
1378: /**
1379: * Create a new IMAPMessage object to represent the given message number.
1380: * Subclasses of IMAPFolder may override this method to create a
1381: * subclass of IMAPMessage.
1382: *
1383: * @param        msgnum        the message sequence number
1384: * @return        the new IMAPMessage object
1385: * @since JavaMail 1.4.6
1386: */
1387: protected IMAPMessage newIMAPMessage(int msgnum) {
1388:         return new IMAPMessage(this, msgnum);
1389: }
1390:
1391: /**
1392: * Create the appropriate IMAP FETCH command items to fetch the
1393: * requested headers.
1394: */
1395: private String createHeaderCommand(String[] hdrs, boolean isRev1) {
1396:         StringBuilder sb;
1397:
1398:         if (isRev1)
1399:          sb = new StringBuilder("BODY.PEEK[HEADER.FIELDS (");
1400:         else
1401:          sb = new StringBuilder("RFC822.HEADER.LINES (");
1402:
1403:         for (int i = 0; i < hdrs.length; i++) {
1404:          if (i > 0)
1405:                 sb.append(" ");
1406:          sb.append(hdrs[i]);
1407:         }
1408:
1409:         if (isRev1)
1410:          sb.append(")]");
1411:         else
1412:          sb.append(")");
1413:         
1414:         return sb.toString();
1415: }
1416:
1417: /**
1418: * Set the specified flags for the given array of messages.
1419: */
1420: @Override
1421: public synchronized void setFlags(Message[] msgs, Flags flag, boolean value)
1422:                         throws MessagingException {
1423:         checkOpened();
1424:         checkFlags(flag); // validate flags
1425:
1426:         if (msgs.length == 0) // boundary condition
1427:          return;
1428:
1429:         synchronized(messageCacheLock) {
1430:          try {
1431:                 IMAPProtocol p = getProtocol();
1432:                 MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
1433:                 if (ms == null)
1434:                  throw new MessageRemovedException(
1435:                                         "Messages have been removed");
1436:                 p.storeFlags(ms, flag, value);
1437:          } catch (ConnectionException cex) {
1438:                 throw new FolderClosedException(this, cex.getMessage());
1439:          } catch (ProtocolException pex) {
1440:                 throw new MessagingException(pex.getMessage(), pex);
1441:          }
1442:         }
1443: }
1444:
1445: /**
1446: * Set the specified flags for the given range of message numbers.
1447: */
1448: @Override
1449: public synchronized void setFlags(int start, int end,
1450:                         Flags flag, boolean value) throws MessagingException {
1451:         checkOpened();
1452:         Message[] msgs = new Message[end - start + 1];
1453:         int i = 0;
1454:         for (int n = start; n <= end; n++)
1455:          msgs[i++] = getMessage(n);
1456:         setFlags(msgs, flag, value);
1457: }
1458:
1459: /**
1460: * Set the specified flags for the given array of message numbers.
1461: */
1462: @Override
1463: public synchronized void setFlags(int[] msgnums, Flags flag, boolean value)
1464:                         throws MessagingException {
1465:         checkOpened();
1466:         Message[] msgs = new Message[msgnums.length];
1467:         for (int i = 0; i < msgnums.length; i++)
1468:          msgs[i] = getMessage(msgnums[i]);
1469:         setFlags(msgs, flag, value);
1470: }
1471:
1472: /**
1473: * Close this folder.
1474: */
1475: @Override
1476: public synchronized void close(boolean expunge) throws MessagingException {
1477:         close(expunge, false);
1478: }
1479:
1480: /**
1481: * Close this folder without waiting for the server.
1482: *
1483: * @exception        MessagingException for failures
1484: */
1485: public synchronized void forceClose() throws MessagingException {
1486:         close(false, true);
1487: }
1488:
1489: /*
1490: * Common close method.
1491: */
1492: private void close(boolean expunge, boolean force)
1493:                                 throws MessagingException {
1494:         assert Thread.holdsLock(this);
1495:         synchronized(messageCacheLock) {
1496:          /*
1497:          * If we already know we're closed, this is illegal.
1498:          * Can't use checkOpened() because if we were forcibly
1499:          * closed asynchronously we just want to complete the
1500:          * closing here.
1501:          */
1502:          if (!opened && reallyClosed)
1503:                 throw new IllegalStateException(
1504:                  "This operation is not allowed on a closed folder"
1505:                 );
1506:
1507:          reallyClosed = true; // Ok, lets reset
1508:
1509:          // Maybe this folder is already closed, or maybe another
1510:          // thread which had the messageCacheLock earlier, found
1511:          // that our server connection is dead and cleaned up
1512:          // everything ..
1513:          if (!opened)
1514:                 return;
1515:
1516:          boolean reuseProtocol = true;
1517:          try {
1518:                 waitIfIdle();
1519:                 if (force) {
1520:                  logger.log(Level.FINE, "forcing folder {0} to close",
1521:                                                                  fullName);
1522:                  if (protocol != null)
1523:                         protocol.disconnect();
1524: } else if (((IMAPStore)store).isConnectionPoolFull()) {
1525:                  // If the connection pool is full, logout the connection
1526:                  logger.fine(
1527:                         "pool is full, not adding an Authenticated connection");
1528:
1529:                  // If the expunge flag is set, close the folder first.
1530:                  if (expunge && protocol != null)
1531:                         protocol.close();
1532:
1533:                  if (protocol != null)
1534:                         protocol.logout();
1535: } else {
1536:                  // If the expunge flag is set or we're open read-only we
1537:                  // can just close the folder, otherwise open it read-only
1538:                  // before closing, or unselect it if supported.
1539: if (!expunge && mode == READ_WRITE) {
1540: try {
1541:                          if (protocol != null &&
1542:                                  protocol.hasCapability("UNSELECT"))
1543:                                 protocol.unselect();
1544:                          else {
1545:                                 // Unselect isn't supported so we need to
1546:                                 // select a folder to cause this one to be
1547:                                 // deselected without expunging messages.
1548:                                 // We try to do that by reopening the current
1549:                                 // folder read-only. If the current folder
1550:                                 // was renamed out from under us, the EXAMINE
1551:                                 // might fail, but that's ok because it still
1552:                                 // leaves us with the folder deselected.
1553:                                 if (protocol != null) {
1554:                                  boolean selected = true;
1555:                                  try {
1556:                                         protocol.examine(fullName);
1557:                                         // success, folder still selected
1558:                                  } catch (CommandFailedException ex) {
1559:                                         // EXAMINE failed, folder is no
1560:                                         // longer selected
1561:                                         selected = false;
1562:                                  }
1563:                                  if (selected && protocol != null)
1564:                                         protocol.close();
1565:                                 }
1566:                          }
1567: } catch (ProtocolException pex2) {
1568:                          reuseProtocol = false;        // something went wrong
1569: }
1570: } else {
1571:                         if (protocol != null)
1572:                          protocol.close();
1573:                  }
1574: }
1575:          } catch (ProtocolException pex) {
1576:                 throw new MessagingException(pex.getMessage(), pex);
1577:          } finally {
1578:                 // cleanup if we haven't already
1579:                 if (opened)
1580:                  cleanup(reuseProtocol);
1581:          }
1582:         }
1583: }
1584:
1585: // NOTE: this method can currently be invoked from close() or
1586: // from handleResponses(). Both invocations are conditional,
1587: // based on the "opened" flag, so we are sure that multiple
1588: // Connection.CLOSED events are not generated. Also both
1589: // invocations are from within messageCacheLock-ed areas.
1590: private void cleanup(boolean returnToPool) {
1591:         assert Thread.holdsLock(messageCacheLock);
1592: releaseProtocol(returnToPool);
1593:         messageCache = null;
1594:         uidTable = null;
1595:         exists = false; // to force a recheck in exists().
1596:         attributes = null;
1597: opened = false;
1598:         idleState = RUNNING;        // just in case
1599:         messageCacheLock.notifyAll();        // wake up anyone waiting
1600:         notifyConnectionListeners(ConnectionEvent.CLOSED);
1601: }
1602:
1603: /**
1604: * Check whether this connection is really open.
1605: */
1606: @Override
1607: public synchronized boolean isOpen() {
1608:         synchronized(messageCacheLock) {
1609:          // Probe the connection to make sure its really open.
1610:          if (opened) {
1611:                 try {
1612:                  keepConnectionAlive(false);
1613:                 } catch (ProtocolException pex) { }
1614:          }
1615:         }
1616:
1617:         return opened;
1618: }
1619:
1620: /**
1621: * Return the permanent flags supported by the server.
1622: */
1623: @Override
1624: public synchronized Flags getPermanentFlags() {
1625:         if (permanentFlags == null)
1626:          return null;
1627:         return (Flags)(permanentFlags.clone());
1628: }
1629:
1630: /**
1631: * Get the total message count.
1632: */
1633: @Override
1634: public synchronized int getMessageCount() throws MessagingException {
1635:         synchronized (messageCacheLock) {
1636:          if (opened) {
1637:                 // Folder is open, we know what the total message count is ..
1638:                 // tickle the folder and store connections.
1639:                 try {
1640:                  keepConnectionAlive(true);
1641:                  return total;
1642:                 } catch (ConnectionException cex) {
1643:                  throw new FolderClosedException(this, cex.getMessage());
1644:                 } catch (ProtocolException pex) {
1645:                  throw new MessagingException(pex.getMessage(), pex);
1646:                 }
1647:          }
1648:         }
1649:
1650:         // If this folder is not yet open, we use STATUS to
1651:         // get the total message count
1652:         checkExists();
1653:         try {
1654:          Status status = getStatus();
1655:          return status.total;
1656:         } catch (BadCommandException bex) {
1657:          // doesn't support STATUS, probably vanilla IMAP4 ..
1658:          // lets try EXAMINE
1659:          IMAPProtocol p = null;
1660:
1661:          try {
1662:                 p = getStoreProtocol();        // XXX
1663:                 MailboxInfo minfo = p.examine(fullName);
1664:                 p.close();
1665:                 return minfo.total;
1666:          } catch (ProtocolException pex) {
1667:                 // Give up.
1668:                 throw new MessagingException(pex.getMessage(), pex);
1669:          } finally {
1670:                 releaseStoreProtocol(p);
1671:          }
1672:         } catch (ConnectionException cex) {
1673:          throw new StoreClosedException(store, cex.getMessage());
1674:         } catch (ProtocolException pex) {
1675:          throw new MessagingException(pex.getMessage(), pex);
1676:         }
1677: }
1678:
1679: /**
1680: * Get the new message count.
1681: */
1682: @Override
1683: public synchronized int getNewMessageCount() throws MessagingException {
1684:         synchronized (messageCacheLock) {
1685:          if (opened) {
1686:                 // Folder is open, we know what the new message count is ..
1687:                 // tickle the folder and store connections.
1688:                 try {
1689:                  keepConnectionAlive(true);
1690:                  return recent;
1691:                 } catch (ConnectionException cex) {
1692:                  throw new FolderClosedException(this, cex.getMessage());
1693:                 } catch (ProtocolException pex) {
1694:                  throw new MessagingException(pex.getMessage(), pex);
1695:                 }
1696:          }
1697:         }
1698:
1699:         // If this folder is not yet open, we use STATUS to
1700:         // get the new message count
1701:         checkExists();
1702:         try {
1703:          Status status = getStatus();
1704:          return status.recent;
1705:         } catch (BadCommandException bex) {
1706:          // doesn't support STATUS, probably vanilla IMAP4 ..
1707:          // lets try EXAMINE
1708:          IMAPProtocol p = null;
1709:
1710:          try {
1711:                 p = getStoreProtocol();        // XXX
1712:                 MailboxInfo minfo = p.examine(fullName);
1713:                 p.close();
1714:                 return minfo.recent;
1715:          } catch (ProtocolException pex) {
1716:                 // Give up.
1717:                 throw new MessagingException(pex.getMessage(), pex);
1718:          } finally {
1719:                 releaseStoreProtocol(p);
1720:          }
1721:         } catch (ConnectionException cex) {
1722:          throw new StoreClosedException(store, cex.getMessage());
1723:         } catch (ProtocolException pex) {
1724:          throw new MessagingException(pex.getMessage(), pex);
1725:         }
1726: }
1727:
1728: /**
1729: * Get the unread message count.
1730: */
1731: @Override
1732: public synchronized int getUnreadMessageCount()
1733:                         throws MessagingException {
1734:         if (!opened) {
1735:          checkExists();
1736:          // If this folder is not yet open, we use STATUS to
1737:          // get the unseen message count
1738:          try {
1739:                 Status status = getStatus();
1740:                 return status.unseen;
1741:          } catch (BadCommandException bex) {
1742:                 // doesn't support STATUS, probably vanilla IMAP4 ..
1743:                 // Could EXAMINE, SEARCH for UNREAD messages and
1744:                 // return the count .. bah, not worth it.
1745:                 return -1;
1746:          } catch (ConnectionException cex) {
1747:                 throw new StoreClosedException(store, cex.getMessage());
1748:          } catch (ProtocolException pex) {
1749:                 throw new MessagingException(pex.getMessage(), pex);
1750:          }
1751:         }
1752:
1753:         // if opened, issue server-side search for messages that do
1754:         // *not* have the SEEN flag.
1755:         Flags f = new Flags();
1756:         f.add(Flags.Flag.SEEN);
1757:         try {
1758:          synchronized(messageCacheLock) {
1759:                 int[] matches = getProtocol().search(new FlagTerm(f, false));
1760:                 return matches.length; // NOTE: 'matches' is never null
1761:          }
1762:         } catch (ConnectionException cex) {
1763:          throw new FolderClosedException(this, cex.getMessage());
1764:         } catch (ProtocolException pex) {
1765:          // Shouldn't happen
1766:          throw new MessagingException(pex.getMessage(), pex);
1767:         }
1768: }
1769:
1770: /**
1771: * Get the deleted message count.
1772: */
1773: @Override
1774: public synchronized int getDeletedMessageCount()
1775:                         throws MessagingException {
1776:         if (!opened) {
1777:          checkExists();
1778:          // no way to do this on closed folders
1779:          return -1;
1780:         }
1781:
1782:         // if opened, issue server-side search for messages that do
1783:         // have the DELETED flag.
1784:         Flags f = new Flags();
1785:         f.add(Flags.Flag.DELETED);
1786:         try {
1787:          synchronized(messageCacheLock) {
1788:                 int[] matches = getProtocol().search(new FlagTerm(f, true));
1789:                 return matches.length; // NOTE: 'matches' is never null
1790:          }
1791:         } catch (ConnectionException cex) {
1792:          throw new FolderClosedException(this, cex.getMessage());
1793:         } catch (ProtocolException pex) {
1794:          // Shouldn't happen
1795:          throw new MessagingException(pex.getMessage(), pex);
1796:         }
1797: }
1798:
1799: /*
1800: * Get results of STATUS command for this folder, checking cache first.
1801: * ASSERT: Must be called with this folder's synchronization lock held.
1802: * ASSERT: The folder must be closed.
1803: */
1804: private Status getStatus() throws ProtocolException {
1805:         int statusCacheTimeout = ((IMAPStore)store).getStatusCacheTimeout();
1806:
1807:         // if allowed to cache and our cache is still valid, return it
1808:         if (statusCacheTimeout > 0 && cachedStatus != null &&
1809:          System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout)
1810:          return cachedStatus;
1811:
1812: IMAPProtocol p = null;
1813:
1814:         try {
1815:          p = getStoreProtocol();        // XXX
1816:          Status s = p.status(fullName, null);
1817:          // if allowed to cache, do so
1818:          if (statusCacheTimeout > 0) {
1819:                 cachedStatus = s;
1820:                 cachedStatusTime = System.currentTimeMillis();
1821:          }
1822:          return s;
1823: } finally {
1824: releaseStoreProtocol(p);
1825: }
1826: }
1827:
1828: /**
1829: * Get the specified message.
1830: */
1831: @Override
1832: public synchronized Message getMessage(int msgnum)
1833:                 throws MessagingException {
1834:         checkOpened();
1835:         checkRange(msgnum);
1836:
1837:         return messageCache.getMessage(msgnum);
1838: }
1839:
1840: /**
1841: * {@inheritDoc}
1842: */
1843: @Override
1844: public synchronized Message[] getMessages() throws MessagingException {
1845:         /*
1846:          * Need to override Folder method to throw FolderClosedException
1847:          * instead of IllegalStateException if not really closed.
1848:          */
1849:         checkOpened();
1850:         int total = getMessageCount();
1851:         Message[] msgs = new Message[total];
1852:         for (int i = 1; i <= total; i++)
1853:          msgs[i - 1] = messageCache.getMessage(i);
1854:         return msgs;
1855: }
1856:
1857: /**
1858: * Append the given messages into this folder.
1859: */
1860: @Override
1861: public synchronized void appendMessages(Message[] msgs)
1862:                                 throws MessagingException {
1863:         checkExists(); // verify that self exists
1864:
1865:         // XXX - have to verify that messages are in a different
1866:         // store (if any) than target folder, otherwise could
1867:         // deadlock trying to fetch messages on the same connection
1868:         // we're using for the append.
1869:
1870:         int maxsize = ((IMAPStore)store).getAppendBufferSize();
1871:
1872:         for (int i = 0; i < msgs.length; i++) {
1873:          final Message m = msgs[i];
1874:          Date d = m.getReceivedDate(); // retain dates
1875:          if (d == null)
1876:                 d = m.getSentDate();
1877:          final Date dd = d;
1878:          final Flags f = m.getFlags();
1879:
1880:          final MessageLiteral mos;
1881:          try {
1882:                 // if we know the message is too big, don't buffer any of it
1883:                 mos = new MessageLiteral(m,
1884:                                 m.getSize() > maxsize ? 0 : maxsize);
1885:          } catch (IOException ex) {
1886:                 throw new MessagingException(
1887:                                 "IOException while appending messages", ex);
1888:          } catch (MessageRemovedException mrex) {
1889:                 continue; // just skip this expunged message
1890:          }
1891:
1892:          doCommand(new ProtocolCommand() {
1893:                 @Override
1894:                 public Object doCommand(IMAPProtocol p)
1895:                         throws ProtocolException {
1896:                  p.append(fullName, f, dd, mos);
1897:                  return null;
1898:                 }
1899:          });
1900:         }
1901: }
1902:
1903: /**
1904: * Append the given messages into this folder.
1905: * Return array of AppendUID objects containing
1906: * UIDs of these messages in the destination folder.
1907: * Each element of the returned array corresponds to
1908: * an element of the <code>msgs</code> array. A null
1909: * element means the server didn't return UID information
1910: * for the appended message. <p>
1911: *
1912: * Depends on the APPENDUID response code defined by the
1913: * UIDPLUS extension -
1914: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
1915: *
1916: * @param        msgs        the messages to append
1917: * @return                array of AppendUID objects
1918: * @exception        MessagingException for failures
1919: * @since        JavaMail 1.4
1920: */
1921: public synchronized AppendUID[] appendUIDMessages(Message[] msgs)
1922:                                 throws MessagingException {
1923:         checkExists(); // verify that self exists
1924:
1925:         // XXX - have to verify that messages are in a different
1926:         // store (if any) than target folder, otherwise could
1927:         // deadlock trying to fetch messages on the same connection
1928:         // we're using for the append.
1929:
1930:         int maxsize = ((IMAPStore)store).getAppendBufferSize();
1931:
1932:         AppendUID[] uids = new AppendUID[msgs.length];
1933:         for (int i = 0; i < msgs.length; i++) {
1934:          final Message m = msgs[i];
1935:          final MessageLiteral mos;
1936:
1937:          try {
1938:                 // if we know the message is too big, don't buffer any of it
1939:                 mos = new MessageLiteral(m,
1940:                                 m.getSize() > maxsize ? 0 : maxsize);
1941:          } catch (IOException ex) {
1942:                 throw new MessagingException(
1943:                                 "IOException while appending messages", ex);
1944:          } catch (MessageRemovedException mrex) {
1945:                 continue; // just skip this expunged message
1946:          }
1947:
1948:          Date d = m.getReceivedDate(); // retain dates
1949:          if (d == null)
1950:                 d = m.getSentDate();
1951:          final Date dd = d;
1952:          final Flags f = m.getFlags();
1953:          AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() {
1954:                 @Override
1955:                 public Object doCommand(IMAPProtocol p)
1956:                         throws ProtocolException {
1957:                  return p.appenduid(fullName, f, dd, mos);
1958:                 }
1959:          });
1960:          uids[i] = auid;
1961:         }
1962:         return uids;
1963: }
1964:
1965: /**
1966: * Append the given messages into this folder.
1967: * Return array of Message objects representing
1968: * the messages in the destination folder. Note
1969: * that the folder must be open.
1970: * Each element of the returned array corresponds to
1971: * an element of the <code>msgs</code> array. A null
1972: * element means the server didn't return UID information
1973: * for the appended message. <p>
1974: *
1975: * Depends on the APPENDUID response code defined by the
1976: * UIDPLUS extension -
1977: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
1978: *
1979: * @param        msgs        the messages to add
1980: * @return                the messages in this folder
1981: * @exception        MessagingException for failures
1982: * @since        JavaMail 1.4
1983: */
1984: public synchronized Message[] addMessages(Message[] msgs)
1985:                                 throws MessagingException {
1986:         checkOpened();
1987:         Message[] rmsgs = new MimeMessage[msgs.length];
1988:         AppendUID[] uids = appendUIDMessages(msgs);
1989:         for (int i = 0; i < uids.length; i++) {
1990:          AppendUID auid = uids[i];
1991:          if (auid != null) {
1992:                 if (auid.uidvalidity == uidvalidity) {
1993:                  try {
1994:                         rmsgs[i] = getMessageByUID(auid.uid);
1995:                  } catch (MessagingException mex) {
1996:                         // ignore errors at this stage
1997:                  }
1998:                 }
1999:          }
2000:         }
2001:         return rmsgs;
2002: }
2003:
2004: /**
2005: * Copy the specified messages from this folder, to the
2006: * specified destination.
2007: */
2008: @Override
2009: public synchronized void copyMessages(Message[] msgs, Folder folder)
2010:                         throws MessagingException {
2011:         copymoveMessages(msgs, folder, false);
2012: }
2013:
2014: /**
2015: * Copy the specified messages from this folder, to the
2016: * specified destination.
2017: * Return array of AppendUID objects containing
2018: * UIDs of these messages in the destination folder.
2019: * Each element of the returned array corresponds to
2020: * an element of the <code>msgs</code> array. A null
2021: * element means the server didn't return UID information
2022: * for the copied message. <p>
2023: *
2024: * Depends on the COPYUID response code defined by the
2025: * UIDPLUS extension -
2026: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
2027: *
2028: * @param        msgs        the messages to copy
2029: * @param        folder        the folder to copy the messages to
2030: * @return                array of AppendUID objects
2031: * @exception        MessagingException for failures
2032: * @since        JavaMail 1.5.1
2033: */
2034: public synchronized AppendUID[] copyUIDMessages(Message[] msgs,
2035:                         Folder folder) throws MessagingException {
2036:         return copymoveUIDMessages(msgs, folder, false);
2037: }
2038:
2039: /**
2040: * Move the specified messages from this folder, to the
2041: * specified destination.
2042: *
2043: * Depends on the MOVE extension
2044: * (<A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>).
2045: *
2046: * @param        msgs        the messages to move
2047: * @param        folder        the folder to move the messages to
2048: * @exception        MessagingException for failures
2049: *
2050: * @since        JavaMail 1.5.4
2051: */
2052: public synchronized void moveMessages(Message[] msgs, Folder folder)
2053:                         throws MessagingException {
2054:         copymoveMessages(msgs, folder, true);
2055: }
2056:
2057: /**
2058: * Move the specified messages from this folder, to the
2059: * specified destination.
2060: * Return array of AppendUID objects containing
2061: * UIDs of these messages in the destination folder.
2062: * Each element of the returned array corresponds to
2063: * an element of the <code>msgs</code> array. A null
2064: * element means the server didn't return UID information
2065: * for the moved message. <p>
2066: *
2067: * Depends on the MOVE extension
2068: * (<A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>)
2069: * and the COPYUID response code defined by the
2070: * UIDPLUS extension
2071: * (<A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>).
2072: *
2073: * @param        msgs        the messages to move
2074: * @param        folder        the folder to move the messages to
2075: * @return                array of AppendUID objects
2076: * @exception        MessagingException for failures
2077: * @since        JavaMail 1.5.4
2078: */
2079: public synchronized AppendUID[] moveUIDMessages(Message[] msgs,
2080:                         Folder folder) throws MessagingException {
2081:         return copymoveUIDMessages(msgs, folder, true);
2082: }
2083:
2084: /**
2085: * Copy or move the specified messages from this folder, to the
2086: * specified destination.
2087: *
2088: * @since        JavaMail 1.5.4
2089: */
2090: private synchronized void copymoveMessages(Message[] msgs, Folder folder,
2091:                         boolean move) throws MessagingException {
2092:         checkOpened();
2093:
2094:         if (msgs.length == 0) // boundary condition
2095:          return;
2096:
2097:         // If the destination belongs to our same store, optimize
2098:         if (folder.getStore() == store) {
2099:          synchronized(messageCacheLock) {
2100:                 try {
2101:                  IMAPProtocol p = getProtocol();
2102:                  MessageSet[] ms = Utility.toMessageSet(msgs, null);
2103:                  if (ms == null)
2104:                         throw new MessageRemovedException(
2105:                                         "Messages have been removed");
2106:                  if (move)
2107:                         p.move(ms, folder.getFullName());
2108:                  else
2109:                         p.copy(ms, folder.getFullName());
2110:                 } catch (CommandFailedException cfx) {
2111:                  if (cfx.getMessage().indexOf("TRYCREATE") != -1)
2112:                         throw new FolderNotFoundException(
2113: folder,
2114:                          folder.getFullName() + " does not exist"
2115:                          );
2116:                  else
2117:                         throw new MessagingException(cfx.getMessage(), cfx);
2118:                 } catch (ConnectionException cex) {
2119:                  throw new FolderClosedException(this, cex.getMessage());
2120:                 } catch (ProtocolException pex) {
2121:                  throw new MessagingException(pex.getMessage(), pex);
2122:                  }
2123:          }
2124:         } else // destination is a different store.
2125:          if (move)
2126:                 throw new MessagingException(
2127:                                         "Move between stores not supported");
2128:          else
2129:                 super.copyMessages(msgs, folder);
2130: }
2131:
2132: /**
2133: * Copy or move the specified messages from this folder, to the
2134: * specified destination.
2135: * Return array of AppendUID objects containing
2136: * UIDs of these messages in the destination folder.
2137: * Each element of the returned array corresponds to
2138: * an element of the <code>msgs</code> array. A null
2139: * element means the server didn't return UID information
2140: * for the copied message. <p>
2141: *
2142: * Depends on the COPYUID response code defined by the
2143: * UIDPLUS extension -
2144: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
2145: * Move depends on the MOVE extension -
2146: * <A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>.
2147: *
2148: * @param        msgs        the messages to copy
2149: * @param        folder        the folder to copy the messages to
2150: * @param        move        move instead of copy?
2151: * @return                array of AppendUID objects
2152: * @exception        MessagingException for failures
2153: * @since        JavaMail 1.5.4
2154: */
2155: private synchronized AppendUID[] copymoveUIDMessages(Message[] msgs,
2156:                         Folder folder, boolean move) throws MessagingException {
2157:         checkOpened();
2158:
2159:         if (msgs.length == 0) // boundary condition
2160:          return null;
2161:
2162:         // the destination must belong to our same store
2163:         if (folder.getStore() != store) // destination is a different store.
2164:          throw new MessagingException(
2165:                         move ?
2166:                         "can't moveUIDMessages to a different store" :
2167:                         "can't copyUIDMessages to a different store");
2168:
2169:         // call fetch to make sure we have all the UIDs
2170:         // necessary to interpret the COPYUID response
2171:         FetchProfile fp = new FetchProfile();
2172:         fp.add(UIDFolder.FetchProfileItem.UID);
2173:         fetch(msgs, fp);
2174:         // XXX - could pipeline the FETCH with the COPY/MOVE below
2175:
2176:         synchronized (messageCacheLock) {
2177:          try {
2178:                 IMAPProtocol p = getProtocol();
2179:                 // XXX - messages have to be from this Folder, who checks?
2180:                 MessageSet[] ms = Utility.toMessageSet(msgs, null);
2181:                 if (ms == null)
2182:                  throw new MessageRemovedException(
2183:                                  "Messages have been removed");
2184:                 CopyUID cuid;
2185:                 if (move)
2186:                  cuid = p.moveuid(ms, folder.getFullName());
2187:                 else
2188:                  cuid = p.copyuid(ms, folder.getFullName());
2189:
2190:                 /*
2191:                  * Correlate source UIDs with destination UIDs.
2192:                  * This won't be time or space efficient if there's
2193:                  * a lot of messages.
2194:                  *
2195:                  * In order to make sense of the returned UIDs, we need
2196:                  * the UIDs for every one of the original messages.
2197:                  * We fetch them above, to make sure we have them.
2198:                  * This is critical for MOVE since after the MOVE the
2199:                  * messages are gone/expunged.
2200:                  *
2201:                  * Assume the common case is that the messages are
2202:                  * in order by UID. Map the returned source
2203:                  * UIDs to their corresponding Message objects.
2204:                  * Step through the msgs array looking for the
2205:                  * Message object in the returned source message
2206:                  * list. Most commonly the source message (UID)
2207:                  * for the Nth original message will be in the Nth
2208:                  * position in the returned source message (UID)
2209:                  * list. Thus, the destination UID is in the Nth
2210:                  * position in the returned destination UID list.
2211:                  * But if the source message isn't where expected,
2212:                  * we have to search the entire source message
2213:                  * list, starting from where we expect it and
2214:                  * wrapping around until we've searched it all.
2215:                  * (Gmail will often return the lists in an unexpected order.)
2216:                  *
2217:                  * A possible optimization:
2218:                  * If the number of UIDs returned is the same as the
2219:                  * number of messages being copied/moved, we could
2220:                  * sort the source messages by message number, sort
2221:                  * the source and destination parallel arrays by source
2222:                  * UID, and the resulting message and destination UID
2223:                  * arrays will correspond.
2224:                  *
2225:                  * If the returned UID array size is different, some
2226:                  * message was expunged while we were trying to copy/move it.
2227:                  * This should be rare but would mean falling back to the
2228:                  * general algorithm.
2229:                  */
2230:                 long[] srcuids = UIDSet.toArray(cuid.src);
2231:                 long[] dstuids = UIDSet.toArray(cuid.dst);
2232:                 // map source UIDs to Message objects
2233:                 // XXX - could inline/optimize this
2234:                 Message[] srcmsgs = getMessagesByUID(srcuids);
2235:                 AppendUID[] result = new AppendUID[msgs.length];
2236:                 for (int i = 0; i < msgs.length; i++) {
2237:                  int j = i;
2238:                  do {
2239:                         if (msgs[i] == srcmsgs[j]) {
2240:                          result[i] = new AppendUID(
2241:                                          cuid.uidvalidity, dstuids[j]);
2242:                          break;
2243:                         }
2244:                         j++;
2245:                         if (j >= srcmsgs.length)
2246:                          j = 0;
2247:                  } while (j != i);
2248:                 }
2249:                 return result;
2250:          } catch (CommandFailedException cfx) {
2251:                 if (cfx.getMessage().indexOf("TRYCREATE") != -1)
2252:                  throw new FolderNotFoundException(
2253:                         folder,
2254:                         folder.getFullName() + " does not exist"
2255:                  );
2256:                 else
2257:                  throw new MessagingException(cfx.getMessage(), cfx);
2258:          } catch (ConnectionException cex) {
2259:                 throw new FolderClosedException(this, cex.getMessage());
2260:          } catch (ProtocolException pex) {
2261:                 throw new MessagingException(pex.getMessage(), pex);
2262:          }
2263:         }
2264: }
2265:
2266: /**
2267: * Expunge all messages marked as DELETED.
2268: */
2269: @Override
2270: public synchronized Message[] expunge() throws MessagingException {
2271:         return expunge(null);
2272: }
2273:
2274: /**
2275: * Expunge the indicated messages, which must have been marked as DELETED.
2276: *
2277: * Depends on the UIDPLUS extension -
2278: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
2279: *
2280: * @param        msgs        the messages to expunge
2281: * @return                the expunged messages
2282: * @exception        MessagingException for failures
2283: */
2284: public synchronized Message[] expunge(Message[] msgs)
2285:                                 throws MessagingException {
2286:         checkOpened();
2287:
2288:         if (msgs != null) {
2289:          // call fetch to make sure we have all the UIDs
2290:          FetchProfile fp = new FetchProfile();
2291:          fp.add(UIDFolder.FetchProfileItem.UID);
2292:          fetch(msgs, fp);
2293:         }
2294:
2295:         IMAPMessage[] rmsgs;
2296:         synchronized(messageCacheLock) {
2297:          doExpungeNotification = false; // We do this ourselves later
2298:          try {
2299:                 IMAPProtocol p = getProtocol();
2300:                 if (msgs != null)
2301:                  p.uidexpunge(Utility.toUIDSet(msgs));
2302:                 else
2303:                  p.expunge();
2304:          } catch (CommandFailedException cfx) {
2305:                 // expunge not allowed, perhaps due to a permission problem?
2306:                 if (mode != READ_WRITE)
2307:                  throw new IllegalStateException(
2308:                         "Cannot expunge READ_ONLY folder: " + fullName);
2309:                 else
2310:                  throw new MessagingException(cfx.getMessage(), cfx);
2311:          } catch (ConnectionException cex) {
2312:                 throw new FolderClosedException(this, cex.getMessage());
2313:          } catch (ProtocolException pex) {
2314:                 // Bad bad server ..
2315:                 throw new MessagingException(pex.getMessage(), pex);
2316:          } finally {
2317:                 doExpungeNotification = true;
2318:          }
2319:
2320:          // Cleanup expunged messages and sync messageCache with reality.
2321:          if (msgs != null)
2322:                 rmsgs = messageCache.removeExpungedMessages(msgs);
2323:          else
2324:                 rmsgs = messageCache.removeExpungedMessages();
2325:          if (uidTable != null) {
2326:                 for (int i = 0; i < rmsgs.length; i++) {
2327:                  IMAPMessage m = rmsgs[i];
2328:                  /* remove this message from the UIDTable */
2329:                  long uid = m.getUID();
2330:                  if (uid != -1)
2331:                         uidTable.remove(Long.valueOf(uid));
2332:                 }
2333:          }
2334:
2335:          // Update 'total'
2336:          total = messageCache.size();
2337:         }
2338:
2339:         // Notify listeners. This time its for real, guys.
2340:         if (rmsgs.length > 0)
2341:          notifyMessageRemovedListeners(true, rmsgs);
2342:         return rmsgs;
2343: }
2344:
2345: /**
2346: * Search whole folder for messages matching the given term.
2347: * If the property <code>mail.imap.throwsearchexception</code> is true,
2348: * and the search term is too complex for the IMAP protocol,
2349: * SearchException is thrown. Otherwise, if the search term is too
2350: * complex, <code>super.search</code> is called to do the search on
2351: * the client.
2352: *
2353: * @param        term        the search term
2354: * @return                the messages that match
2355: * @exception        SearchException if mail.imap.throwsearchexception is
2356: *                        true and the search is too complex for the IMAP protocol
2357: * @exception        MessagingException for other failures
2358: */
2359: @Override
2360: public synchronized Message[] search(SearchTerm term)
2361:                                 throws MessagingException {
2362:         checkOpened();
2363:
2364:         try {
2365:          Message[] matchMsgs = null;
2366:
2367:          synchronized(messageCacheLock) {
2368:                 int[] matches = getProtocol().search(term);
2369:                 if (matches != null)
2370:                  matchMsgs = getMessagesBySeqNumbers(matches);
2371:          }
2372:          return matchMsgs;
2373:
2374:         } catch (CommandFailedException cfx) {
2375:          // unsupported charset or search criterion
2376:          return super.search(term);
2377:         } catch (SearchException sex) {
2378:          // too complex for IMAP
2379:          if (((IMAPStore)store).throwSearchException())
2380:                 throw sex;
2381:          return super.search(term);
2382:         } catch (ConnectionException cex) {
2383:          throw new FolderClosedException(this, cex.getMessage());
2384:         } catch (ProtocolException pex) {
2385:          // bug in our IMAP layer ?
2386:          throw new MessagingException(pex.getMessage(), pex);
2387:         }
2388: }
2389:
2390: /**
2391: * Search the folder for messages matching the given term. Returns
2392: * array of matching messages. Returns an empty array if no matching
2393: * messages are found.
2394: */
2395: @Override
2396: public synchronized Message[] search(SearchTerm term, Message[] msgs)
2397:                         throws MessagingException {
2398:         checkOpened();
2399:
2400:         if (msgs.length == 0)
2401:          // need to return an empty array (not null!)
2402:          return msgs;
2403:
2404:         try {
2405:          Message[] matchMsgs = null;
2406:
2407:          synchronized(messageCacheLock) {
2408:                 IMAPProtocol p = getProtocol();
2409:                 MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
2410:                 if (ms == null)
2411:                  throw new MessageRemovedException(
2412:                                         "Messages have been removed");
2413:                 int[] matches = p.search(ms, term);
2414:                 if (matches != null)
2415:                  matchMsgs = getMessagesBySeqNumbers(matches);
2416:          }
2417:          return matchMsgs;
2418:
2419:         } catch (CommandFailedException cfx) {
2420:          // unsupported charset or search criterion
2421:          return super.search(term, msgs);
2422:         } catch (SearchException sex) {
2423:          // too complex for IMAP
2424:          return super.search(term, msgs);
2425:         } catch (ConnectionException cex) {
2426:          throw new FolderClosedException(this, cex.getMessage());
2427:         } catch (ProtocolException pex) {
2428:          // bug in our IMAP layer ?
2429:          throw new MessagingException(pex.getMessage(), pex);
2430:         }
2431: }
2432:
2433: /**
2434: * Sort the messages in the folder according to the sort criteria.
2435: * The messages are returned in the sorted order, but the order of
2436: * the messages in the folder is not changed. <p>
2437: *
2438: * Depends on the SORT extension -
2439: * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
2440: *
2441: * @param        term        the SortTerms
2442: * @return                the messages in sorted order
2443: * @exception        MessagingException for failures
2444: * @since JavaMail 1.4.4
2445: */
2446: public synchronized Message[] getSortedMessages(SortTerm[] term)
2447:                                 throws MessagingException {
2448:         return getSortedMessages(term, null);
2449: }
2450:
2451: /**
2452: * Sort the messages in the folder according to the sort criteria.
2453: * The messages are returned in the sorted order, but the order of
2454: * the messages in the folder is not changed. Only messages matching
2455: * the search criteria are considered. <p>
2456: *
2457: * Depends on the SORT extension -
2458: * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
2459: *
2460: * @param        term        the SortTerms
2461: * @param        sterm        the SearchTerm
2462: * @return                the messages in sorted order
2463: * @exception        MessagingException for failures
2464: * @since JavaMail 1.4.4
2465: */
2466: public synchronized Message[] getSortedMessages(SortTerm[] term,
2467:                                 SearchTerm sterm) throws MessagingException {
2468:         checkOpened();
2469:
2470:         try {
2471:          Message[] matchMsgs = null;
2472:
2473:          synchronized(messageCacheLock) {
2474:                 int[] matches = getProtocol().sort(term, sterm);
2475:                 if (matches != null)
2476:                  matchMsgs = getMessagesBySeqNumbers(matches);
2477:          }
2478:          return matchMsgs;
2479:
2480:         } catch (CommandFailedException cfx) {
2481:          // unsupported charset or search criterion
2482:          throw new MessagingException(cfx.getMessage(), cfx);
2483:         } catch (SearchException sex) {
2484:          // too complex for IMAP
2485:          throw new MessagingException(sex.getMessage(), sex);
2486:         } catch (ConnectionException cex) {
2487:          throw new FolderClosedException(this, cex.getMessage());
2488:         } catch (ProtocolException pex) {
2489:          // bug in our IMAP layer ?
2490:          throw new MessagingException(pex.getMessage(), pex);
2491:         }
2492: }
2493:
2494: /*
2495: * Override Folder method to keep track of whether we have any
2496: * message count listeners. Normally we won't have any, so we
2497: * can avoid creating message objects to pass to the notify
2498: * method. It's too hard to keep track of when all listeners
2499: * are removed, and that's a rare case, so we don't try.
2500: */
2501: @Override
2502: public synchronized void addMessageCountListener(MessageCountListener l) {
2503:         super.addMessageCountListener(l);
2504:         hasMessageCountListener = true;
2505: }
2506:
2507: /***********************************************************
2508: *                UIDFolder interface methods
2509: **********************************************************/
2510:
2511: /**
2512: * Returns the UIDValidity for this folder.
2513: */
2514: @Override
2515: public synchronized long getUIDValidity() throws MessagingException {
2516:         if (opened) // we already have this information
2517:          return uidvalidity;
2518:
2519: IMAPProtocol p = null;
2520: Status status = null;
2521:
2522:         try {
2523:          p = getStoreProtocol();        // XXX
2524:          String[] item = { "UIDVALIDITY" };
2525:          status = p.status(fullName, item);
2526:         } catch (BadCommandException bex) {
2527:          // Probably a RFC1730 server
2528:          throw new MessagingException("Cannot obtain UIDValidity", bex);
2529:         } catch (ConnectionException cex) {
2530: // Oops, the store or folder died on us.
2531: throwClosedException(cex);
2532:         } catch (ProtocolException pex) {
2533:          throw new MessagingException(pex.getMessage(), pex);
2534:         } finally {
2535: releaseStoreProtocol(p);
2536: }
2537:
2538:         if (status == null)
2539:          throw new MessagingException("Cannot obtain UIDValidity");
2540:         return status.uidvalidity;
2541: }
2542:
2543: /**
2544: * Returns the predicted UID that will be assigned to the
2545: * next message that is appended to this folder.
2546: * If the folder is closed, the STATUS command is used to
2547: * retrieve this value. If the folder is open, the value
2548: * returned from the SELECT or EXAMINE command is returned.
2549: * Note that messages may have been appended to the folder
2550: * while it was open and thus this value may be out of
2551: * date. <p>
2552: *
2553: * Servers implementing RFC2060 likely won't return this value
2554: * when a folder is opened. Servers implementing RFC3501
2555: * should return this value when a folder is opened. <p>
2556: *
2557: * @return        the UIDNEXT value, or -1 if unknown
2558: * @exception        MessagingException for failures
2559: * @since        JavaMail 1.3.3
2560: */
2561: @Override
2562: public synchronized long getUIDNext() throws MessagingException {
2563:         if (opened) // we already have this information
2564:          return uidnext;
2565:
2566: IMAPProtocol p = null;
2567: Status status = null;
2568:
2569:         try {
2570:          p = getStoreProtocol();        // XXX
2571:          String[] item = { "UIDNEXT" };
2572:          status = p.status(fullName, item);
2573:         } catch (BadCommandException bex) {
2574:          // Probably a RFC1730 server
2575:          throw new MessagingException("Cannot obtain UIDNext", bex);
2576:         } catch (ConnectionException cex) {
2577: // Oops, the store or folder died on us.
2578: throwClosedException(cex);
2579:         } catch (ProtocolException pex) {
2580:          throw new MessagingException(pex.getMessage(), pex);
2581:         } finally {
2582: releaseStoreProtocol(p);
2583: }
2584:
2585:         if (status == null)
2586:          throw new MessagingException("Cannot obtain UIDNext");
2587:         return status.uidnext;
2588: }
2589:
2590: /**
2591: * Get the Message corresponding to the given UID.
2592: * If no such message exists, <code> null </code> is returned.
2593: */
2594: @Override
2595: public synchronized Message getMessageByUID(long uid)
2596:                         throws MessagingException {
2597:         checkOpened(); // insure folder is open
2598:
2599:         IMAPMessage m = null;
2600:
2601:         try {
2602:          synchronized(messageCacheLock) {
2603:                 Long l = Long.valueOf(uid);
2604:
2605:                 if (uidTable != null) {
2606:                  // Check in uidTable
2607:                  m = uidTable.get(l);
2608:                  if (m != null) // found it
2609:                         return m;
2610:                 } else
2611:                  uidTable = new Hashtable<>();
2612:
2613:                 // Check with the server
2614:                 // Issue UID FETCH command
2615:                 getProtocol().fetchSequenceNumber(uid);
2616:
2617:                 if (uidTable != null) {
2618:                  // Check in uidTable
2619:                  m = uidTable.get(l);
2620:                  if (m != null) // found it
2621:                         return m;
2622:                 }
2623:          }
2624:         } catch(ConnectionException cex) {
2625:          throw new FolderClosedException(this, cex.getMessage());
2626:         } catch (ProtocolException pex) {
2627:          throw new MessagingException(pex.getMessage(), pex);
2628:         }
2629:
2630:         return m;
2631: }
2632:
2633: /**
2634: * Get the Messages specified by the given range. <p>
2635: * Returns Message objects for all valid messages in this range.
2636: * Returns an empty array if no messages are found.
2637: */
2638: @Override
2639: public synchronized Message[] getMessagesByUID(long start, long end)
2640:                         throws MessagingException {
2641:         checkOpened(); // insure that folder is open
2642:
2643:         Message[] msgs; // array of messages to be returned
2644:
2645:         try {
2646:          synchronized(messageCacheLock) {
2647:                 if (uidTable == null)
2648:                  uidTable = new Hashtable<>();
2649:
2650:                 // Issue UID FETCH for given range
2651:                 long[] ua = getProtocol().fetchSequenceNumbers(start, end);
2652:
2653:                 List<Message> ma = new ArrayList<>();
2654:                 // NOTE: Below must be within messageCacheLock region
2655:                 for (int i = 0; i < ua.length; i++) {
2656:                  Message m = uidTable.get(Long.valueOf(ua[i]));
2657:                  if (m != null) // found it
2658:                         ma.add(m);
2659:                 }
2660:                 msgs = ma.toArray(new Message[ma.size()]);
2661:          }
2662:         } catch(ConnectionException cex) {
2663:          throw new FolderClosedException(this, cex.getMessage());
2664:         } catch (ProtocolException pex) {
2665:          throw new MessagingException(pex.getMessage(), pex);
2666:         }
2667:
2668:         return msgs;
2669: }
2670:
2671: /**
2672: * Get the Messages specified by the given array. <p>
2673: *
2674: * <code>uids.length()</code> elements are returned.
2675: * If any UID in the array is invalid, a <code>null</code> entry
2676: * is returned for that element.
2677: */
2678: @Override
2679: public synchronized Message[] getMessagesByUID(long[] uids)
2680:                         throws MessagingException {
2681:         checkOpened(); // insure that folder is open
2682:
2683:         try {
2684:          synchronized(messageCacheLock) {
2685:                 long[] unavailUids = uids;
2686:                 if (uidTable != null) {
2687:                  // to collect unavailable UIDs
2688:                  List<Long> v = new ArrayList<>();
2689:                  for (long uid : uids) {
2690:                         if (!uidTable.containsKey(uid)) {
2691:                          // This UID has not been loaded yet.
2692:                          v.add(uid);
2693:                         }
2694:                  }
2695:
2696:                  int vsize = v.size();
2697:                  unavailUids = new long[vsize];
2698:                  for (int i = 0; i < vsize; i++) {
2699:                         unavailUids[i] = v.get(i);
2700:                  }
2701:                 } else
2702:                  uidTable = new Hashtable<>();
2703:
2704:                 if (unavailUids.length > 0) {
2705:                  // Issue UID FETCH request for given uids
2706:                  getProtocol().fetchSequenceNumbers(unavailUids);
2707:                 }
2708:
2709:                 // Return array of size = uids.length
2710:                 Message[] msgs = new Message[uids.length];
2711:                 for (int i = 0; i < uids.length; i++)
2712:                  msgs[i] = (Message)uidTable.get(Long.valueOf(uids[i]));
2713:                 return msgs;
2714:          }
2715:         } catch(ConnectionException cex) {
2716:          throw new FolderClosedException(this, cex.getMessage());
2717:         } catch (ProtocolException pex) {
2718:          throw new MessagingException(pex.getMessage(), pex);
2719:         }
2720: }
2721:
2722: /**
2723: * Get the UID for the specified message.
2724: */
2725: @Override
2726: public synchronized long getUID(Message message)
2727:                         throws MessagingException {
2728:         if (message.getFolder() != this)
2729:          throw new NoSuchElementException(
2730:                 "Message does not belong to this folder");
2731:
2732:         checkOpened(); // insure that folder is open
2733:
2734:         if (!(message instanceof IMAPMessage))
2735:          throw new MessagingException("message is not an IMAPMessage");
2736:         IMAPMessage m = (IMAPMessage)message;
2737:         // If the message already knows its UID, great ..
2738:         long uid;
2739:         if ((uid = m.getUID()) != -1)
2740:          return uid;
2741:
2742:         synchronized(messageCacheLock) { // Acquire Lock
2743:          try {
2744:                 IMAPProtocol p = getProtocol();
2745:                 m.checkExpunged(); // insure that message is not expunged
2746:                 UID u = p.fetchUID(m.getSequenceNumber());
2747:
2748:                 if (u != null) {
2749:                  uid = u.uid;
2750:                  m.setUID(uid); // set message's UID
2751:
2752:                  // insert this message into uidTable
2753:                  if (uidTable == null)
2754:                         uidTable = new Hashtable<>();
2755:                  uidTable.put(Long.valueOf(uid), m);
2756:                 }
2757:          } catch (ConnectionException cex) {
2758:                 throw new FolderClosedException(this, cex.getMessage());
2759:          } catch (ProtocolException pex) {
2760:                 throw new MessagingException(pex.getMessage(), pex);
2761:          }
2762:         }
2763:
2764:         return uid;
2765: }
2766:
2767: /**
2768: * Servers that support the UIDPLUS extension
2769: * (<A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>)
2770: * may indicate that this folder does not support persistent UIDs;
2771: * that is, UIDVALIDITY will be different each time the folder is
2772: * opened. Only valid when the folder is open.
2773: *
2774: * @return        true if UIDs are not sticky
2775: * @exception        MessagingException for failures
2776: * @exception        IllegalStateException        if the folder isn't open
2777: * @see "RFC 4315"
2778: * @since        JavaMail 1.6.0
2779: */
2780: public synchronized boolean getUIDNotSticky() throws MessagingException {
2781:         checkOpened();
2782:         return uidNotSticky;
2783: }
2784:
2785: /**
2786: * Get or create Message objects for the UIDs.
2787: */
2788: private Message[] createMessagesForUIDs(long[] uids) {
2789:         IMAPMessage[] msgs = new IMAPMessage[uids.length];
2790:         for (int i = 0; i < uids.length; i++) {
2791:          IMAPMessage m = null;
2792:          if (uidTable != null)
2793:                 m = uidTable.get(Long.valueOf(uids[i]));
2794:          if (m == null) {
2795:                 // fake it, we don't know what message this really is
2796:                 m = newIMAPMessage(-1);        // no sequence number
2797:                 m.setUID(uids[i]);
2798:                 m.setExpunged(true);
2799:          }
2800:          msgs[i++] = m;
2801:         }
2802:         return msgs;
2803: }
2804:
2805: /**
2806: * Returns the HIGHESTMODSEQ for this folder.
2807: *
2808: * @return        the HIGHESTMODSEQ value
2809: * @exception        MessagingException for failures
2810: * @see "RFC 4551"
2811: * @since        JavaMail 1.5.1
2812: */
2813: public synchronized long getHighestModSeq() throws MessagingException {
2814:         if (opened) // we already have this information
2815:          return highestmodseq;
2816:
2817: IMAPProtocol p = null;
2818: Status status = null;
2819:
2820:         try {
2821:          p = getStoreProtocol();        // XXX
2822:          if (!p.hasCapability("CONDSTORE"))
2823:                 throw new BadCommandException("CONDSTORE not supported");
2824:          String[] item = { "HIGHESTMODSEQ" };
2825:          status = p.status(fullName, item);
2826:         } catch (BadCommandException bex) {
2827:          // Probably a RFC1730 server
2828:          throw new MessagingException("Cannot obtain HIGHESTMODSEQ", bex);
2829:         } catch (ConnectionException cex) {
2830: // Oops, the store or folder died on us.
2831: throwClosedException(cex);
2832:         } catch (ProtocolException pex) {
2833:          throw new MessagingException(pex.getMessage(), pex);
2834:         } finally {
2835: releaseStoreProtocol(p);
2836: }
2837:
2838:         if (status == null)
2839:          throw new MessagingException("Cannot obtain HIGHESTMODSEQ");
2840:         return status.highestmodseq;
2841: }
2842:
2843: /**
2844: * Get the messages that have been changed since the given MODSEQ value.
2845: * Also, prefetch the flags for the messages. <p>
2846: *
2847: * The server must support the CONDSTORE extension.
2848: *
2849: * @param        start        the first message number
2850: * @param        end        the last message number
2851: * @param        modseq        the MODSEQ value
2852: * @return        the changed messages
2853: * @exception        MessagingException for failures
2854: * @see "RFC 4551"
2855: * @since        JavaMail 1.5.1
2856: */
2857: public synchronized Message[] getMessagesByUIDChangedSince(
2858:                                 long start, long end, long modseq)
2859:                                 throws MessagingException {
2860:         checkOpened(); // insure that folder is open
2861:
2862:         try {
2863:          synchronized (messageCacheLock) {
2864:                 IMAPProtocol p = getProtocol();
2865:                 if (!p.hasCapability("CONDSTORE"))
2866:                  throw new BadCommandException("CONDSTORE not supported");
2867:
2868:                 // Issue FETCH for given range
2869:                 int[] nums = p.uidfetchChangedSince(start, end, modseq);
2870:                 return getMessagesBySeqNumbers(nums);
2871:          }
2872:         } catch(ConnectionException cex) {
2873:          throw new FolderClosedException(this, cex.getMessage());
2874:         } catch (ProtocolException pex) {
2875:          throw new MessagingException(pex.getMessage(), pex);
2876:         }
2877: }
2878:
2879: /**
2880: * Get the quotas for the quotaroot associated with this
2881: * folder. Note that many folders may have the same quotaroot.
2882: * Quotas are controlled on the basis of a quotaroot, not
2883: * (necessarily) a folder. The relationship between folders
2884: * and quotaroots depends on the IMAP server. Some servers
2885: * might implement a single quotaroot for all folders owned by
2886: * a user. Other servers might implement a separate quotaroot
2887: * for each folder. A single folder can even have multiple
2888: * quotaroots, perhaps controlling quotas for different
2889: * resources.
2890: *
2891: * @return        array of Quota objects for the quotaroots associated with
2892: *                this folder
2893: * @exception MessagingException        if the server doesn't support the
2894: *                                        QUOTA extension
2895: */
2896: public Quota[] getQuota() throws MessagingException {
2897:         return (Quota[])doOptionalCommand("QUOTA not supported",
2898:          new ProtocolCommand() {
2899:                 @Override
2900:                 public Object doCommand(IMAPProtocol p)
2901:                         throws ProtocolException {
2902:                  return p.getQuotaRoot(fullName);
2903:                 }
2904:          });
2905: }
2906:
2907: /**
2908: * Set the quotas for the quotaroot specified in the quota argument.
2909: * Typically this will be one of the quotaroots associated with this
2910: * folder, as obtained from the <code>getQuota</code> method, but it
2911: * need not be.
2912: *
2913: * @param        quota        the quota to set
2914: * @exception MessagingException        if the server doesn't support the
2915: *                                        QUOTA extension
2916: */
2917: public void setQuota(final Quota quota) throws MessagingException {
2918:         doOptionalCommand("QUOTA not supported",
2919:          new ProtocolCommand() {
2920:                 @Override
2921:                 public Object doCommand(IMAPProtocol p)
2922:                         throws ProtocolException {
2923:                  p.setQuota(quota);
2924:                  return null;
2925:                 }
2926:          });
2927: }
2928:
2929: /**
2930: * Get the access control list entries for this folder.
2931: *
2932: * @return        array of access control list entries
2933: * @exception MessagingException        if the server doesn't support the
2934: *                                        ACL extension
2935: */
2936: public ACL[] getACL() throws MessagingException {
2937:         return (ACL[])doOptionalCommand("ACL not supported",
2938:          new ProtocolCommand() {
2939:                 @Override
2940:                 public Object doCommand(IMAPProtocol p)
2941:                         throws ProtocolException {
2942:                  return p.getACL(fullName);
2943:                 }
2944:          });
2945: }
2946:
2947: /**
2948: * Add an access control list entry to the access control list
2949: * for this folder.
2950: *
2951: * @param        acl        the access control list entry to add
2952: * @exception MessagingException        if the server doesn't support the
2953: *                                        ACL extension
2954: */
2955: public void addACL(ACL acl) throws MessagingException {
2956:         setACL(acl, '\0');
2957: }
2958:
2959: /**
2960: * Remove any access control list entry for the given identifier
2961: * from the access control list for this folder.
2962: *
2963: * @param        name        the identifier for which to remove all ACL entries
2964: * @exception MessagingException        if the server doesn't support the
2965: *                                        ACL extension
2966: */
2967: public void removeACL(final String name) throws MessagingException {
2968:         doOptionalCommand("ACL not supported",
2969:          new ProtocolCommand() {
2970:                 @Override
2971:                 public Object doCommand(IMAPProtocol p)
2972:                         throws ProtocolException {
2973:                  p.deleteACL(fullName, name);
2974:                  return null;
2975:                 }
2976:          });
2977: }
2978:
2979: /**
2980: * Add the rights specified in the ACL to the entry for the
2981: * identifier specified in the ACL. If an entry for the identifier
2982: * doesn't already exist, add one.
2983: *
2984: * @param        acl        the identifer and rights to add
2985: * @exception MessagingException        if the server doesn't support the
2986: *                                        ACL extension
2987: */
2988: public void addRights(ACL acl) throws MessagingException {
2989:         setACL(acl, '+');
2990: }
2991:
2992: /**
2993: * Remove the rights specified in the ACL from the entry for the
2994: * identifier specified in the ACL.
2995: *
2996: * @param        acl        the identifer and rights to remove
2997: * @exception MessagingException        if the server doesn't support the
2998: *                                        ACL extension
2999: */
3000: public void removeRights(ACL acl) throws MessagingException {
3001:         setACL(acl, '-');
3002: }
3003:
3004: /**
3005: * Get all the rights that may be allowed to the given identifier.
3006: * Rights are grouped per RFC 2086 and each group is returned as an
3007: * element of the array. The first element of the array is the set
3008: * of rights that are always granted to the identifier. Later
3009: * elements are rights that may be optionally granted to the
3010: * identifier. <p>
3011: *
3012: * Note that this method lists the rights that it is possible to
3013: * assign to the given identifier, <em>not</em> the rights that are
3014: * actually granted to the given identifier. For the latter, see
3015: * the <code>getACL</code> method.
3016: *
3017: * @param        name        the identifier to list rights for
3018: * @return                array of Rights objects representing possible
3019: *                        rights for the identifier
3020: * @exception MessagingException        if the server doesn't support the
3021: *                                        ACL extension
3022: */
3023: public Rights[] listRights(final String name) throws MessagingException {
3024:         return (Rights[])doOptionalCommand("ACL not supported",
3025:          new ProtocolCommand() {
3026:                 @Override
3027:                 public Object doCommand(IMAPProtocol p)
3028:                         throws ProtocolException {
3029:                  return p.listRights(fullName, name);
3030:                 }
3031:          });
3032: }
3033:
3034: /**
3035: * Get the rights allowed to the currently authenticated user.
3036: *
3037: * @return        the rights granted to the current user
3038: * @exception MessagingException        if the server doesn't support the
3039: *                                        ACL extension
3040: */
3041: public Rights myRights() throws MessagingException {
3042:         return (Rights)doOptionalCommand("ACL not supported",
3043:          new ProtocolCommand() {
3044:                 @Override
3045:                 public Object doCommand(IMAPProtocol p)
3046:                         throws ProtocolException {
3047:                  return p.myRights(fullName);
3048:                 }
3049:          });
3050: }
3051:
3052: private void setACL(final ACL acl, final char mod)
3053:                                 throws MessagingException {
3054:         doOptionalCommand("ACL not supported",
3055:          new ProtocolCommand() {
3056:                 @Override
3057:                 public Object doCommand(IMAPProtocol p)
3058:                         throws ProtocolException {
3059:                  p.setACL(fullName, mod, acl);
3060:                  return null;
3061:                 }
3062:          });
3063: }
3064:
3065: /**
3066: * Get the attributes that the IMAP server returns with the
3067: * LIST response.
3068: *
3069: * @return        array of attributes for this folder
3070: * @exception        MessagingException for failures
3071: * @since        JavaMail 1.3.3
3072: */
3073: public synchronized String[] getAttributes() throws MessagingException {
3074:         checkExists();
3075:         if (attributes == null)
3076:          exists();                // do a LIST to set the attributes
3077:         return attributes == null ? new String[0] : attributes.clone();
3078: }
3079:
3080: /**
3081: * Use the IMAP IDLE command (see
3082: * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>),
3083: * if supported by the server, to enter idle mode so that the server
3084: * can send unsolicited notifications of new messages arriving, etc.
3085: * without the need for the client to constantly poll the server.
3086: * Use an appropriate listener to be notified of new messages or
3087: * other events. When another thread (e.g., the listener thread)
3088: * needs to issue an IMAP comand for this folder, the idle mode will
3089: * be terminated and this method will return. Typically the caller
3090: * will invoke this method in a loop. <p>
3091: *
3092: * The mail.imap.minidletime property enforces a minimum delay
3093: * before returning from this method, to ensure that other threads
3094: * have a chance to issue commands before the caller invokes this
3095: * method again. The default delay is 10 milliseconds.
3096: *
3097: * @exception MessagingException        if the server doesn't support the
3098: *                                        IDLE extension
3099: * @exception IllegalStateException        if the folder isn't open
3100: *
3101: * @since        JavaMail 1.4.1
3102: */
3103: public void idle() throws MessagingException {
3104:         idle(false);
3105: }
3106:
3107: /**
3108: * Like {@link #idle}, but if <code>once</code> is true, abort the
3109: * IDLE command after the first notification, to allow the caller
3110: * to process any notification synchronously.
3111: *
3112: * @param        once        only do one notification?
3113: * @exception MessagingException        if the server doesn't support the
3114: *                                        IDLE extension
3115: * @exception IllegalStateException        if the folder isn't open
3116: *
3117: * @since        JavaMail 1.4.3
3118: */
3119: public void idle(boolean once) throws MessagingException {
3120:         synchronized (this) {
3121:          /*
3122:          * We can't support the idle method if we're using SocketChannels
3123:          * because SocketChannels don't allow simultaneous read and write.
3124:          * If we're blocked in a read waiting for IDLE responses, we can't
3125:          * send the DONE message to abort the IDLE. Sigh.
3126:          * XXX - We could do select here too, like IdleManager, instead
3127:          * of blocking in read, but that's more complicated.
3128:          */
3129:          if (protocol != null && protocol.getChannel() != null)
3130:                 throw new MessagingException(
3131:                          "idle method not supported with SocketChannels");
3132:         }
3133:         if (!startIdle(null))
3134:          return;
3135:
3136:         /*
3137:          * We gave up the folder lock so that other threads
3138:          * can get into the folder far enough to see that we're
3139:          * in IDLE and abort the IDLE.
3140:          *
3141:          * Now we read responses from the IDLE command, especially
3142:          * including unsolicited notifications from the server.
3143:          * We don't hold the messageCacheLock while reading because
3144:          * it protects the idleState and other threads need to be
3145:          * able to examine the state.
3146:          *
3147:          * The messageCacheLock is held in handleIdle while processing
3148:          * the responses so that we can update the number of messages
3149:          * in the folder (for example).
3150:          */
3151:         for (;;) {
3152:          if (!handleIdle(once))
3153:                 break;
3154:         }
3155:
3156:         /*
3157:          * Enforce a minimum delay to give time to threads
3158:          * processing the responses that came in while we
3159:          * were idle.
3160:          */
3161:         int minidle = ((IMAPStore)store).getMinIdleTime();
3162:         if (minidle > 0) {
3163:          try {
3164:                 Thread.sleep(minidle);
3165:          } catch (InterruptedException ex) {
3166:                 // restore the interrupted state, which callers might depend on
3167:                 Thread.currentThread().interrupt();
3168:          }
3169:         }
3170: }
3171:
3172: /**
3173: * Start the IDLE command and put this folder into the IDLE state.
3174: * IDLE processing is done later in handleIdle(), e.g., called from
3175: * the IdleManager.
3176: *
3177: * @return        true if IDLE started, false otherwise
3178: * @exception MessagingException        if the server doesn't support the
3179: *                                        IDLE extension
3180: * @exception IllegalStateException        if the folder isn't open
3181: * @since        JavaMail 1.5.2
3182: */
3183: boolean startIdle(final IdleManager im) throws MessagingException {
3184:         // ASSERT: Must NOT be called with this folder's
3185:         // synchronization lock held.
3186:         assert !Thread.holdsLock(this);
3187:         synchronized(this) {
3188:          checkOpened();
3189:          if (im != null && idleManager != null && im != idleManager)
3190:                 throw new MessagingException(
3191:                  "Folder already being watched by another IdleManager");
3192:          Boolean started = (Boolean)doOptionalCommand("IDLE not supported",
3193:                 new ProtocolCommand() {
3194:                  @Override
3195:                  public Object doCommand(IMAPProtocol p)
3196:                          throws ProtocolException {
3197:                         // if the IdleManager is already watching this folder,
3198:                         // there's nothing to do here
3199:                         if (idleState == IDLE &&
3200:                                 im != null && im == idleManager)
3201:                          return Boolean.TRUE;        // already watching it
3202:                         if (idleState == RUNNING) {
3203:                          p.idleStart();
3204:                          logger.finest("startIdle: set to IDLE");
3205:                          idleState = IDLE;
3206:                          idleManager = im;
3207:                          return Boolean.TRUE;
3208:                         } else {
3209:                          // some other thread must be running the IDLE
3210:                          // command, we'll just wait for it to finish
3211:                          // without aborting it ourselves
3212:                          try {
3213:                                 // give up lock and wait to be not idle
3214:                                 messageCacheLock.wait();
3215:                          } catch (InterruptedException ex) {
3216:                                 // restore the interrupted state, which callers
3217:                                 // might depend on
3218:                                 Thread.currentThread().interrupt();
3219:                          }
3220:                          return Boolean.FALSE;
3221:                         }
3222:                  }
3223:                 });
3224:          logger.log(Level.FINEST, "startIdle: return {0}", started);
3225:          return started.booleanValue();
3226:         }
3227: }
3228:
3229: /**
3230: * Read a response from the server while we're in the IDLE state.
3231: * We hold the messageCacheLock while processing the
3232: * responses so that we can update the number of messages
3233: * in the folder (for example).
3234: *
3235: * @param        once        only do one notification?
3236: * @return        true if we should look for more IDLE responses,
3237: *                false if IDLE is done
3238: * @exception MessagingException        for errors
3239: * @since        JavaMail 1.5.2
3240: */
3241: boolean handleIdle(boolean once) throws MessagingException {
3242:         Response r = null;
3243:         do {
3244:          r = protocol.readIdleResponse();
3245:          try {
3246:                 synchronized (messageCacheLock) {
3247:                  if (r.isBYE() && r.isSynthetic() && idleState == IDLE) {
3248:                         /*
3249:                          * If it was a timeout and no bytes were transferred
3250:                          * we ignore it and go back and read again.
3251:                          * If the I/O was otherwise interrupted, and no
3252:                          * bytes were transferred, we take it as a request
3253:                          * to abort the IDLE.
3254:                          */
3255:                         Exception ex = r.getException();
3256:                         if (ex instanceof InterruptedIOException &&
3257:                          ((InterruptedIOException)ex).
3258:                                  bytesTransferred == 0) {
3259:                          if (ex instanceof SocketTimeoutException) {
3260:                                 logger.finest(
3261:                                  "handleIdle: ignoring socket timeout");
3262:                                 r = null;        // repeat do/while loop
3263:                          } else {
3264:                                 logger.finest("handleIdle: interrupting IDLE");
3265:                                 IdleManager im = idleManager;
3266:                                 if (im != null) {
3267:                                  logger.finest(
3268:                                  "handleIdle: request IdleManager to abort");
3269:                                  im.requestAbort(this);
3270:                                 } else {
3271:                                  logger.finest("handleIdle: abort IDLE");
3272:                                  protocol.idleAbort();
3273:                                  idleState = ABORTING;
3274:                                 }
3275:                                 // normally will exit the do/while loop
3276:                          }
3277:                          continue;
3278:                         }
3279:                  }
3280:                  boolean done = true;
3281:                  try {
3282:                         if (protocol == null ||
3283:                                 !protocol.processIdleResponse(r))
3284:                          return false;        // done
3285:                         done = false;
3286:                  } finally {
3287:                         if (done) {
3288:                          logger.finest("handleIdle: set to RUNNING");
3289:                          idleState = RUNNING;
3290:                          idleManager = null;
3291:                          messageCacheLock.notifyAll();
3292:                         }
3293:                  }
3294:                  if (once) {
3295:                         if (idleState == IDLE) {
3296:                          try {
3297:                                 protocol.idleAbort();
3298:                          } catch (Exception ex) {
3299:                                 // ignore any failures, still have to abort.
3300:                                 // connection failures will be detected above
3301:                                 // in the call to readIdleResponse.
3302:                          }
3303:                          idleState = ABORTING;
3304:                         }
3305:                  }
3306:                 }
3307:          } catch (ConnectionException cex) {
3308:                 // Oops, the folder died on us.
3309:                 throw new FolderClosedException(this, cex.getMessage());
3310:          } catch (ProtocolException pex) {
3311:                 throw new MessagingException(pex.getMessage(), pex);
3312:          }
3313:         // keep processing responses already in our buffer
3314:         } while (r == null || protocol.hasResponse());
3315:         return true;
3316: }
3317:
3318: /*
3319: * If an IDLE command is in progress, abort it if necessary,
3320: * and wait until it completes.
3321: * ASSERT: Must be called with the message cache lock held.
3322: */
3323: void waitIfIdle() throws ProtocolException {
3324:         assert Thread.holdsLock(messageCacheLock);
3325:         while (idleState != RUNNING) {
3326:          if (idleState == IDLE) {
3327:                 IdleManager im = idleManager;
3328:                 if (im != null) {
3329:                  logger.finest("waitIfIdle: request IdleManager to abort");
3330:                  im.requestAbort(this);
3331:                 } else {
3332:                  logger.finest("waitIfIdle: abort IDLE");
3333:                  protocol.idleAbort();
3334:                  idleState = ABORTING;
3335:                 }
3336:          } else
3337:                 logger.log(Level.FINEST, "waitIfIdle: idleState {0}", idleState);
3338:          try {
3339:                 // give up lock and wait to be not idle
3340:                 if (logger.isLoggable(Level.FINEST))
3341:                  logger.finest("waitIfIdle: wait to be not idle: " +
3342:                                  Thread.currentThread());
3343:                 messageCacheLock.wait();
3344:                 if (logger.isLoggable(Level.FINEST))
3345:                  logger.finest("waitIfIdle: wait done, idleState " +
3346:                                  idleState + ": " + Thread.currentThread());
3347:          } catch (InterruptedException ex) {
3348:                 // restore the interrupted state, which callers might depend on
3349:                 Thread.currentThread().interrupt();
3350:                 // If someone is trying to interrupt us we can't keep going
3351:                 // around the loop waiting for IDLE to complete, but we can't
3352:                 // just return because callers expect the idleState to be
3353:                 // RUNNING when we return. Throwing this exception seems
3354:                 // like the best choice.
3355:                 throw new ProtocolException("Interrupted waitIfIdle", ex);
3356:          }
3357:         }
3358: }
3359:
3360: /*
3361: * Send the DONE command that aborts the IDLE; used by IdleManager.
3362: */
3363: void idleAbort() {
3364:         synchronized (messageCacheLock) {
3365:          if (idleState == IDLE && protocol != null) {
3366:                 protocol.idleAbort();
3367:                 idleState = ABORTING;
3368:          }
3369:         }
3370: }
3371:
3372: /*
3373: * Send the DONE command that aborts the IDLE and wait for the response;
3374: * used by IdleManager.
3375: */
3376: void idleAbortWait() {
3377:         synchronized (messageCacheLock) {
3378:          if (idleState == IDLE && protocol != null) {
3379:                 protocol.idleAbort();
3380:                 idleState = ABORTING;
3381:
3382:                 // read responses until OK or connection failure
3383:                 try {
3384:                  for (;;) {
3385:                         if (!handleIdle(false))
3386:                          break;
3387:                  }
3388:                 } catch (Exception ex) {
3389:                  // assume it's a connection failure; nothing more to do
3390:                  logger.log(Level.FINEST, "Exception in idleAbortWait", ex);
3391:                 }
3392:                 logger.finest("IDLE aborted");
3393:          }
3394:         }
3395: }
3396:
3397: /**
3398: * Return the SocketChannel for this connection, if any, for use
3399: * in IdleManager.
3400: */
3401: SocketChannel getChannel() {
3402:         return protocol != null ? protocol.getChannel() : null;
3403: }
3404:
3405: /**
3406: * Send the IMAP ID command (if supported by the server) and return
3407: * the result from the server. The ID command identfies the client
3408: * to the server and returns information about the server to the client.
3409: * See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
3410: * The returned Map is unmodifiable.
3411: *
3412: * @param        clientParams        a Map of keys and values identifying the client
3413: * @return                        a Map of keys and values identifying the server
3414: * @exception MessagingException        if the server doesn't support the
3415: *                                        ID extension
3416: * @since        JavaMail 1.5.1
3417: */
3418: @SuppressWarnings("unchecked")
3419: public Map<String, String> id(final Map<String, String> clientParams)
3420:                                 throws MessagingException {
3421:         checkOpened();
3422:         return (Map<String,String>)doOptionalCommand("ID not supported",
3423:          new ProtocolCommand() {
3424:                 @Override
3425:                 public Object doCommand(IMAPProtocol p)
3426:                         throws ProtocolException {
3427:                  return p.id(clientParams);
3428:                 }
3429:          });
3430: }
3431:
3432: /**
3433: * Use the IMAP STATUS command to get the indicated item.
3434: * The STATUS item may be a standard item such as "RECENT" or "UNSEEN",
3435: * or may be a server-specific item.
3436: * The folder must be closed. If the item is not found, or the
3437: * folder is open, -1 is returned.
3438: *
3439: * @param        item        the STATUS item to fetch
3440: * @return                the value of the STATUS item, or -1
3441: * @exception MessagingException        for errors
3442: * @since        JavaMail 1.5.2
3443: */
3444: public synchronized long getStatusItem(String item)
3445:                                 throws MessagingException {
3446:         if (!opened) {
3447:          checkExists();
3448:
3449:          IMAPProtocol p = null;
3450:          Status status = null;
3451:          try {
3452:                 p = getStoreProtocol();        // XXX
3453:                 String[] items = { item };
3454:                 status = p.status(fullName, items);
3455:                 return status != null ? status.getItem(item) : -1;
3456:          } catch (BadCommandException bex) {
3457:                 // doesn't support STATUS, probably vanilla IMAP4 ..
3458:                 // Could EXAMINE, SEARCH for UNREAD messages and
3459:                 // return the count .. bah, not worth it.
3460:                 return -1;
3461:          } catch (ConnectionException cex) {
3462:                 throw new StoreClosedException(store, cex.getMessage());
3463:          } catch (ProtocolException pex) {
3464:                 throw new MessagingException(pex.getMessage(), pex);
3465:          } finally {
3466:                 releaseStoreProtocol(p);
3467:          }
3468:         }
3469:         return -1;
3470: }
3471:
3472: /**
3473: * The response handler. This is the callback routine that is
3474: * invoked by the protocol layer.
3475: */
3476: /*
3477: * ASSERT: This method must be called only when holding the
3478: * messageCacheLock.
3479: * ASSERT: This method must *not* invoke any other method that
3480: * might grab the 'folder' lock or 'message' lock (i.e., any
3481: * synchronized methods on IMAPFolder or IMAPMessage)
3482: * since that will result in violating the locking hierarchy.
3483: */
3484: @Override
3485: public void handleResponse(Response r) {
3486:         assert Thread.holdsLock(messageCacheLock);
3487:
3488:         /*
3489:          * First, delegate possible ALERT or notification to the Store.
3490:          */
3491:         if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
3492:          ((IMAPStore)store).handleResponseCode(r);
3493:
3494:         /*
3495:          * Now check whether this is a BYE or OK response and
3496:          * handle appropriately.
3497:          */
3498:         if (r.isBYE()) {
3499:          if (opened)                // XXX - accessed without holding folder lock
3500:                 cleanup(false);
3501:          return;
3502:         } else if (r.isOK()) {
3503:          // HIGHESTMODSEQ can be updated on any OK response
3504:          r.skipSpaces();
3505:          if (r.readByte() == '[') {
3506:                 String s = r.readAtom();
3507:                 if (s.equalsIgnoreCase("HIGHESTMODSEQ"))
3508:                  highestmodseq = r.readLong();
3509:          }
3510:          r.reset();
3511:          return;
3512:         } else if (!r.isUnTagged()) {
3513:          return;        // might be a continuation for IDLE
3514:         }
3515:
3516:         /* Now check whether this is an IMAP specific response */
3517:         if (!(r instanceof IMAPResponse)) {
3518:          // Probably a bug in our code !
3519:          // XXX - should be an assert
3520:          logger.fine("UNEXPECTED RESPONSE : " + r.toString());
3521:          return;
3522:         }
3523:
3524:         IMAPResponse ir = (IMAPResponse)r;
3525:
3526:         if (ir.keyEquals("EXISTS")) { // EXISTS
3527:          int exists = ir.getNumber();
3528:          if (exists <= realTotal)
3529:                 // Could be the EXISTS following EXPUNGE, ignore 'em
3530:                 return;
3531:         
3532:          int count = exists - realTotal; // number of new messages
3533:          Message[] msgs = new Message[count];
3534:
3535:          // Add 'count' new IMAPMessage objects into the messageCache
3536:          messageCache.addMessages(count, realTotal + 1);
3537:          int oldtotal = total;        // used in loop below
3538:          realTotal += count;
3539:          total += count;
3540:
3541:          // avoid instantiating Message objects if no listeners.
3542:          if (hasMessageCountListener) {
3543:                 for (int i = 0; i < count; i++)
3544:                  msgs[i] = messageCache.getMessage(++oldtotal);
3545:
3546:                 // Notify listeners.
3547:                 notifyMessageAddedListeners(msgs);
3548:          }
3549:
3550:         } else if (ir.keyEquals("EXPUNGE")) {
3551:          // EXPUNGE response.
3552:
3553:          int seqnum = ir.getNumber();
3554:          if (seqnum > realTotal) {
3555:                 // A message was expunged that we never knew about.
3556:                 // Exchange will do this. Just ignore the notification.
3557:                 // (Alternatively, we could simulate an EXISTS for the
3558:                 // expunged message before expunging it.)
3559:                 return;
3560:          }
3561:          Message[] msgs = null;
3562:          if (doExpungeNotification && hasMessageCountListener) {
3563:                 // save the Message object first; can't look it
3564:                 // up after it's expunged
3565:                 msgs = new Message[] { getMessageBySeqNumber(seqnum) };
3566:                 if (msgs[0] == null)        // XXX - should never happen
3567:                  msgs = null;
3568:          }
3569:
3570:          messageCache.expungeMessage(seqnum);
3571:
3572:          // decrement 'realTotal'; but leave 'total' unchanged
3573:          realTotal--;
3574:
3575:          if (msgs != null)        // Do the notification here.
3576:                 notifyMessageRemovedListeners(false, msgs);
3577:
3578:         } else if (ir.keyEquals("VANISHED")) {
3579:          // after the folder is opened with QRESYNC, a VANISHED response
3580:          // without the (EARLIER) tag is used instead of the EXPUNGE
3581:          // response
3582:
3583:          // "VANISHED" SP ["(EARLIER)"] SP known-uids
3584:          String[] s = ir.readAtomStringList();
3585:          if (s == null) {        // no (EARLIER)
3586:                 String uids = ir.readAtom();
3587:                 UIDSet[] uidset = UIDSet.parseUIDSets(uids);
3588:                 // assume no duplicates and no UIDs out of range
3589:                 realTotal -= UIDSet.size(uidset);
3590:                 long[] luid = UIDSet.toArray(uidset);
3591:                 Message[] msgs = createMessagesForUIDs(luid);
3592:                 for (Message m : msgs) {
3593:                  if (m.getMessageNumber() > 0)
3594:                         messageCache.expungeMessage(m.getMessageNumber());
3595:                 }
3596:                 if (doExpungeNotification && hasMessageCountListener) {
3597:                  notifyMessageRemovedListeners(true, msgs);
3598:                 }
3599:          } // else if (EARLIER), ignore
3600:
3601:         } else if (ir.keyEquals("FETCH")) {
3602:          assert ir instanceof FetchResponse : "!ir instanceof FetchResponse";
3603:          Message msg = processFetchResponse((FetchResponse)ir);
3604:          if (msg != null)
3605:                 notifyMessageChangedListeners(
3606:                         MessageChangedEvent.FLAGS_CHANGED, msg);
3607:
3608:         } else if (ir.keyEquals("RECENT")) {
3609:          // update 'recent'
3610:          recent = ir.getNumber();
3611:         }
3612: }
3613:
3614: /**
3615: * Process a FETCH response.
3616: * The only unsolicited FETCH response that makes sense
3617: * to me (for now) is FLAGS updates, which might include
3618: * UID and MODSEQ information. Ignore any other junk.
3619: */
3620: private Message processFetchResponse(FetchResponse fr) {
3621:         IMAPMessage msg = getMessageBySeqNumber(fr.getNumber());
3622:         if (msg != null) {        // should always be true
3623:          boolean notify = false;
3624:
3625:          UID uid = fr.getItem(UID.class);
3626:          if (uid != null && msg.getUID() != uid.uid) {
3627:                 msg.setUID(uid.uid);
3628:                 if (uidTable == null)
3629:                  uidTable = new Hashtable<>();
3630:                 uidTable.put(Long.valueOf(uid.uid), msg);
3631:                 notify = true;
3632:          }
3633:
3634:          MODSEQ modseq = fr.getItem(MODSEQ.class);
3635:          if (modseq != null && msg._getModSeq() != modseq.modseq) {
3636:                 msg.setModSeq(modseq.modseq);
3637:                 /*
3638:                  * XXX - should we update the folder's HIGHESTMODSEQ or not?
3639:                  *
3640:                 if (modseq.modseq > highestmodseq)
3641:                  highestmodseq = modseq.modseq;
3642:                  */
3643:                 notify = true;
3644:          }
3645:
3646:          // Get FLAGS response, if present
3647:          FLAGS flags = fr.getItem(FLAGS.class);
3648:          if (flags != null) {
3649:                 msg._setFlags(flags);        // assume flags changed
3650:                 notify = true;
3651:          }
3652:
3653:          // handle any extension items that might've changed
3654:          // XXX - no notifications associated with extension items
3655:          msg.handleExtensionFetchItems(fr.getExtensionItems());
3656:
3657:          if (!notify)
3658:                 msg = null;
3659:         }
3660:         return msg;
3661: }
3662:
3663: /**
3664: * Handle the given array of Responses.
3665: *
3666: * ASSERT: This method must be called only when holding the
3667: *         messageCacheLock
3668: */
3669: void handleResponses(Response[] r) {
3670:         for (int i = 0; i < r.length; i++) {
3671:          if (r[i] != null)
3672:                 handleResponse(r[i]);
3673:         }
3674: }
3675:
3676: /**
3677: * Get this folder's Store's protocol connection.
3678: *
3679: * When acquiring a store protocol object, it is important to
3680: * use the following steps:
3681: *
3682: * <blockquote><pre>
3683: * IMAPProtocol p = null;
3684: * try {
3685: * p = getStoreProtocol();
3686: * // perform the command
3687: * } catch (WhateverException ex) {
3688: * // handle it
3689: * } finally {
3690: * releaseStoreProtocol(p);
3691: * }
3692: * </pre></blockquote>
3693: *
3694: * ASSERT: Must be called with this folder's synchronization lock held.
3695: *
3696: * @return        the IMAPProtocol for the Store's connection
3697: * @exception        ProtocolException for protocol errors
3698: */
3699: protected synchronized IMAPProtocol getStoreProtocol()
3700: throws ProtocolException {
3701:         connectionPoolLogger.fine("getStoreProtocol() borrowing a connection");
3702:         return ((IMAPStore)store).getFolderStoreProtocol();
3703: }
3704:
3705: /**
3706: * Throw the appropriate 'closed' exception.
3707: *
3708: * @param        cex        the ConnectionException
3709: * @exception        FolderClosedException if the folder is closed
3710: * @exception        StoreClosedException if the store is closed
3711: */
3712: protected synchronized void throwClosedException(ConnectionException cex)
3713: throws FolderClosedException, StoreClosedException {
3714:         // If it's the folder's protocol object, throw a FolderClosedException;
3715:         // otherwise, throw a StoreClosedException.
3716:         // If a command has failed because the connection is closed,
3717:         // the folder will have already been forced closed by the
3718:         // time we get here and our protocol object will have been
3719:         // released, so if we no longer have a protocol object we base
3720:         // this decision on whether we *think* the folder is open.
3721:         if ((protocol != null && cex.getProtocol() == protocol) ||
3722:                 (protocol == null && !reallyClosed))
3723: throw new FolderClosedException(this, cex.getMessage());
3724: else
3725: throw new StoreClosedException(store, cex.getMessage());
3726: }
3727:
3728: /**
3729: * Return the IMAPProtocol object for this folder. <p>
3730: *
3731: * This method will block if necessary to wait for an IDLE
3732: * command to finish.
3733: *
3734: * @return        the IMAPProtocol object used when the folder is open
3735: * @exception        ProtocolException for protocol errors
3736: */
3737: protected IMAPProtocol getProtocol() throws ProtocolException {
3738:         assert Thread.holdsLock(messageCacheLock);
3739:         waitIfIdle();
3740:         // if we no longer have a protocol object after waiting, it probably
3741:         // means the connection has been closed due to a communnication error,
3742:         // or possibly because the folder has been closed
3743:         if (protocol == null)
3744:          throw new ConnectionException("Connection closed");
3745: return protocol;
3746: }
3747:
3748: /**
3749: * A simple interface for user-defined IMAP protocol commands.
3750: */
3751: public static interface ProtocolCommand {
3752:         /**
3753:          * Execute the user-defined command using the supplied IMAPProtocol
3754:          * object.
3755:          *
3756:          * @param        protocol        the IMAPProtocol for the connection
3757:          * @return                        the results of the command
3758:          * @exception        ProtocolException for protocol errors
3759:          */
3760:         public Object doCommand(IMAPProtocol protocol) throws ProtocolException;
3761: }
3762:
3763: /**
3764: * Execute a user-supplied IMAP command. The command is executed
3765: * in the appropriate context with the necessary locks held and
3766: * using the appropriate <code>IMAPProtocol</code> object. <p>
3767: *
3768: * This method returns whatever the <code>ProtocolCommand</code>
3769: * object's <code>doCommand</code> method returns. If the
3770: * <code>doCommand</code> method throws a <code>ConnectionException</code>
3771: * it is translated into a <code>StoreClosedException</code> or
3772: * <code>FolderClosedException</code> as appropriate. If the
3773: * <code>doCommand</code> method throws a <code>ProtocolException</code>
3774: * it is translated into a <code>MessagingException</code>. <p>
3775: *
3776: * The following example shows how to execute the IMAP NOOP command.
3777: * Executing more complex IMAP commands requires intimate knowledge
3778: * of the <code>org.eclipse.angus.mail.iap</code> and
3779: * <code>org.eclipse.angus.mail.imap.protocol</code> packages, best acquired by
3780: * reading the source code.
3781: *
3782: * <blockquote><pre>
3783: * import org.eclipse.angus.mail.iap.*;
3784: * import org.eclipse.angus.mail.imap.*;
3785: * import org.eclipse.angus.mail.imap.protocol.*;
3786: *
3787: * ...
3788: *
3789: * IMAPFolder f = (IMAPFolder)folder;
3790: * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
3791: *        public Object doCommand(IMAPProtocol p)
3792: *                        throws ProtocolException {
3793: *         p.simpleCommand("NOOP", null);
3794: *         return null;
3795: *        }
3796: * });
3797: * </pre></blockquote>
3798: * <p>
3799: *
3800: * Here's a more complex example showing how to use the proposed
3801: * IMAP SORT extension:
3802: *
3803: * <blockquote><pre>
3804: * import org.eclipse.angus.mail.iap.*;
3805: * import org.eclipse.angus.mail.imap.*;
3806: * import org.eclipse.angus.mail.imap.protocol.*;
3807: *
3808: * ...
3809: *
3810: * IMAPFolder f = (IMAPFolder)folder;
3811: * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
3812: *        public Object doCommand(IMAPProtocol p)
3813: *                        throws ProtocolException {
3814: *         // Issue command
3815: *         Argument args = new Argument();
3816: *         Argument list = new Argument();
3817: *         list.writeString("SUBJECT");
3818: *         args.writeArgument(list);
3819: *         args.writeString("UTF-8");
3820: *         args.writeString("ALL");
3821: *         Response[] r = p.command("SORT", args);
3822: *         Response response = r[r.length-1];
3823: *
3824: *         // Grab response
3825: *         Vector v = new Vector();
3826: *         if (response.isOK()) { // command succesful
3827: *                for (int i = 0, len = r.length; i < len; i++) {
3828: *                 if (!(r[i] instanceof IMAPResponse))
3829: *                        continue;
3830: *
3831: *                 IMAPResponse ir = (IMAPResponse)r[i];
3832: *                 if (ir.keyEquals("SORT")) {
3833: *                        String num;
3834: *                        while ((num = ir.readAtomString()) != null)
3835: *                         System.out.println(num);
3836: *                        r[i] = null;
3837: *                 }
3838: *                }
3839: *         }
3840: *
3841: *         // dispatch remaining untagged responses
3842: *         p.notifyResponseHandlers(r);
3843: *         p.handleResult(response);
3844: *
3845: *         return null;
3846: *        }
3847: * });
3848: * </pre></blockquote>
3849: *
3850: * @param        cmd        the protocol command
3851: * @return                the result of the command
3852: * @exception        MessagingException for failures
3853: */
3854: public Object doCommand(ProtocolCommand cmd) throws MessagingException {
3855:         try {
3856:          return doProtocolCommand(cmd);
3857:         } catch (ConnectionException cex) {
3858: // Oops, the store or folder died on us.
3859: throwClosedException(cex);
3860:         } catch (ProtocolException pex) {
3861:          throw new MessagingException(pex.getMessage(), pex);
3862:         }
3863:         return null;
3864: }
3865:
3866: public Object doOptionalCommand(String err, ProtocolCommand cmd)
3867:                                 throws MessagingException {
3868:         try {
3869:          return doProtocolCommand(cmd);
3870:         } catch (BadCommandException bex) {
3871:          throw new MessagingException(err, bex);
3872:         } catch (ConnectionException cex) {
3873: // Oops, the store or folder died on us.
3874: throwClosedException(cex);
3875:         } catch (ProtocolException pex) {
3876:          throw new MessagingException(pex.getMessage(), pex);
3877:         }
3878:         return null;
3879: }
3880:
3881: public Object doCommandIgnoreFailure(ProtocolCommand cmd)
3882:                                 throws MessagingException {
3883:         try {
3884:          return doProtocolCommand(cmd);
3885:         } catch (CommandFailedException cfx) {
3886:          return null;
3887:         } catch (ConnectionException cex) {
3888: // Oops, the store or folder died on us.
3889: throwClosedException(cex);
3890:         } catch (ProtocolException pex) {
3891:          throw new MessagingException(pex.getMessage(), pex);
3892:         }
3893:         return null;
3894: }
3895:
3896: protected synchronized Object doProtocolCommand(ProtocolCommand cmd)
3897:                                 throws ProtocolException {
3898:         /*
3899:          * Check whether we have a protocol object, not whether we're
3900:          * opened, to allow use of the exsting protocol object in the
3901:          * open method before the state is changed to "opened".
3902:          */
3903:         if (protocol != null) {
3904:          synchronized (messageCacheLock) {
3905:                 return cmd.doCommand(getProtocol());
3906:          }
3907:         }
3908:
3909:         // only get here if using store's connection
3910:         IMAPProtocol p = null;
3911:
3912:         try {
3913: p = getStoreProtocol();
3914:          return cmd.doCommand(p);
3915:         } finally {
3916:          releaseStoreProtocol(p);
3917:         }
3918: }
3919:
3920: /**
3921: * Release the store protocol object. If we borrowed a protocol
3922: * object from the connection pool, give it back. If we used our
3923: * own protocol object, nothing to do.
3924: *
3925: * ASSERT: Must be called with this folder's synchronization lock held.
3926: *
3927: * @param        p        the IMAPProtocol object
3928: */
3929: protected synchronized void releaseStoreProtocol(IMAPProtocol p) {
3930: if (p != protocol)
3931: ((IMAPStore)store).releaseFolderStoreProtocol(p);
3932:         else {
3933:          // XXX - should never happen
3934:          logger.fine("releasing our protocol as store protocol?");
3935:         }
3936: }
3937:
3938: /**
3939: * Release the protocol object.
3940: *
3941: * ASSERT: This method must be called only when holding the
3942: * messageCacheLock
3943: *
3944: * @param        returnToPool        return the protocol object to the pool?
3945: */
3946: protected void releaseProtocol(boolean returnToPool) {
3947: if (protocol != null) {
3948: protocol.removeResponseHandler(this);
3949:
3950: if (returnToPool)
3951: ((IMAPStore)store).releaseProtocol(this, protocol);
3952: else {
3953:                 protocol.disconnect();        // make sure it's disconnected
3954: ((IMAPStore)store).releaseProtocol(this, null);
3955:          }
3956:          protocol = null;
3957: }
3958: }
3959:
3960: /**
3961: * Issue a noop command for the connection if the connection has not been
3962: * used in more than a second. If <code>keepStoreAlive</code> is true,
3963: * also issue a noop over the store's connection.
3964: *
3965: * ASSERT: This method must be called only when holding the
3966: * messageCacheLock
3967: *
3968: * @param        keepStoreAlive        keep the Store alive too?
3969: * @exception        ProtocolException for protocol errors
3970: */
3971: protected void keepConnectionAlive(boolean keepStoreAlive)
3972: throws ProtocolException {
3973:
3974:         assert Thread.holdsLock(messageCacheLock);
3975:         if (protocol == null)        // in case connection was closed
3976:          return;
3977: if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) {
3978:          waitIfIdle();
3979:          if (protocol != null)
3980:                 protocol.noop();
3981:         }
3982:
3983: if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection()) {
3984: IMAPProtocol p = null;
3985:          try {
3986:                 p = ((IMAPStore)store).getFolderStoreProtocol();
3987:                 if (System.currentTimeMillis() - p.getTimestamp() > 1000)
3988:                  p.noop();
3989:          } finally {
3990:                 ((IMAPStore)store).releaseFolderStoreProtocol(p);
3991:          }
3992: }
3993: }
3994:
3995: /**
3996: * Get the message object for the given sequence number. If
3997: * none found, null is returned.
3998: *
3999: * ASSERT: This method must be called only when holding the
4000: * messageCacheLock
4001: *
4002: * @param        seqnum        the message sequence number
4003: * @return        the IMAPMessage object
4004: */
4005: protected IMAPMessage getMessageBySeqNumber(int seqnum) {
4006:         if (seqnum > messageCache.size()) {
4007:          // Microsoft Exchange will sometimes return message
4008:          // numbers that it has not yet notified the client
4009:          // about via EXISTS; ignore those messages here.
4010:          // GoDaddy IMAP does this too.
4011:          if (logger.isLoggable(Level.FINE))
4012:                 logger.fine("ignoring message number " +
4013:                  seqnum + " outside range " + messageCache.size());
4014:          return null;
4015:         }
4016:         return messageCache.getMessageBySeqnum(seqnum);
4017: }
4018:
4019: /**
4020: * Get the message objects for the given sequence numbers.
4021: *
4022: * ASSERT: This method must be called only when holding the
4023: * messageCacheLock
4024: *
4025: * @param        seqnums        the array of message sequence numbers
4026: * @return        the IMAPMessage objects
4027: * @since        JavaMail 1.5.3
4028: */
4029: protected IMAPMessage[] getMessagesBySeqNumbers(int[] seqnums) {
4030:         IMAPMessage[] msgs = new IMAPMessage[seqnums.length];
4031:         int nulls = 0;
4032:         // Map seq-numbers into actual Messages.
4033:         for (int i = 0; i < seqnums.length; i++) {
4034:          msgs[i] = getMessageBySeqNumber(seqnums[i]);
4035:          if (msgs[i] == null)
4036:                 nulls++;
4037:         }
4038:         if (nulls > 0) {        // compress the array to remove the nulls
4039:          IMAPMessage[] nmsgs = new IMAPMessage[seqnums.length - nulls];
4040:          for (int i = 0, j = 0; i < msgs.length; i++) {
4041:                 if (msgs[i] != null)
4042:                  nmsgs[j++] = msgs[i];
4043:          }
4044:          msgs = nmsgs;
4045:         }
4046:         return msgs;
4047: }
4048:
4049: private boolean isDirectory() {
4050:         return ((type & HOLDS_FOLDERS) != 0);
4051: }
4052: }
4053:
4054: /**
4055: * An object that holds a Message object
4056: * and reports its size and writes it to another OutputStream
4057: * on demand. Used by appendMessages to avoid the need to
4058: * buffer the entire message in memory in a single byte array
4059: * before sending it to the server.
4060: */
4061: class MessageLiteral implements Literal {
4062: private Message msg;
4063: private int msgSize = -1;
4064: private byte[] buf;                // the buffered message, if not null
4065:
4066: public MessageLiteral(Message msg, int maxsize)
4067:                                 throws MessagingException, IOException {
4068:         this.msg = msg;
4069:         // compute the size here so exceptions can be returned immediately
4070:         LengthCounter lc = new LengthCounter(maxsize);
4071:         OutputStream os = new CRLFOutputStream(lc);
4072:         msg.writeTo(os);
4073:         os.flush();
4074:         msgSize = lc.getSize();
4075:         buf = lc.getBytes();
4076: }
4077:
4078: @Override
4079: public int size() {
4080:         return msgSize;
4081: }
4082:
4083: @Override
4084: public void writeTo(OutputStream os) throws IOException {
4085:         // the message should not change between the constructor and this call
4086:         try {
4087:          if (buf != null)
4088:                 os.write(buf, 0, msgSize);
4089:          else {
4090:                 os = new CRLFOutputStream(os);
4091:                 msg.writeTo(os);
4092:          }
4093:         } catch (MessagingException mex) {
4094:          // exceptions here are bad, "should" never happen
4095:          throw new IOException("MessagingException while appending message: "
4096:                                  + mex);
4097:         }
4098: }
4099: }
4100:
4101: /**
4102: * Count the number of bytes written to the stream.
4103: * Also, save a copy of small messages to avoid having to process
4104: * the data again.
4105: */
4106: class LengthCounter extends OutputStream {
4107: private int size = 0;
4108: private byte[] buf;
4109: private int maxsize;
4110:
4111: public LengthCounter(int maxsize) {
4112:         buf = new byte[8192];
4113:         this.maxsize = maxsize;
4114: }
4115:
4116: @Override
4117: public void write(int b) {
4118:         int newsize = size + 1;
4119:         if (buf != null) {
4120:          if (newsize > maxsize && maxsize >= 0) {
4121:                 buf = null;
4122:          } else if (newsize > buf.length) {
4123:                 byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
4124:                 System.arraycopy(buf, 0, newbuf, 0, size);
4125:                 buf = newbuf;
4126:                 buf[size] = (byte)b;
4127:          } else {
4128:                 buf[size] = (byte)b;
4129:          }
4130:         }
4131:         size = newsize;
4132: }
4133:
4134: @Override
4135: public void write(byte b[], int off, int len) {
4136:         if ((off < 0) || (off > b.length) || (len < 0) ||
4137: ((off + len) > b.length) || ((off + len) < 0)) {
4138:          throw new IndexOutOfBoundsException();
4139:         } else if (len == 0) {
4140:          return;
4141:         }
4142: int newsize = size + len;
4143:         if (buf != null) {
4144:          if (newsize > maxsize && maxsize >= 0) {
4145:                 buf = null;
4146:          } else if (newsize > buf.length) {
4147:                 byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
4148:                 System.arraycopy(buf, 0, newbuf, 0, size);
4149:                 buf = newbuf;
4150:                 System.arraycopy(b, off, buf, size, len);
4151:          } else {
4152:                 System.arraycopy(b, off, buf, size, len);
4153:          }
4154:         }
4155: size = newsize;
4156: }
4157:
4158: @Override
4159: public void write(byte[] b) throws IOException {
4160:         write(b, 0, b.length);
4161: }
4162:
4163: public int getSize() {
4164:         return size;
4165: }
4166:
4167: public byte[] getBytes() {
4168:         return buf;
4169: }
4170: }