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 }