View Javadoc

1   /**
2    *
3    * Copyright 2003-2006 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package javax.mail;
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  import javax.mail.Flags.Flag;
23  import javax.mail.event.ConnectionEvent;
24  import javax.mail.event.ConnectionListener;
25  import javax.mail.event.FolderEvent;
26  import javax.mail.event.FolderListener;
27  import javax.mail.event.MessageChangedEvent;
28  import javax.mail.event.MessageChangedListener;
29  import javax.mail.event.MessageCountEvent;
30  import javax.mail.event.MessageCountListener;
31  import javax.mail.search.SearchTerm;
32  
33  /**
34   * An abstract representation of a folder in a mail system; subclasses would
35   * implement Folders for each supported protocol.
36   * <p/>
37   * Depending on protocol and implementation, folders may contain other folders, messages,
38   * or both as indicated by the {@link Folder#HOLDS_FOLDERS} and {@link Folder#HOLDS_MESSAGES} flags.
39   * If the immplementation supports hierarchical folders, the format of folder names is
40   * implementation dependent; however, components of the name are separated by the
41   * delimiter character returned by {@link Folder#getSeparator()}.
42   * <p/>
43   * The case-insensitive folder name "INBOX" is reserved to refer to the primary folder
44   * for the current user on the current server; not all stores will provide an INBOX
45   * and it may not be available at all times.
46   *
47   * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
48   */
49  public abstract class Folder {
50      /**
51       * Flag that indicates that a folder can contain messages.
52       */
53      public static final int HOLDS_MESSAGES = 1;
54      /**
55       * Flag that indicates that a folder can contain other folders.
56       */
57      public static final int HOLDS_FOLDERS = 2;
58  
59      /**
60       * Flag indicating that this folder cannot be modified.
61       */
62      public static final int READ_ONLY = 1;
63      /**
64       * Flag indictaing that this folder can be modified.
65       * Question: what does it mean if both are set?
66       */
67      public static final int READ_WRITE = 2;
68  
69      /**
70       * The store that this folder is part of.
71       */
72      protected Store store;
73      /**
74       * The current mode of this folder.
75       * When open, this can be {@link #READ_ONLY} or {@link #READ_WRITE};
76       * otherwise is set to -1.
77       */
78      protected int mode = -1;
79  
80      private final List connectionListeners = new ArrayList(2);
81      private final List folderListeners = new ArrayList(2);
82      private final List messageChangedListeners = new ArrayList(2);
83      private final List messageCountListeners = new ArrayList(2);
84      private final EventQueue queue = new EventQueue();
85  
86      /**
87       * Constructor that initializes the Store.
88       *
89       * @param store the store that this folder is part of
90       */
91      protected Folder(Store store) {
92          this.store = store;
93      }
94  
95      /**
96       * Return the name of this folder.
97       * This can be invoked when the folder is closed.
98       *
99       * @return this folder's name
100      */
101     public abstract String getName();
102 
103     /**
104      * Return the full absolute name of this folder.
105      * This can be invoked when the folder is closed.
106      *
107      * @return the full name of this folder
108      */
109     public abstract String getFullName();
110 
111     /**
112      * Return the URLName for this folder, which includes the location of the store.
113      *
114      * @return the URLName for this folder
115      * @throws MessagingException
116      */
117     public URLName getURLName() throws MessagingException {
118         // 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 }