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