001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package javax.mail;
021    
022    import java.util.ArrayList;
023    import java.util.List;
024    
025    import javax.mail.Flags.Flag;
026    import javax.mail.event.ConnectionEvent;
027    import javax.mail.event.ConnectionListener;
028    import javax.mail.event.FolderEvent;
029    import javax.mail.event.FolderListener;
030    import javax.mail.event.MailEvent;
031    import javax.mail.event.MessageChangedEvent;
032    import javax.mail.event.MessageChangedListener;
033    import javax.mail.event.MessageCountEvent;
034    import javax.mail.event.MessageCountListener;
035    import javax.mail.search.SearchTerm;
036    
037    /**
038     * An abstract representation of a folder in a mail system; subclasses would
039     * implement Folders for each supported protocol.
040     * <p/>
041     * Depending on protocol and implementation, folders may contain other folders, messages,
042     * or both as indicated by the {@link Folder#HOLDS_FOLDERS} and {@link Folder#HOLDS_MESSAGES} flags.
043     * If the immplementation supports hierarchical folders, the format of folder names is
044     * implementation dependent; however, components of the name are separated by the
045     * delimiter character returned by {@link Folder#getSeparator()}.
046     * <p/>
047     * The case-insensitive folder name "INBOX" is reserved to refer to the primary folder
048     * for the current user on the current server; not all stores will provide an INBOX
049     * and it may not be available at all times.
050     *
051     * @version $Rev: 582780 $ $Date: 2007-10-08 07:17:15 -0400 (Mon, 08 Oct 2007) $
052     */
053    public abstract class Folder {
054        /**
055         * Flag that indicates that a folder can contain messages.
056         */
057        public static final int HOLDS_MESSAGES = 1;
058        /**
059         * Flag that indicates that a folder can contain other folders.
060         */
061        public static final int HOLDS_FOLDERS = 2;
062    
063        /**
064         * Flag indicating that this folder cannot be modified.
065         */
066        public static final int READ_ONLY = 1;
067        /**
068         * Flag indictaing that this folder can be modified.
069         * Question: what does it mean if both are set?
070         */
071        public static final int READ_WRITE = 2;
072    
073        /**
074         * The store that this folder is part of.
075         */
076        protected Store store;
077        /**
078         * The current mode of this folder.
079         * When open, this can be {@link #READ_ONLY} or {@link #READ_WRITE};
080         * otherwise is set to -1.
081         */
082        protected int mode = -1;
083    
084        private final ArrayList connectionListeners = new ArrayList(2);
085        private final ArrayList folderListeners = new ArrayList(2);
086        private final ArrayList messageChangedListeners = new ArrayList(2);
087        private final ArrayList messageCountListeners = new ArrayList(2);
088        // the EventQueue spins off a new thread, so we only create this 
089        // if we have actual listeners to dispatch an event to. 
090        private EventQueue queue = null;
091    
092        /**
093         * Constructor that initializes the Store.
094         *
095         * @param store the store that this folder is part of
096         */
097        protected Folder(Store store) {
098            this.store = store;
099        }
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    }