1 /**
2 *
3 * Copyright 2003-2006 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package javax.mail;
19
20 import java.util.ArrayList;
21 import java.util.List;
22 import javax.mail.Flags.Flag;
23 import javax.mail.event.ConnectionEvent;
24 import javax.mail.event.ConnectionListener;
25 import javax.mail.event.FolderEvent;
26 import javax.mail.event.FolderListener;
27 import javax.mail.event.MessageChangedEvent;
28 import javax.mail.event.MessageChangedListener;
29 import javax.mail.event.MessageCountEvent;
30 import javax.mail.event.MessageCountListener;
31 import javax.mail.search.SearchTerm;
32
33 /**
34 * An abstract representation of a folder in a mail system; subclasses would
35 * implement Folders for each supported protocol.
36 * <p/>
37 * Depending on protocol and implementation, folders may contain other folders, messages,
38 * or both as indicated by the {@link Folder#HOLDS_FOLDERS} and {@link Folder#HOLDS_MESSAGES} flags.
39 * If the immplementation supports hierarchical folders, the format of folder names is
40 * implementation dependent; however, components of the name are separated by the
41 * delimiter character returned by {@link Folder#getSeparator()}.
42 * <p/>
43 * The case-insensitive folder name "INBOX" is reserved to refer to the primary folder
44 * for the current user on the current server; not all stores will provide an INBOX
45 * and it may not be available at all times.
46 *
47 * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
48 */
49 public abstract class Folder {
50 /**
51 * Flag that indicates that a folder can contain messages.
52 */
53 public static final int HOLDS_MESSAGES = 1;
54 /**
55 * Flag that indicates that a folder can contain other folders.
56 */
57 public static final int HOLDS_FOLDERS = 2;
58
59 /**
60 * Flag indicating that this folder cannot be modified.
61 */
62 public static final int READ_ONLY = 1;
63 /**
64 * Flag indictaing that this folder can be modified.
65 * Question: what does it mean if both are set?
66 */
67 public static final int READ_WRITE = 2;
68
69 /**
70 * The store that this folder is part of.
71 */
72 protected Store store;
73 /**
74 * The current mode of this folder.
75 * When open, this can be {@link #READ_ONLY} or {@link #READ_WRITE};
76 * otherwise is set to -1.
77 */
78 protected int mode = -1;
79
80 private final List connectionListeners = new ArrayList(2);
81 private final List folderListeners = new ArrayList(2);
82 private final List messageChangedListeners = new ArrayList(2);
83 private final List messageCountListeners = new ArrayList(2);
84 private final EventQueue queue = new EventQueue();
85
86 /**
87 * Constructor that initializes the Store.
88 *
89 * @param store the store that this folder is part of
90 */
91 protected Folder(Store store) {
92 this.store = store;
93 }
94
95 /**
96 * Return the name of this folder.
97 * This can be invoked when the folder is closed.
98 *
99 * @return this folder's name
100 */
101 public abstract String getName();
102
103 /**
104 * Return the full absolute name of this folder.
105 * This can be invoked when the folder is closed.
106 *
107 * @return the full name of this folder
108 */
109 public abstract String getFullName();
110
111 /**
112 * Return the URLName for this folder, which includes the location of the store.
113 *
114 * @return the URLName for this folder
115 * @throws MessagingException
116 */
117 public URLName getURLName() throws MessagingException {
118
119 return store.getURLName();
120 }
121
122 /**
123 * Return the store that this folder is part of.
124 *
125 * @return the store this folder is part of
126 */
127 public Store getStore() {
128 return store;
129 }
130
131 /**
132 * Return the parent for this folder; if the folder is at the root of a heirarchy
133 * this returns null.
134 * This can be invoked when the folder is closed.
135 *
136 * @return this folder's parent
137 * @throws MessagingException
138 */
139 public abstract Folder getParent() throws MessagingException;
140
141 /**
142 * Check to see if this folder physically exists in the store.
143 * This can be invoked when the folder is closed.
144 *
145 * @return true if the folder really exists
146 * @throws MessagingException if there was a problem accessing the store
147 */
148 public abstract boolean exists() throws MessagingException;
149
150 /**
151 * Return a list of folders from this Folder's namespace that match the supplied pattern.
152 * Patterns may contain the following wildcards:
153 * <ul><li>'%' which matches any characater except hierarchy delimiters</li>
154 * <li>'*' which matches any character including hierarchy delimiters</li>
155 * </ul>
156 * This can be invoked when the folder is closed.
157 *
158 * @param pattern the pattern to search for
159 * @return a, possibly empty, array containing Folders that matched the pattern
160 * @throws MessagingException if there was a problem accessing the store
161 */
162 public abstract Folder[] list(String pattern) throws MessagingException;
163
164 /**
165 * Return a list of folders to which the user is subscribed and which match the supplied pattern.
166 * If the store does not support the concept of subscription then this should match against
167 * all folders; the default implementation of this method achieves this by defaulting to the
168 * {@link #list(String)} method.
169 *
170 * @param pattern the pattern to search for
171 * @return a, possibly empty, array containing subscribed Folders that matched the pattern
172 * @throws MessagingException if there was a problem accessing the store
173 */
174 public Folder[] listSubscribed(String pattern) throws MessagingException {
175 return list(pattern);
176 }
177
178 /**
179 * Convenience method that invokes {@link #list(String)} with the pattern "%".
180 *
181 * @return a, possibly empty, array of subfolders
182 * @throws MessagingException if there was a problem accessing the store
183 */
184 public Folder[] list() throws MessagingException {
185 return list("%");
186 }
187
188 /**
189 * Convenience method that invokes {@link #listSubscribed(String)} with the pattern "%".
190 *
191 * @return a, possibly empty, array of subscribed subfolders
192 * @throws MessagingException if there was a problem accessing the store
193 */
194 public Folder[] listSubscribed() throws MessagingException {
195 return listSubscribed("%");
196 }
197
198 /**
199 * Return the character used by this folder's Store to separate path components.
200 *
201 * @return the name separater character
202 * @throws MessagingException if there was a problem accessing the store
203 */
204 public abstract char getSeparator() throws MessagingException;
205
206 /**
207 * Return the type of this folder, indicating whether it can contain subfolders,
208 * messages, or both. The value returned is a bitmask with the appropriate bits set.
209 *
210 * @return the type of this folder
211 * @throws MessagingException if there was a problem accessing the store
212 * @see #HOLDS_FOLDERS
213 * @see #HOLDS_MESSAGES
214 */
215 public abstract int getType() throws MessagingException;
216
217 /**
218 * Create a new folder capable of containing subfoldera and/or messages as
219 * determined by the type parameter. Any hierarchy defined by the folder
220 * name will be recursively created.
221 * If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent}
222 * is sent to all FolderListeners registered with this Folder or with the Store.
223 *
224 * @param type the type, indicating if this folder should contain subfolders, messages or both
225 * @return true if the folder was sucessfully created
226 * @throws MessagingException if there was a problem accessing the store
227 */
228 public abstract boolean create(int type) throws MessagingException;
229
230 /**
231 * Determine if the user is subscribed to this Folder. The default implementation in
232 * this class always returns true.
233 *
234 * @return true is the user is subscribed to this Folder
235 */
236 public boolean isSubscribed() {
237 return true;
238 }
239
240 /**
241 * Set the user's subscription to this folder.
242 * Not all Stores support subscription; the default implementation in this class
243 * always throws a MethodNotSupportedException
244 *
245 * @param subscribed whether to subscribe to this Folder
246 * @throws MessagingException if there was a problem accessing the store
247 * @throws MethodNotSupportedException if the Store does not support subscription
248 */
249 public void setSubscribed(boolean subscribed) throws MessagingException {
250 throw new MethodNotSupportedException();
251 }
252
253 /**
254 * Check to see if this Folder conatins messages with the {@link Flag.RECENT} flag set.
255 * This can be used when the folder is closed to perform a light-weight check for new mail;
256 * to perform an incremental check for new mail the folder must be opened.
257 *
258 * @return true if the Store has recent messages
259 * @throws MessagingException if there was a problem accessing the store
260 */
261 public abstract boolean hasNewMessages() throws MessagingException;
262
263 /**
264 * Get the Folder determined by the supplied name; if the name is relative
265 * then it is interpreted relative to this folder. This does not check that
266 * the named folder actually exists.
267 *
268 * @param name the name of the folder to return
269 * @return the named folder
270 * @throws MessagingException if there was a problem accessing the store
271 */
272 public abstract Folder getFolder(String name) throws MessagingException;
273
274 /**
275 * Delete this folder and possibly any subfolders. This operation can only be
276 * performed on a closed folder.
277 * If recurse is true, then all subfolders are deleted first, then any messages in
278 * this folder are removed and it is finally deleted; {@link FolderEvent#DELETED}
279 * events are sent as appropriate.
280 * If recurse is false, then the behaviour depends on the folder type and store
281 * implementation as followd:
282 * <ul>
283 * <li>If the folder can only conrain messages, then all messages are removed and
284 * then the folder is deleted; a {@link FolderEvent#DELETED} event is sent.</li>
285 * <li>If the folder can onlu contain subfolders, then if it is empty it will be
286 * deleted and a {@link FolderEvent#DELETED} event is sent; if the folder is not
287 * empty then the delete fails and this method returns false.</li>
288 * <li>If the folder can contain both subfolders and messages, then if the folder
289 * does not contain any subfolders, any messages are deleted, the folder itself
290 * is deleted and a {@link FolderEvent#DELETED} event is sent; if the folder does
291 * contain subfolders then the implementation may choose from the following three
292 * behaviors:
293 * <ol>
294 * <li>it may return false indicting the operation failed</li>
295 * <li>it may remove all messages within the folder, send a {@link FolderEvent#DELETED}
296 * event, and then return true to indicate the delete was performed. Note this does
297 * not delete the folder itself and the {@link #exists()} operation for this folder
298 * will return true</li>
299 * <li>it may remove all messages within the folder as per the previous option; in
300 * addition it may change the type of the Folder to only HOLDS_FOLDERS indictaing
301 * that messages may no longer be added</li>
302 * </li>
303 * </ul>
304 * FolderEvents are sent to all listeners registered with this folder or
305 * with the Store.
306 *
307 * @param recurse whether subfolders should be recursively deleted as well
308 * @return true if the delete operation succeeds
309 * @throws MessagingException if there was a problem accessing the store
310 */
311 public abstract boolean delete(boolean recurse) throws MessagingException;
312
313 /**
314 * Rename this folder; the folder must be closed.
315 * If the rename is successfull, a {@link FolderEvent#RENAMED} event is sent to
316 * all listeners registered with this folder or with the store.
317 *
318 * @param newName the new name for this folder
319 * @return true if the rename succeeded
320 * @throws MessagingException if there was a problem accessing the store
321 */
322 public abstract boolean renameTo(Folder newName) throws MessagingException;
323
324 /**
325 * Open this folder; the folder must be able to contain messages and
326 * must currently be closed. If the folder is opened successfully then
327 * a {@link ConnectionEvent#OPENED} event is sent to listeners registered
328 * with this Folder.
329 * <p/>
330 * Whether the Store allows multiple connections or if it allows multiple
331 * writers is implementation defined.
332 *
333 * @param mode READ_ONLY or READ_WRITE
334 * @throws MessagingException if there was a problem accessing the store
335 */
336 public abstract void open(int mode) throws MessagingException;
337
338 /**
339 * Close this folder; it must already be open.
340 * A {@link ConnectionEvent#CLOSED} event is sent to all listeners registered
341 * with this folder.
342 *
343 * @param expunge whether to expunge all deleted messages
344 * @throws MessagingException if there was a problem accessing the store; the folder is still closed
345 */
346 public abstract void close(boolean expunge) throws MessagingException;
347
348 /**
349 * Indicates that the folder has been opened.
350 *
351 * @return true if the folder is open
352 */
353 public abstract boolean isOpen();
354
355 /**
356 * Return the mode of this folder ass passed to {@link #open(int)}, or -1 if
357 * the folder is closed.
358 *
359 * @return the mode this folder was opened with
360 */
361 public int getMode() {
362 return mode;
363 }
364
365 /**
366 * Get the flags supported by this folder.
367 *
368 * @return the flags supported by this folder, or null if unknown
369 * @see Flags
370 */
371 public abstract Flags getPermanentFlags();
372
373 /**
374 * Return the number of messages this folder contains.
375 * If this operation is invoked on a closed folder, the implementation
376 * may choose to return -1 to avoid the expense of opening the folder.
377 *
378 * @return the number of messages, or -1 if unknown
379 * @throws MessagingException if there was a problem accessing the store
380 */
381 public abstract int getMessageCount() throws MessagingException;
382
383 /**
384 * Return the numbew of messages in this folder that have the {@link Flag.RECENT} flag set.
385 * If this operation is invoked on a closed folder, the implementation
386 * may choose to return -1 to avoid the expense of opening the folder.
387 * The default implmentation of this method iterates over all messages
388 * in the folder; subclasses should override if possible to provide a more
389 * efficient implementation.
390 *
391 * @return the number of new messages, or -1 if unknown
392 * @throws MessagingException if there was a problem accessing the store
393 */
394 public int getNewMessageCount() throws MessagingException {
395 return getCount(Flags.Flag.RECENT, true);
396 }
397
398 /**
399 * Return the numbew of messages in this folder that do not have the {@link Flag.SEEN} flag set.
400 * If this operation is invoked on a closed folder, the implementation
401 * may choose to return -1 to avoid the expense of opening the folder.
402 * The default implmentation of this method iterates over all messages
403 * in the folder; subclasses should override if possible to provide a more
404 * efficient implementation.
405 *
406 * @return the number of new messages, or -1 if unknown
407 * @throws MessagingException if there was a problem accessing the store
408 */
409 public int getUnreadMessageCount() throws MessagingException {
410 return getCount(Flags.Flag.SEEN, false);
411 }
412
413 /**
414 * Return the numbew of messages in this folder that have the {@link Flag.DELETED} flag set.
415 * If this operation is invoked on a closed folder, the implementation
416 * may choose to return -1 to avoid the expense of opening the folder.
417 * The default implmentation of this method iterates over all messages
418 * in the folder; subclasses should override if possible to provide a more
419 * efficient implementation.
420 *
421 * @return the number of new messages, or -1 if unknown
422 * @throws MessagingException if there was a problem accessing the store
423 */
424 public int getDeletedMessageCount() throws MessagingException {
425 return getCount(Flags.Flag.DELETED, true);
426 }
427
428 private int getCount(Flag flag, boolean value) throws MessagingException {
429 if (!isOpen()) {
430 return -1;
431 }
432 Message[] messages = getMessages();
433 int total = 0;
434 for (int i = 0; i < messages.length; i++) {
435 if (messages[i].getFlags().contains(flag) == value) {
436 total++;
437 }
438 }
439 return total;
440 }
441
442 /**
443 * Retrieve the message with the specified index in this Folder;
444 * messages indices start at 1 not zero.
445 * Clients should note that the index for a specific message may change
446 * if the folder is expunged; {@link Message} objects should be used as
447 * references instead.
448 *
449 * @param index the index of the message to fetch
450 * @return the message
451 * @throws MessagingException if there was a problem accessing the store
452 */
453 public abstract Message getMessage(int index) throws MessagingException;
454
455 /**
456 * Retrieve messages with index between start and end inclusive
457 *
458 * @param start index of first message
459 * @param end index of last message
460 * @return an array of messages from start to end inclusive
461 * @throws MessagingException if there was a problem accessing the store
462 */
463 public Message[] getMessages(int start, int end) throws MessagingException {
464 Message[] result = new Message[end - start + 1];
465 for (int i = 0; i < result.length; i++) {
466 result[i] = getMessage(start++);
467 }
468 return result;
469 }
470
471 /**
472 * Retrieve messages with the specified indices.
473 *
474 * @param ids the indices of the messages to fetch
475 * @return the specified messages
476 * @throws MessagingException if there was a problem accessing the store
477 */
478 public Message[] getMessages(int ids[]) throws MessagingException {
479 Message[] result = new Message[ids.length];
480 for (int i = 0; i < ids.length; i++) {
481 result[i] = getMessage(ids[i]);
482 }
483 return result;
484 }
485
486 /**
487 * Retrieve all messages.
488 *
489 * @return all messages in this folder
490 * @throws MessagingException if there was a problem accessing the store
491 */
492 public Message[] getMessages() throws MessagingException {
493 return getMessages(1, getMessageCount());
494 }
495
496 /**
497 * Append the supplied messages to this folder. A {@link MessageCountEvent} is sent
498 * to all listeners registered with this folder when all messages have been appended.
499 * If the array contains a previously expunged message, it must be re-appended to the Store
500 * and implementations must not abort this operation.
501 *
502 * @param messages the messages to append
503 * @throws MessagingException if there was a problem accessing the store
504 */
505 public abstract void appendMessages(Message[] messages) throws MessagingException;
506
507 /**
508 * Hint to the store to prefetch information on the supplied messaged.
509 * Subclasses should override this method to provide an efficient implementation;
510 * the default implementation in this class simply returns.
511 *
512 * @param messages messages for which information should be fetched
513 * @param profile the information to fetch
514 * @throws MessagingException if there was a problem accessing the store
515 * @see FetchProfile
516 */
517 public void fetch(Message[] messages, FetchProfile profile) throws MessagingException {
518 return;
519 }
520
521 /**
522 * Set flags on the messages to the supplied value; all messages must belong to this folder.
523 * This method may be overridden by subclasses that can optimize the setting
524 * of flags on multiple messages at once; the default implementation simply calls
525 * {@link Message#setFlags(Flags, boolean)} for each supplied messages.
526 *
527 * @param messages whose flags should be set
528 * @param flags the set of flags to modify
529 * @param value the value the flags should be set to
530 * @throws MessagingException if there was a problem accessing the store
531 */
532 public void setFlags(Message[] messages, Flags flags, boolean value) throws MessagingException {
533 for (int i = 0; i < messages.length; i++) {
534 Message message = messages[i];
535 message.setFlags(flags, value);
536 }
537 }
538
539 /**
540 * Set flags on a range of messages to the supplied value.
541 * This method may be overridden by subclasses that can optimize the setting
542 * of flags on multiple messages at once; the default implementation simply
543 * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
544 *
545 * @param start first message end set
546 * @param end last message end set
547 * @param flags the set of flags end modify
548 * @param value the value the flags should be set end
549 * @throws MessagingException if there was a problem accessing the store
550 */
551 public void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException {
552 for (int i = start; i <= end; i++) {
553 Message message = getMessage(i);
554 message.setFlags(flags, value);
555 }
556 }
557
558 /**
559 * Set flags on a set of messages to the supplied value.
560 * This method may be overridden by subclasses that can optimize the setting
561 * of flags on multiple messages at once; the default implementation simply
562 * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
563 *
564 * @param ids the indexes of the messages to set
565 * @param flags the set of flags end modify
566 * @param value the value the flags should be set end
567 * @throws MessagingException if there was a problem accessing the store
568 */
569 public void setFlags(int ids[], Flags flags, boolean value) throws MessagingException {
570 for (int i = 0; i < ids.length; i++) {
571 Message message = getMessage(ids[i]);
572 message.setFlags(flags, value);
573 }
574 }
575
576 /**
577 * Copy the specified messages to another folder.
578 * The default implementation simply appends the supplied messages to the
579 * target folder using {@link #appendMessages(Message[])}.
580 * @param messages the messages to copy
581 * @param folder the folder to copy to
582 * @throws MessagingException if there was a problem accessing the store
583 */
584 public void copyMessages(Message[] messages, Folder folder) throws MessagingException {
585 folder.appendMessages(messages);
586 }
587
588 /**
589 * Permanently delete all supplied messages that have the DELETED flag set from the Store.
590 * The original message indices of all messages actually deleted are returned and a
591 * {@link MessageCountEvent} event is sent to all listeners with this folder. The expunge
592 * may cause the indices of all messaged that remain in the folder to change.
593 *
594 * @return the original indices of messages that were actually deleted
595 * @throws MessagingException if there was a problem accessing the store
596 */
597 public abstract Message[] expunge() throws MessagingException;
598
599 /**
600 * Search this folder for messages matching the supplied search criteria.
601 * The default implementation simply invoke <code>search(term, getMessages())
602 * applying the search over all messages in the folder; subclasses may provide
603 * a more efficient mechanism.
604 *
605 * @param term the search criteria
606 * @return an array containing messages that match the criteria
607 * @throws MessagingException if there was a problem accessing the store
608 */
609 public Message[] search(SearchTerm term) throws MessagingException {
610 return search(term, getMessages());
611 }
612
613 /**
614 * Search the supplied messages for those that match the supplied criteria;
615 * messages must belong to this folder.
616 * The default implementation iterates through the messages, returning those
617 * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
618 * subclasses may provide a more efficient implementation.
619 *
620 * @param term the search criteria
621 * @param messages the messages to search
622 * @return an array containing messages that match the criteria
623 * @throws MessagingException if there was a problem accessing the store
624 */
625 public Message[] search(SearchTerm term, Message[] messages) throws MessagingException {
626 List result = new ArrayList(messages.length);
627 for (int i = 0; i < messages.length; i++) {
628 Message message = messages[i];
629 if (message.match(term)) {
630 result.add(message);
631 }
632 }
633 return (Message[]) result.toArray(new Message[result.size()]);
634 }
635
636 public void addConnectionListener(ConnectionListener listener) {
637 connectionListeners.add(listener);
638 }
639
640 public void removeConnectionListener(ConnectionListener listener) {
641 connectionListeners.remove(listener);
642 }
643
644 protected void notifyConnectionListeners(int type) {
645 queue.queueEvent(new ConnectionEvent(this, type), connectionListeners);
646 }
647
648 public void addFolderListener(FolderListener listener) {
649 folderListeners.add(listener);
650 }
651
652 public void removeFolderListener(FolderListener listener) {
653 folderListeners.remove(listener);
654 }
655
656 protected void notifyFolderListeners(int type) {
657 queue.queueEvent(new FolderEvent(this, this, type), folderListeners);
658 }
659
660 protected void notifyFolderRenamedListeners(Folder newFolder) {
661 queue.queueEvent(new FolderEvent(this, this, newFolder, FolderEvent.RENAMED), folderListeners);
662 }
663
664 public void addMessageCountListener(MessageCountListener listener) {
665 messageCountListeners.add(listener);
666 }
667
668 public void removeMessageCountListener(MessageCountListener listener) {
669 messageCountListeners.remove(listener);
670 }
671
672 protected void notifyMessageAddedListeners(Message[] messages) {
673 queue.queueEvent(new MessageCountEvent(this, MessageCountEvent.ADDED, false, messages), messageChangedListeners);
674 }
675
676 protected void notifyMessageRemovedListeners(boolean removed, Message[] messages) {
677 queue.queueEvent(new MessageCountEvent(this, MessageCountEvent.REMOVED, removed, messages), messageChangedListeners);
678 }
679
680 public void addMessageChangedListener(MessageChangedListener listener) {
681 messageChangedListeners.add(listener);
682 }
683
684 public void removeMessageChangedListener(MessageChangedListener listener) {
685 messageChangedListeners.remove(listener);
686 }
687
688 protected void notifyMessageChangedListeners(int type, Message message) {
689 queue.queueEvent(new MessageChangedEvent(this, type, message), messageChangedListeners);
690 }
691
692 /**
693 * Unregisters all listeners.
694 */
695 protected void finalize() throws Throwable {
696 queue.stop();
697 connectionListeners.clear();
698 folderListeners.clear();
699 messageChangedListeners.clear();
700 messageCountListeners.clear();
701 store = null;
702 super.finalize();
703 }
704
705 /**
706 * Returns the full name of this folder; if null, returns the value from the superclass.
707 * @return a string form of this folder
708 */
709 public String toString() {
710 String name = getFullName();
711 return name == null ? super.toString() : name;
712 }
713 }