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