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