View Javadoc

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