KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > suberic > pooka > FolderInfo


1 package net.suberic.pooka;
2
3 import javax.mail.*;
4 import javax.mail.event.*;
5 import javax.mail.search.*;
6 import javax.swing.event.EventListenerList JavaDoc;
7 import java.util.*;
8 import java.util.logging.*;
9 import javax.swing.AbstractAction JavaDoc;
10 import javax.swing.Action JavaDoc;
11 import java.awt.event.ActionEvent JavaDoc;
12 import net.suberic.pooka.gui.*;
13 import net.suberic.pooka.thread.*;
14 import net.suberic.pooka.event.*;
15 import net.suberic.util.ValueChangeListener;
16 import net.suberic.util.thread.ActionThread;
17
18 /**
19  * This class does all of the work for a Folder. If a FolderTableModel,
20  * FolderWindow, Message/Row-to-MessageInfo map, or FolderTreeNode exist
21  * for a Folder, the FolderInfo object has a reference to it.
22  */

23
24 public class FolderInfo implements MessageCountListener, ValueChangeListener, UserProfileContainer, MessageChangedListener, ConnectionListener {
25
26   // logger
27
Logger mLogger = null;
28
29   // folder is currently open and available.
30
public static int CONNECTED = 0;
31
32   // folder is disconnected, but should be open; try to reopen at first
33
// opportunity
34
public static int LOST_CONNECTION = 5;
35
36   // folder is available, but only should be accessed during the checkFolder
37
// phase.
38

39   public static int PASSIVE = 10;
40
41   // folder is running in disconnected mode; only act on the cached
42
// messages.
43
public static int DISCONNECTED = 15;
44
45   // Folder doesn't seem to exist on server, but exists in cache.
46
public static int CACHE_ONLY = 18;
47
48   // folder is just simply closed.
49
public static int CLOSED = 20;
50
51   // folder is not yet loaded.
52
public static int NOT_LOADED = 25;
53
54   // folder does not exist on server or in cache.
55
public static int INVALID = 30;
56
57   // shows the current status of the FolderInfo.
58
protected int status = NOT_LOADED;
59
60   // shows the type of this folder.
61
protected int type = 0;
62
63   // shows the preferred state of the FolderInfo. should be CONNECTED,
64
// PASSIVE, DISCONNECTED, or CLOSED.
65
protected int preferredStatus = CONNECTED;
66
67   // the resource for the folder disconnected message
68
protected static String JavaDoc disconnectedMessage = "error.Folder.disconnected";
69
70   // the Folder wrapped by this FolderInfo.
71
private Folder folder;
72
73   // The is the folder ID: storeName.parentFolderName.folderName
74
private String JavaDoc folderID;
75
76   // This is just the simple folderName, such as "INBOX"
77
private String JavaDoc mFolderName;
78
79   private EventListenerList JavaDoc eventListeners = new EventListenerList JavaDoc();
80
81   // Information for the FolderNode
82
protected FolderNode folderNode;
83   protected Vector children;
84
85   // Information for the FolderTable.
86
protected FolderTableModel folderTableModel;
87   protected Hashtable messageToInfoTable = new Hashtable();
88   private List JavaDoc columnValues;
89   private List JavaDoc<String JavaDoc> columnNames;
90   private List JavaDoc<String JavaDoc> columnSizes;
91   private List JavaDoc<String JavaDoc> columnIds;
92
93   // GUI information.
94
private FolderDisplayUI folderDisplayUI;
95   private Action JavaDoc[] defaultActions;
96
97   //filters
98
protected BackendMessageFilter[] backendFilters = null;
99   protected MessageFilter[] displayFilters = null;
100
101   //protected LoadMessageThread loaderThread;
102
protected MessageLoader mMessageLoader = null;
103   private FolderTracker folderTracker = null;
104
105   protected boolean loading = false;
106   protected int unreadCount = 0;
107   protected int messageCount = 0;
108   private boolean newMessages = false;
109
110   private FolderInfo parentFolder = null;
111   private StoreInfo parentStore = null;
112   private UserProfile defaultProfile = null;
113
114   private boolean sentFolder = false;
115   private boolean trashFolder = false;
116
117   private boolean notifyNewMessagesMain = true;
118   private boolean notifyNewMessagesNode = true;
119   private boolean tracksUnreadMessages = true;
120
121   protected FetchProfile fetchProfile = null;
122
123   protected OutgoingMailServer mailServer = null;
124
125   // whether or not this is a namespace
126
protected boolean mNamespace = false;
127
128   // the thread for connections to this folder.
129
//protected ActionThread mFolderThread;
130

131   /**
132    * For subclasses.
133    */

134   protected FolderInfo() {
135   }
136
137   /**
138    * Creates a new FolderInfo from a parent FolderInfo and a Folder
139    * name.
140    */

141
142   public FolderInfo(FolderInfo parent, String JavaDoc fname) {
143     parentFolder = parent;
144     setFolderID(parent.getFolderID() + "." + fname);
145     mFolderName = fname;
146
147     try {
148       if (parent.isLoaded())
149         loadFolder();
150     } catch (MessagingException me) {
151       // if we get an exception loading the folder while creating the folder
152
// object, just ignore it.
153
if (getLogger().isLoggable(Level.FINE)) {
154         getLogger().log(Level.FINE, Thread.currentThread() + "loading folder " + getFolderID() + ": caught messaging exception from parentStore getting folder: " + me);
155         me.printStackTrace();
156
157       }
158     }
159
160     updateChildren();
161
162     createFilters();
163
164     resetDefaultActions();
165
166     if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesMain", "").equalsIgnoreCase("false"))
167       setNotifyNewMessagesMain(true);
168     else
169       setNotifyNewMessagesMain(false);
170
171     if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesNode", "").equalsIgnoreCase("false")) {
172       setNotifyNewMessagesNode(true);
173     } else {
174       setNotifyNewMessagesNode(false);
175     }
176   }
177
178
179
180   /**
181    * Creates a new FolderInfo from a parent StoreInfo and a Folder
182    * name.
183    */

184
185   public FolderInfo(StoreInfo parent, String JavaDoc fname) {
186     parentStore = parent;
187     setFolderID(parent.getStoreID() + "." + fname);
188     mFolderName = fname;
189
190     mNamespace = Pooka.getProperty(getFolderID() + "._namespace", "false").equalsIgnoreCase("true");
191
192     try {
193       if (parent.isConnected())
194         loadFolder();
195     } catch (MessagingException me) {
196       // if we get an exception loading the folder while creating the folder
197
// object, just ignore it.
198
if (getLogger().isLoggable(Level.FINE)) {
199         getLogger().log(Level.FINE, Thread.currentThread() + "loading folder " + getFolderID() + ": caught messaging exception from parentStore getting folder: " + me);
200         me.printStackTrace();
201
202       }
203     }
204
205     updateChildren();
206
207     createFilters();
208
209     resetDefaultActions();
210
211     if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesMain", "").equalsIgnoreCase("false"))
212       setNotifyNewMessagesMain(true);
213     else
214       setNotifyNewMessagesMain(false);
215
216     if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesNode", "").equalsIgnoreCase("false"))
217       setNotifyNewMessagesNode(true);
218     else
219       setNotifyNewMessagesNode(false);
220   }
221
222   /**
223    * This actually loads up the Folder object itself. This is used so
224    * that we can have a FolderInfo even if we're not connected to the
225    * parent Store.
226    *
227    * Before we load the folder, the FolderInfo has the state of NOT_LOADED.
228    * Once the parent store is connected, we can try to load the folder.
229    * If the load is successful, we go to a CLOSED state. If it isn't,
230    * then we can either return to NOT_LOADED, or INVALID.
231    */

232   public void loadFolder() throws MessagingException {
233     loadFolder(true);
234   }
235
236   /**
237    * This actually loads up the Folder object itself. This is used so
238    * that we can have a FolderInfo even if we're not connected to the
239    * parent Store.
240    *
241    * Before we load the folder, the FolderInfo has the state of NOT_LOADED.
242    * Once the parent store is connected, we can try to load the folder.
243    * If the load is successful, we go to a CLOSED state. If it isn't,
244    * then we can either return to NOT_LOADED, or INVALID.
245    */

246   public void loadFolder(boolean pConnectStore) throws MessagingException {
247     boolean parentIsConnected = false;
248
249     if (isLoaded() || (loading && children == null))
250       return;
251
252     Folder[] tmpFolder = null;
253     Folder tmpParentFolder;
254
255     try {
256       loading = true;
257
258       if (parentStore != null) {
259         getLogger().log(Level.FINE, Thread.currentThread() + "loading folder " + getFolderID() + ": checking parent store connection.");
260
261         if (! parentStore.isAvailable())
262           throw new MessagingException();
263
264         if (!parentStore.isConnected()) {
265           if (pConnectStore) {
266             parentStore.connectStore();
267           } else {
268             return;
269           }
270         }
271
272
273         Store store = parentStore.getStore();
274
275         // first see if we're a namespace
276
try {
277           getLogger().log(Level.FINE, "checking to see if " + getFolderID() + " is a shared folder.");
278
279           Folder[] sharedFolders = store.getSharedNamespaces();
280
281           if (sharedFolders != null && sharedFolders.length > 0) {
282             for (int i = 0; ( tmpFolder == null || tmpFolder.length == 0 ) && i < sharedFolders.length; i++) {
283               if (sharedFolders[i].getName().equalsIgnoreCase(mFolderName)) {
284                 if (!mNamespace) {
285                   Pooka.setProperty(getFolderID() + "._namespace", "true");
286                   mNamespace = true;
287                 }
288                 tmpFolder = new Folder[1];
289                 tmpFolder[0] = sharedFolders[i] ;
290               }
291             }
292           }
293         } catch (Exception JavaDoc e) {
294           // if we get a not supported exception or some such here,
295
// just ignore it.
296
}
297
298         if (tmpFolder == null || tmpFolder.length == 0) {
299           // not a shared namespace
300
tmpParentFolder = store.getDefaultFolder();
301           getLogger().log(Level.FINE, "got " + tmpParentFolder + " as Default Folder for store.");
302           getLogger().log(Level.FINE, "doing a list on default folder " + tmpParentFolder + " for folder " + mFolderName);
303           tmpFolder = tmpParentFolder.list(mFolderName);
304         }
305
306         getLogger().log(Level.FINE, "got " + tmpFolder + " as Folder for folder " + getFolderID() + ".");
307
308       } else {
309         if (!parentFolder.isLoaded())
310           parentFolder.loadFolder();
311         if (!parentFolder.isLoaded()) {
312           tmpFolder = null;
313         } else {
314           tmpParentFolder = parentFolder.getFolder();
315           if (tmpParentFolder != null) {
316             parentIsConnected = true;
317             getLogger().log(Level.FINE, "running list (" + mFolderName + ") on parent folder " + tmpParentFolder);
318             tmpFolder = tmpParentFolder.list(mFolderName);
319           } else {
320             tmpFolder = null;
321           }
322         }
323       }
324
325       if (tmpFolder != null && tmpFolder.length > 0) {
326         setFolder(tmpFolder[0]);
327         if (! getFolder().isSubscribed())
328           getFolder().setSubscribed(true);
329
330         type = getFolder().getType();
331         setStatus(CLOSED);
332       } else {
333         getLogger().log(Level.FINE, "folder " + mFolderName + " does not exist; setting as INVALID.");
334         if (parentIsConnected)
335           setStatus(INVALID);
336         setFolder(null);
337       }
338
339
340       /*
341         if (mFolderThread == null) {
342         mFolderThread = new ActionThread(getParentStore().getStoreID() + "." + getFolderID() + " - ActionThread");
343         mFolderThread.start();
344         }
345       */

346
347
348     } finally {
349       loading = false;
350     }
351
352     initializeFolderInfo();
353
354   }
355
356   /**
357    * This adds this a listener to the Folder.
358    */

359   protected void addFolderListeners() {
360     if (folder != null) {
361       folder.addMessageChangedListener(this);
362       folder.addMessageCountListener(this);
363       folder.addConnectionListener(this);
364     }
365   }
366
367   /**
368    * This removes this as a listener to the Folder.
369    */

370   protected void removeFolderListeners() {
371     if (folder != null) {
372       folder.removeMessageChangedListener(this);
373       folder.removeMessageCountListener(this);
374       folder.removeConnectionListener(this);
375     }
376   }
377
378   /**
379    * this is called by loadFolders if a proper Folder object
380    * is returned.
381    */

382   protected void initializeFolderInfo() {
383     addFolderListeners();
384
385     Pooka.getResources().addValueChangeListener(this, getFolderProperty());
386     Pooka.getResources().addValueChangeListener(this, getFolderProperty() + ".folderList");
387     Pooka.getResources().addValueChangeListener(this, getFolderProperty() + ".defaultProfile");
388     Pooka.getResources().addValueChangeListener(this, getFolderProperty() + ".displayFilters");
389     Pooka.getResources().addValueChangeListener(this, getFolderProperty() + ".backendFilters");
390     Pooka.getResources().addValueChangeListener(this, getFolderProperty() + ".notifyNewMessagesMain");
391     Pooka.getResources().addValueChangeListener(this, getFolderProperty() + ".notifyNewMessagesNode");
392
393     Pooka.getLogManager().addLogger(getFolderProperty());
394
395     String JavaDoc defProfile = Pooka.getProperty(getFolderProperty() + ".defaultProfile", "");
396     if ((!defProfile.equals("")) && (!defProfile.equals(UserProfile.S_DEFAULT_PROFILE_KEY)))
397       defaultProfile = Pooka.getPookaManager().getUserProfileManager().getProfile(defProfile);
398
399     // if we got to this point, we should assume that the open worked.
400

401     if (getFolderTracker() == null) {
402       FolderTracker tracker = Pooka.getFolderTracker();
403       if (tracker != null) {
404         tracker.addFolder(this);
405         this.setFolderTracker(tracker);
406       } else {
407         if (Pooka.sStartupManager.isShuttingDown()) {
408           getLogger().fine("No FolderTracker available.");
409         } else {
410           getLogger().warning("Error: No FolderTracker available for folder " + getFolderID());
411         }
412       }
413     }
414   }
415
416   public void closed(ConnectionEvent e) {
417
418     synchronized(this) {
419
420       getLogger().log(Level.FINE, "Folder " + getFolderID() + " closed: " + e);
421       // if this happened accidentally, check it.
422
if (getStatus() != CLOSED && getStatus() != DISCONNECTED) {
423
424         getFolderThread().addToQueue(new javax.swing.AbstractAction JavaDoc() {
425             public void actionPerformed(java.awt.event.ActionEvent JavaDoc e) {
426               // check to see if the parent store is still open.
427

428               StoreInfo parentStoreInfo = getParentStore();
429               if (parentStoreInfo != null) {
430                 if (parentStoreInfo.isConnected())
431                   parentStoreInfo.checkConnection();
432               }
433             }
434           }, new java.awt.event.ActionEvent JavaDoc(this, 0, "folder-closed"), ActionThread.PRIORITY_HIGH);
435
436         if (getFolderDisplayUI() != null) {
437           getFolderDisplayUI().showStatusMessage(Pooka.getProperty(disconnectedMessage, "Lost connection to folder..."));
438         }
439
440         if (status == CONNECTED) {
441           setStatus(LOST_CONNECTION);
442         }
443       }
444
445     }
446     fireConnectionEvent(e);
447
448   }
449
450   public void disconnected(ConnectionEvent e) {
451     synchronized(this) {
452       if (getLogger().isLoggable(Level.FINE)) {
453         getLogger().log(Level.FINE, "Folder " + getFolderID() + " disconnected.");
454         Thread.dumpStack();
455       }
456
457       // if this happened accidentally, check it.
458
if (getStatus() != CLOSED) {
459         getFolderThread().addToQueue(new javax.swing.AbstractAction JavaDoc() {
460             public void actionPerformed(java.awt.event.ActionEvent JavaDoc e) {
461               // check to see if the parent store is still open.
462

463               StoreInfo parentStoreInfo = getParentStore();
464               if (parentStoreInfo != null && parentStoreInfo.isConnected()) {
465                 parentStoreInfo.checkConnection();
466               }
467             }
468           }, new java.awt.event.ActionEvent JavaDoc(this, 0, "folder-closed"), ActionThread.PRIORITY_HIGH);
469
470         if (getFolderDisplayUI() != null) {
471           getFolderDisplayUI().showStatusMessage(Pooka.getProperty("error.UIDFolder.disconnected", "Lost connection to folder..."));
472         }
473
474         if (status == CONNECTED) {
475           setStatus(LOST_CONNECTION);
476         }
477       }
478
479     }
480
481     fireConnectionEvent(e);
482   }
483
484
485   /**
486    * Invoked when a Store/Folder/Transport is opened.
487    *
488    * As specified in javax.mail.event.ConnectionListener.
489    */

490   public void opened (ConnectionEvent e) {
491     fireConnectionEvent(e);
492   }
493
494   /**
495    * Invoked when a Store/Folder/Transport is opened.
496    *
497    * As specified in javax.mail.event.ConnectionListener.
498    */

499   public void connected (ConnectionEvent e) {
500     fireConnectionEvent(e);
501   }
502
503   /**
504    * This method opens the Folder, and sets the FolderInfo to know that
505    * the Folder should be open. You should use this method instead of
506    * calling getFolder().open(), because if you use this method, then
507    * the FolderInfo will try to keep the Folder open, and will try to
508    * reopen the Folder if it gets closed before closeFolder is called.
509    *
510    * This method can also be used to reset the mode of an already
511    * opened folder.
512    */

513   public void openFolder(int mode) throws MessagingException {
514     openFolder(mode, true);
515   }
516
517   /**
518    * This method opens the Folder, and sets the FolderInfo to know that
519    * the Folder should be open. You should use this method instead of
520    * calling getFolder().open(), because if you use this method, then
521    * the FolderInfo will try to keep the Folder open, and will try to
522    * reopen the Folder if it gets closed before closeFolder is called.
523    *
524    * This method can also be used to reset the mode of an already
525    * opened folder.
526    */

527   public void openFolder(int mode, boolean pConnectStore) throws MessagingException {
528     //System.err.println("timing: opening folder " + getFolderID());
529
//long currentTime = System.currentTimeMillis();
530

531     getLogger().log(Level.FINE, this + ": checking parent store.");
532
533     if (!getParentStore().isConnected() && pConnectStore) {
534       getLogger().log(Level.FINE, this + ": parent store isn't connected. trying connection.");
535       getParentStore().connectStore();
536     }
537
538     getLogger().log(Level.FINE, this + ": loading folder.");
539
540     if (! isLoaded() && status != CACHE_ONLY)
541       loadFolder(pConnectStore);
542
543     getLogger().log(Level.FINE, this + ": folder loaded. status is " + status);
544
545     getLogger().log(Level.FINE, this + ": checked on parent store. trying isLoaded() and isAvailable().");
546
547     if (status == CLOSED || status == LOST_CONNECTION || status == DISCONNECTED) {
548       getLogger().log(Level.FINE, this + ": isLoaded() and isAvailable().");
549       if (folder.isOpen()) {
550         if (folder.getMode() == mode)
551           return;
552         else {
553           folder.close(false);
554           openFolder(mode);
555           updateFolderOpenStatus(true);
556           resetMessageCounts();
557         }
558       } else {
559         folder.open(mode);
560         updateFolderOpenStatus(true);
561         resetMessageCounts();
562       }
563     } else if (status == INVALID) {
564       throw new MessagingException(Pooka.getProperty("error.folderInvalid", "Error: folder is invalid. ") + getFolderID());
565     }
566
567     //System.err.println("timing: opening folder " + getFolderID() + " took " + (System.currentTimeMillis() - currentTime) + " milliseconds.");
568

569   }
570
571   /**
572    * Actually records that the folder has been opened or closed.
573    * This is separated out so that subclasses can override it more
574    * easily.
575    */

576   protected void updateFolderOpenStatus(boolean isNowOpen) {
577     if (isNowOpen) {
578       setStatus(CONNECTED);
579     } else {
580       setStatus(CLOSED);
581     }
582   }
583
584   /**
585    * This method calls openFolder() on this FolderInfo, and then, if
586    * this FolderInfo has any children, calls openFolder() on them,
587    * also.
588    *
589    * This is usually called by StoreInfo.connectStore() if
590    * Pooka.openFoldersOnConnect is set to true.
591    */

592
593   public void openAllFolders(int mode) {
594     try {
595       openFolder(mode, false);
596     } catch (MessagingException me) {
597     }
598
599     if (children != null) {
600       for (int i = 0; i < children.size(); i++) {
601         doOpenFolders((FolderInfo) children.elementAt(i), mode);
602       }
603     }
604   }
605
606   /**
607    * Handles the threading of doing an openAllFolders.
608    */

609   private void doOpenFolders(FolderInfo fi, int mode) {
610     if (Pooka.getProperty("Pooka.openFoldersInBackground", "false").equalsIgnoreCase("true")) {
611       final FolderInfo current = fi;
612       final int finalMode = mode;
613
614       javax.swing.AbstractAction JavaDoc openFoldersAction =new javax.swing.AbstractAction JavaDoc() {
615           public void actionPerformed(java.awt.event.ActionEvent JavaDoc e) {
616             current.openAllFolders(finalMode);
617           }
618         };
619
620       openFoldersAction.putValue(javax.swing.Action.NAME, "file-open");
621       openFoldersAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "file-open on folder " + fi.getFolderID());
622       getFolderThread().addToQueue(openFoldersAction, new java.awt.event.ActionEvent JavaDoc(this, 0, "open-all"), ActionThread.PRIORITY_LOW);
623     } else {
624       fi.openAllFolders(mode);
625     }
626   }
627
628   /**
629    * This method closes the Folder. If you open the Folder using
630    * openFolder (which you should), then you should use this method
631    * instead of calling getFolder.close(). If you don't, then the
632    * FolderInfo will try to reopen the folder.
633    */

634   public void closeFolder(boolean expunge, boolean closeDisplay) throws MessagingException {
635
636     if (closeDisplay) {
637       unloadAllMessages();
638
639       if (getFolderDisplayUI() != null)
640         getFolderDisplayUI().closeFolderDisplay();
641
642       setFolderDisplayUI(null);
643     }
644
645     if (getFolderTracker() != null) {
646       getFolderTracker().removeFolder(this);
647       setFolderTracker(null);
648     }
649
650     if (isLoaded() && isValid()) {
651       setStatus(CLOSED);
652       try {
653         folder.close(expunge);
654       } catch (java.lang.IllegalStateException JavaDoc ise) {
655         throw new MessagingException(ise.getMessage(), ise);
656       }
657     }
658
659   }
660
661   public void closeFolder(boolean expunge) throws MessagingException {
662     closeFolder(expunge, true);
663   }
664
665   /**
666    * This closes the current Folder as well as all subfolders.
667    */

668   public void closeAllFolders(boolean expunge, boolean shuttingDown) throws MessagingException {
669     /*
670       if (shuttingDown && loaderThread != null) {
671       loaderThread.stopThread();
672       }
673     */

674
675     if (shuttingDown && mMessageLoader != null) {
676       mMessageLoader.stopLoading();
677     }
678
679     synchronized(getFolderThread().getRunLock()) {
680       MessagingException otherException = null;
681       Vector folders = getChildren();
682       if (folders != null) {
683         for (int i = 0; i < folders.size(); i++) {
684           try {
685             ((FolderInfo) folders.elementAt(i)).closeAllFolders(expunge, shuttingDown);
686           } catch (MessagingException me) {
687             if (otherException == null)
688               otherException = me;
689           } catch (Exception JavaDoc e) {
690             MessagingException newMe = new MessagingException (e.getMessage(), e);
691             if (otherException == null)
692               otherException = newMe;
693           }
694         }
695       }
696
697       closeFolder(expunge, false);
698
699       if (otherException != null)
700         throw otherException;
701     }
702   }
703
704   /**
705    * Gets all of the children folders of this FolderInfo which are
706    * both Open and can contain Messages. The return value should include
707    * the current FolderInfo, if it is Open and can contain Messages.
708    */

709   public Vector getAllFolders() {
710     Vector returnValue = new Vector();
711     if (children != null) {
712       for (int i = 0 ; i < children.size(); i++)
713         returnValue.addAll(((FolderInfo) children.elementAt(i)).getAllFolders());
714     }
715
716     if (isSortaOpen() && (getType() & Folder.HOLDS_MESSAGES) != 0)
717       returnValue.add(this);
718
719     return returnValue;
720   }
721
722   /**
723    * Synchronizes the locally stored subscribed folders list to the subscribed
724    * folder information from the IMAP server.
725    */

726   public void synchSubscribed() throws MessagingException {
727
728     // if we're a namespace, then ignore.
729
if (mNamespace)
730       return;
731
732     // at this point we should get folder objects.
733
if (! isLoaded())
734       loadFolder();
735
736     if ((getType() & Folder.HOLDS_FOLDERS) != 0) {
737
738       Folder[] subscribedFolders = folder.list();
739
740       StringBuffer JavaDoc newSubscribed = new StringBuffer JavaDoc();
741
742       for (int i = 0; subscribedFolders != null && i < subscribedFolders.length; i++) {
743         // sometimes listSubscribed() doesn't work.
744
if (subscribedFolders[i].isSubscribed() || subscribedFolders[i].getName().equalsIgnoreCase("INBOX")) {
745           String JavaDoc folderName = subscribedFolders[i].getName();
746           newSubscribed.append(folderName).append(':');
747         }
748       }
749
750       if (newSubscribed.length() > 0)
751         newSubscribed.deleteCharAt(newSubscribed.length() -1);
752
753       // this will update our children vector.
754
Pooka.setProperty(getFolderProperty() + ".folderList", newSubscribed.toString());
755
756       for (int i = 0; children != null && i < children.size(); i++) {
757         FolderInfo fi = (FolderInfo) children.elementAt(i);
758         fi.synchSubscribed();
759       }
760     }
761   }
762
763   /**
764    * Loads the column names and sizes.
765    */

766   protected FetchProfile createColumnInformation() {
767     String JavaDoc tableType;
768
769     if (isSentFolder())
770       tableType="SentFolderTable";
771     //else if (this instanceof VirtualFolderInfo)
772
// tableType="SearchResultsTable";
773
else if (isOutboxFolder())
774       tableType="SentFolderTable";
775     else
776       tableType="FolderTable";
777
778     FetchProfile fp = new FetchProfile();
779     fp.add(FetchProfile.Item.FLAGS);
780     if (columnValues == null) {
781       List JavaDoc<String JavaDoc> colIds = Pooka.getResources().getPropertyAsList(tableType, "");
782       Vector colvals = new Vector();
783       Vector<String JavaDoc> colnames = new Vector<String JavaDoc>();
784       Vector<String JavaDoc> colsizes = new Vector<String JavaDoc>();
785
786       for (String JavaDoc tmp: colIds) {
787         String JavaDoc type = Pooka.getProperty(tableType + "." + tmp + ".type", "");
788         if (type.equalsIgnoreCase("Multi")) {
789           SearchTermIconManager stm = new SearchTermIconManager(tableType + "." + tmp);
790           colvals.addElement(stm);
791           Vector toFetch = Pooka.getResources().getPropertyAsVector(tableType + "." + tmp + ".profileItems", "");
792           if (toFetch != null) {
793             for (int z = 0; z < toFetch.size(); z++) {
794               String JavaDoc profileDef = (String JavaDoc) toFetch.elementAt(z);
795               if (profileDef.equalsIgnoreCase("Flags")) {
796                 getLogger().log(Level.FINE, "adding FLAGS to FetchProfile.");
797                 fp.add(FetchProfile.Item.FLAGS);
798               } else if (profileDef.equalsIgnoreCase("Envelope")) {
799                 getLogger().log(Level.FINE, "adding ENVELOPE to FetchProfile.");
800                 fp.add(FetchProfile.Item.ENVELOPE);
801               } else if (profileDef.equalsIgnoreCase("Content_Info")) {
802                 getLogger().log(Level.FINE, "adding CONTENT_INFO to FetchProfile.");
803                 fp.add(FetchProfile.Item.CONTENT_INFO);
804               } else {
805                 getLogger().log(Level.FINE, "adding " + profileDef + " to FetchProfile.");
806                 fp.add(profileDef);
807               }
808             }
809           }
810         } else if (type.equalsIgnoreCase("RowCounter")) {
811           colvals.addElement(RowCounter.getInstance());
812         } else {
813           String JavaDoc value = Pooka.getProperty(tableType + "." + tmp + ".value", tmp);
814           colvals.addElement(value);
815           String JavaDoc fpValue = Pooka.getProperty(tableType + "." + tmp + ".profileItems", value);
816           fp.add(fpValue);
817         }
818
819         colnames.addElement(Pooka.getProperty(tableType + "." + tmp + ".label", tmp));
820           String JavaDoc value = Pooka.getProperty(getFolderProperty() + ".columnsize." + tmp + ".value", Pooka.getProperty(tableType + "." + tmp + ".value", tmp));
821         colsizes.addElement(Pooka.getProperty(getFolderProperty() + ".columnsize." + tmp + ".value", Pooka.getProperty(tableType + "." + tmp + ".size", tmp)));
822       }
823       setColumnNames(colnames);
824       setColumnValues(colvals);
825       setColumnSizes(colsizes);
826       setColumnIds(colIds);
827     }
828
829     // if we've already loaded the filters, then add those in, too.
830
if (filterHeaders != null) {
831       for (int i = 0; i < filterHeaders.size(); i++) {
832         fp.add((String JavaDoc) filterHeaders.get(i));
833       }
834     }
835
836     if (getLogger().isLoggable(Level.FINE)) {
837       getLogger().log(Level.FINE, "created fetch profile.");
838       String JavaDoc[] headers = fp.getHeaderNames();
839       if (headers != null) {
840         for (int i = 0; i < headers.length; i++) {
841           getLogger().log(Level.FINE, "headers["+i+"]=" + headers[i]);
842         }
843       }
844       getLogger().log(Level.FINE, "headers done.");
845     }
846
847     return fp;
848   }
849
850   /**
851    * During loadAllMessages, updates the display to say that we're loading
852    * messages.
853    */

854   protected void updateDisplay(boolean start) {
855     if (getFolderDisplayUI() != null) {
856       if (start) {
857         getFolderDisplayUI().setBusy(true);
858         getFolderDisplayUI().showStatusMessage(Pooka.getProperty("messages.Folder.loading.starting", "Loading messages."));
859       } else {
860         getFolderDisplayUI().setBusy(false);
861         getFolderDisplayUI().showStatusMessage(Pooka.getProperty("messages.Folder.loading.finished", "Done loading messages."));
862       }
863     }
864   }
865
866   /**
867    * While loading messages, attempts to update the folder status.
868    */

869   protected void updateFolderStatusForLoading() throws MessagingException {
870     if (! isConnected() ) {
871       openFolder(Folder.READ_WRITE);
872     }
873   }
874
875   /**
876    * Notifies the FolderNode that it needs to be updated.
877    */

878   protected void updateNode() {
879     if (getFolderNode() != null)
880       getFolderNode().updateNode();
881   }
882
883   /**
884    * Loads the MessageInfos and MesageProxies. Returns a List of
885    * newly created MessageProxies.
886    */

887   protected List JavaDoc createInfosAndProxies() throws MessagingException {
888     int fetchBatchSize = 50;
889     try {
890       fetchBatchSize = Integer.parseInt(Pooka.getProperty("Pooka.fetchBatchSize", "50"));
891     } catch (NumberFormatException JavaDoc nfe) {
892     }
893
894     Vector messageProxies = new Vector();
895
896     Message[] msgs = folder.getMessages();
897
898     Message[] toFetch = msgs;
899
900     // go ahead and fetch the first set of messages; the rest will be
901
// taken care of by the loaderThread.
902
if (msgs.length > fetchBatchSize) {
903       toFetch = new Message[fetchBatchSize];
904       System.arraycopy(msgs, msgs.length - fetchBatchSize, toFetch, 0, fetchBatchSize);
905     }
906
907     folder.fetch(toFetch, fetchProfile);
908
909     int firstFetched = Math.max(msgs.length - fetchBatchSize, 0);
910
911     MessageInfo mi;
912
913     for (int i = 0; i < msgs.length; i++) {
914       mi = new MessageInfo(msgs[i], this);
915
916       if ( i >= firstFetched)
917         mi.setFetched(true);
918
919       messageProxies.add(new MessageProxy(getColumnValues() , mi));
920       messageToInfoTable.put(msgs[i], mi);
921     }
922
923     return messageProxies;
924   }
925
926   /**
927    * Applies message filters to the new messages.
928    */

929   public void runFilters(List JavaDoc proxies) throws MessagingException {
930     if (isConnected()) {
931       Folder current = getFolder();
932       if (current != null && current.isOpen()) {
933         int newCount = current.getNewMessageCount();
934
935         if (newCount > 0) {
936           int numProxies = proxies.size();
937           List JavaDoc newProxies = new ArrayList();
938           for (int i = 0; i < newCount; i++) {
939             newProxies.add(proxies.get((numProxies - newCount) + i));
940           }
941           proxies.removeAll(applyFilters(newProxies));
942         }
943       }
944     }
945
946   }
947
948   /**
949    * Updates any caching information, if necessary.
950    */

951   protected void updateCache() throws MessagingException {
952     // no-op by default.
953
}
954
955   /**
956    * Loads all Messages into a new FolderTableModel, sets this
957    * FolderTableModel as the current FolderTableModel, and then returns
958    * said FolderTableModel. This is the basic way to populate a new
959    * FolderTableModel.
960    */

961   public synchronized void loadAllMessages() throws MessagingException {
962     if (folderTableModel == null) {
963       updateDisplay(true);
964
965       if (! isLoaded())
966         loadFolder();
967
968       fetchProfile = createColumnInformation();
969
970       /*
971         if (loaderThread == null)
972         loaderThread = createLoaderThread();
973       */

974       if (mMessageLoader == null)
975         mMessageLoader = createMessageLoader();
976
977       try {
978         updateFolderStatusForLoading();
979
980         List JavaDoc messageProxies = createInfosAndProxies();
981
982         runFilters(messageProxies);
983
984         FolderTableModel ftm = new FolderTableModel(messageProxies, getColumnNames(), getColumnSizes(), getColumnValues(), getColumnIds());
985
986         setFolderTableModel(ftm);
987
988         updateCache();
989
990         Vector loadImmediately = null;
991
992         int loadBatchSize = 25;
993
994         if (messageProxies.size() > loadBatchSize) {
995           // get the first unread.
996
int firstUnread = messageProxies.size();
997           if (Pooka.getProperty("Pooka.autoSelectFirstUnread", "true").equalsIgnoreCase("true")) {
998             firstUnread = getFirstUnreadMessage();
999           }
1000
1001          int lastLoaded = messageProxies.size() - 1;
1002          int firstLoaded = messageProxies.size() - loadBatchSize - 1;
1003
1004          if (firstUnread > -1) {
1005            if (firstUnread < firstLoaded) {
1006              firstLoaded = Math.max(0, firstUnread - 5);
1007              lastLoaded = firstLoaded + loadBatchSize;
1008            }
1009          }
1010
1011          loadImmediately = new Vector();
1012          for (int i = lastLoaded; i >= firstLoaded; i--) {
1013            loadImmediately.add(messageProxies.get(i));
1014          }
1015        } else {
1016          loadImmediately = new Vector(messageProxies);
1017        }
1018
1019        loadMessageTableInfos(loadImmediately);
1020
1021        /*
1022          loaderThread.loadMessages(messageProxies);
1023
1024          if (!loaderThread.isAlive())
1025          loaderThread.start();
1026        */

1027        mMessageLoader.loadMessages(messageProxies);
1028
1029
1030      } finally {
1031        updateDisplay(false);
1032      }
1033
1034    }
1035  }
1036
1037  /**
1038   * Loads the FolderTableInfo objects for the given messages.
1039   */

1040  public void loadMessageTableInfos(Vector messages) {
1041    int numMessages = messages.size();
1042    MessageProxy mp;
1043
1044    int updateCounter = 0;
1045
1046    if (numMessages > 0) {
1047
1048      int fetchBatchSize = 25;
1049      int loadBatchSize = 25;
1050      try {
1051        fetchBatchSize = Integer.parseInt(Pooka.getProperty("Pooka.fetchBatchSize", "50"));
1052      } catch (NumberFormatException JavaDoc nfe) {
1053      }
1054
1055      FetchProfile fetchProfile = getFetchProfile();
1056
1057      int i = numMessages - 1;
1058      while ( i >= 0 ) {
1059        for (int batchCount = 0; i >=0 && batchCount < loadBatchSize; batchCount++) {
1060          mp=(MessageProxy)messages.elementAt(i);
1061
1062          if (! mp.getMessageInfo().hasBeenFetched()) {
1063            try {
1064              int fetchCount = 0;
1065              Vector fetchVector = new Vector();
1066              for (int j = i; fetchCount < fetchBatchSize && j >= 0; j--) {
1067                MessageInfo fetchInfo = ((MessageProxy) messages.elementAt(j)).getMessageInfo();
1068                if (! fetchInfo.hasBeenFetched()) {
1069                  fetchVector.add(fetchInfo);
1070                  fetchInfo.setFetched(true);
1071                }
1072              }
1073
1074              MessageInfo[] toFetch = new MessageInfo[fetchVector.size()];
1075              toFetch = (MessageInfo[]) fetchVector.toArray(toFetch);
1076              this.fetch(toFetch, fetchProfile);
1077            } catch(MessagingException me) {
1078              getLogger().log(Level.FINE, "caught error while fetching for folder " + getFolderID() + ": " + me);
1079              me.printStackTrace();
1080            }
1081
1082          }
1083          try {
1084            if (! mp.isLoaded())
1085              mp.loadTableInfo();
1086            if (mp.needsRefresh())
1087              mp.refreshMessage();
1088            else if (! mp.matchedFilters()) {
1089              mp.matchFilters();
1090            }
1091          } catch (Exception JavaDoc e) {
1092            e.printStackTrace();
1093          }
1094          i--;
1095        }
1096
1097      }
1098    }
1099  }
1100
1101  /**
1102   * Fetches the information for the given messages using the given
1103   * FetchProfile.
1104   */

1105  public void fetch(MessageInfo[] messages, FetchProfile profile) throws MessagingException {
1106    Message[] realMsgs = new Message[messages.length];
1107    for (int i = 0; i < realMsgs.length; i++) {
1108      realMsgs[i] = messages[i].getMessage();
1109    }
1110    getFolder().fetch(realMsgs, profile);
1111
1112    for (int i = 0 ; i < messages.length; i++) {
1113      messages[i].setFetched(true);
1114    }
1115  }
1116
1117  /**
1118   * Unloads all messages. This should be run if ever the current message
1119   * information becomes out of date, as can happen when the connection
1120   * to the folder goes down.
1121   */

1122  public void unloadAllMessages() {
1123    folderTableModel = null;
1124  }
1125
1126
1127  /**
1128   * Unloads all of the tableInfos of the MessageInfo objects. This
1129   * should be used either when the message information is stale, or when
1130   * the display rules have changed.
1131   */

1132  public void unloadTableInfos() {
1133    if (folderTableModel != null) {
1134      List JavaDoc allProxies = folderTableModel.getAllProxies();
1135      for (int i = 0; i < allProxies.size(); i++) {
1136        MessageProxy mp = (MessageProxy) allProxies.get(i);
1137        mp.unloadTableInfo();
1138      }
1139
1140      /*
1141        if (loaderThread != null)
1142        loaderThread.loadMessages(allProxies);
1143      */

1144
1145      if (mMessageLoader != null)
1146        mMessageLoader.loadMessages(allProxies);
1147
1148    }
1149  }
1150
1151  /**
1152   * Unloads the matching filters.
1153   */

1154  public void unloadMatchingFilters() {
1155    if (folderTableModel != null) {
1156      List JavaDoc allProxies = folderTableModel.getAllProxies();
1157      for (int i = 0; i < allProxies.size(); i++) {
1158        MessageProxy mp = (MessageProxy) allProxies.get(i);
1159        mp.clearMatchedFilters();
1160      }
1161
1162      /*
1163        if (loaderThread != null)
1164        loaderThread.loadMessages(allProxies);
1165      */

1166
1167      if (mMessageLoader != null)
1168        mMessageLoader.loadMessages(allProxies);
1169
1170    }
1171  }
1172
1173  /**
1174   * Refreshes the headers for the given MessageInfo.
1175   */

1176  public void refreshHeaders(MessageInfo mi) throws MessagingException {
1177    // no-op for default; only really used by UIDFolderInfos.
1178
}
1179
1180  /**
1181   * Refreshes the flags for the given MessageInfo.
1182   */

1183  public void refreshFlags(MessageInfo mi) throws MessagingException {
1184    // no-op for default; only really used by UIDFolderInfos.
1185
}
1186
1187
1188  /**
1189   * This just checks to see if we can get a NewMessageCount from the
1190   * folder. As a brute force method, it also accesses the folder
1191   * at every check. It's nasty, but it _should_ keep the Folder open..
1192   */

1193  public void checkFolder() throws javax.mail.MessagingException JavaDoc {
1194    getLogger().log(Level.FINE, "checking folder " + getFolderID());
1195
1196    // i'm taking this almost directly from ICEMail; i don't know how
1197
// to keep the stores/folders open, either. :)
1198

1199    if (isConnected()) {
1200      Folder current = getFolder();
1201      if (current != null && current.isOpen()) {
1202        current.getNewMessageCount();
1203        current.getUnreadMessageCount();
1204      }
1205      resetMessageCounts();
1206    }
1207  }
1208
1209  /**
1210   * Gets the row number of the first unread message. Returns -1 if
1211   * there are no unread messages, or if the FolderTableModel is not
1212   * set or empty.
1213   */

1214  public int getFirstUnreadMessage() {
1215    getLogger().log(Level.FINE, "getting first unread message");
1216
1217    if (! tracksUnreadMessages())
1218      return -1;
1219
1220    if (getFolderTableModel() == null)
1221      return -1;
1222
1223    try {
1224      int countUnread = 0;
1225      int i;
1226      if (unreadCount > 0) {
1227
1228        // one part brute, one part force, one part ignorance.
1229

1230        Message[] messages = getFolder().getMessages();
1231        int lastUnreadFound = -1;
1232        for (i = messages.length - 1; ( i >= 0 && countUnread < unreadCount) ; i--) {
1233          if (!(messages[i].isSet(Flags.Flag.SEEN))) {
1234            lastUnreadFound = i;
1235            countUnread++;
1236          }
1237        }
1238        if (lastUnreadFound != -1) {
1239          getLogger().log(Level.FINE, "Returning " + (lastUnreadFound + 1));
1240          return lastUnreadFound;
1241        } else {
1242          getLogger().log(Level.FINE, "unreads detected, but none found.");
1243          return -1;
1244        }
1245
1246      } else {
1247        getLogger().log(Level.FINE, "Returning -1");
1248        return -1;
1249      }
1250    } catch (MessagingException me) {
1251      getLogger().log(Level.FINE, "Messaging Exception. Returning -1");
1252      return -1;
1253    }
1254  }
1255
1256  /**
1257   * This updates the children of the current folder. Generally called
1258   * when the folderList property is changed.
1259   *
1260   * Should be called on the folder thread.
1261   */

1262  public void updateChildren() {
1263    Vector newChildren = new Vector();
1264
1265    String JavaDoc childList = Pooka.getProperty(getFolderProperty() + ".folderList", "");
1266    if (childList != "") {
1267      StringTokenizer tokens = new StringTokenizer(childList, ":");
1268
1269      String JavaDoc newFolderName;
1270
1271      for (int i = 0 ; tokens.hasMoreTokens() ; i++) {
1272        newFolderName = (String JavaDoc)tokens.nextToken();
1273        FolderInfo childFolder = getChild(newFolderName);
1274        if (childFolder == null) {
1275          childFolder = new FolderInfo(this, newFolderName);
1276          newChildren.add(childFolder);
1277        } else {
1278          newChildren.add(childFolder);
1279        }
1280      }
1281
1282      children = newChildren;
1283
1284      if (folderNode != null)
1285        folderNode.loadChildren();
1286    }
1287  }
1288
1289  /**
1290   * This goes through the list of children of this folder and
1291   * returns the FolderInfo for the given childName, if one exists.
1292   * If none exists, or if the children Vector has not been loaded
1293   * yet, or if this is a leaf node, then this method returns null.
1294   */

1295  public FolderInfo getChild(String JavaDoc childName) {
1296    getLogger().log(Level.FINE, "folder " + getFolderID() + " getting child " + childName);
1297
1298    FolderInfo childFolder = null;
1299    String JavaDoc folderName = null, subFolderName = null;
1300
1301    if (children != null) {
1302      int divider = childName.indexOf('/');
1303      if (divider > 0) {
1304        folderName = childName.substring(0, divider);
1305        if (divider < childName.length() - 1)
1306          subFolderName = childName.substring(divider + 1);
1307      } else
1308        folderName = childName;
1309
1310      getLogger().log(Level.FINE, "getting direct child " + folderName);
1311
1312      for (int i = 0; i < children.size(); i++)
1313        if (((FolderInfo)children.elementAt(i)).getFolderName().equals(folderName))
1314          childFolder = (FolderInfo)children.elementAt(i);
1315    } else {
1316      getLogger().log(Level.FINE, "children of " + getFolderID() + " is null.");
1317    }
1318
1319    if (childFolder != null && subFolderName != null)
1320      return childFolder.getChild(subFolderName);
1321    else
1322      return childFolder;
1323  }
1324
1325  /**
1326   * This goes through the list of children of this store and
1327   * returns the FolderInfo that matches this folderID.
1328   * If none exists, or if the children Vector has not been loaded
1329   * yet, or if this is a leaf node, then this method returns null.
1330   */

1331  public FolderInfo getFolderById(String JavaDoc folderID) {
1332    FolderInfo childFolder = null;
1333
1334    if (getFolderID().equals(folderID))
1335      return this;
1336
1337    if (children != null) {
1338      for (int i = 0; i < children.size(); i++) {
1339        FolderInfo possibleMatch = ((FolderInfo)children.elementAt(i)).getFolderById(folderID);
1340        if (possibleMatch != null) {
1341          return possibleMatch;
1342        }
1343      }
1344    }
1345
1346    return null;
1347  }
1348
1349  /**
1350   * creates the loader thread.
1351   */

1352  public LoadMessageThread createLoaderThread() {
1353    LoadMessageThread lmt = new LoadMessageThread(this);
1354    return lmt;
1355  }
1356
1357  /**
1358   * creates the MessageLoader.
1359   */

1360  public MessageLoader createMessageLoader() {
1361    MessageLoader ml = new MessageLoader(this);
1362    return ml;
1363  }
1364
1365  /**
1366   * Returns the MessageLoader for this FolderInfo.
1367   */

1368  public MessageLoader getMessageLoader() {
1369    return mMessageLoader;
1370  }
1371
1372
1373  /**
1374   * gets the 'real' message for the given MessageInfo.
1375   */

1376  public Message getRealMessage(MessageInfo mi) throws MessagingException {
1377    return mi.getMessage();
1378  }
1379
1380  /**
1381   * This sets the given Flag for all the MessageInfos given.
1382   */

1383  public void setFlags(MessageInfo[] msgs, Flags flag, boolean value) throws MessagingException {
1384    Message[] m = new Message[msgs.length];
1385    for (int i = 0; i < msgs.length; i++) {
1386      m[i] = msgs[i].getRealMessage();
1387    }
1388
1389    getFolder().setFlags(m, flag, value);
1390  }
1391
1392  /**
1393   * This copies the given messages to the given FolderInfo.
1394   */

1395  public void copyMessages(MessageInfo[] msgs, FolderInfo targetFolder) throws MessagingException {
1396    if (targetFolder == null) {
1397      throw new MessagingException(Pooka.getProperty("error.null", "Error: null folder"));
1398    } else if (targetFolder.getStatus() == INVALID) {
1399      throw new MessagingException(Pooka.getProperty("error.folderInvalid", "Error: folder is invalid. ") + targetFolder.getFolderID());
1400    }
1401
1402    if (! targetFolder.isAvailable())
1403      targetFolder.loadFolder();
1404
1405    synchronized(targetFolder.getFolderThread().getRunLock()) {
1406      Folder target = targetFolder.getFolder();
1407      if (target != null) {
1408        Message[] m = new Message[msgs.length];
1409        for (int i = 0; i < msgs.length; i++) {
1410          m[i] = msgs[i].getRealMessage();
1411        }
1412
1413        getFolder().copyMessages(m, target);
1414
1415        // if we do a copy, we'll probably need to do a refresh on the target
1416
// folder, also.
1417

1418        targetFolder.checkFolder();
1419
1420      } else {
1421        targetFolder.appendMessages(msgs);
1422      }
1423    }
1424  }
1425
1426  /**
1427   * This appends the given message to the given FolderInfo.
1428   */

1429  public void appendMessages(MessageInfo[] msgs) throws MessagingException {
1430    if (! isSortaOpen())
1431      openFolder(Folder.READ_WRITE);
1432    Message[] m = new Message[msgs.length];
1433    for (int i = 0; i < msgs.length; i++) {
1434      m[i] = msgs[i].getRealMessage();
1435    }
1436
1437    getFolder().appendMessages(m);
1438  }
1439
1440  /**
1441   * This expunges the deleted messages from the Folder.
1442   */

1443  public void expunge() throws MessagingException {
1444    getFolder().expunge();
1445  }
1446
1447  /**
1448   * This handles the MessageLoadedEvent.
1449   *
1450   * As defined in interface net.suberic.pooka.event.MessageLoadedListener.
1451   */

1452
1453  public void fireMessageChangedEvent(MessageChangedEvent mce) {
1454    // from the EventListenerList javadoc, including comments.
1455

1456    // Guaranteed to return a non-null array
1457
Object JavaDoc[] listeners = eventListeners.getListenerList();
1458    // Process the listeners last to first, notifying
1459
// those that are interested in this event
1460
for (int i = listeners.length-2; i>=0; i-=2) {
1461      if (listeners[i]==MessageChangedListener.class) {
1462        ((MessageChangedListener)listeners[i+1]).messageChanged(mce);
1463      }
1464    }
1465  }
1466
1467  public void addConnectionListener(ConnectionListener newListener) {
1468    eventListeners.add(ConnectionListener.class, newListener);
1469  }
1470
1471  public void removeConnectionListener(ConnectionListener oldListener) {
1472    eventListeners.remove(ConnectionListener.class, oldListener);
1473  }
1474
1475
1476  /**
1477   * This handles the distributions of any Connection events.
1478   *
1479   * As defined in interface net.suberic.pooka.event.MessageLoadedListener.
1480   */

1481  public void fireConnectionEvent(ConnectionEvent e) {
1482    // from the EventListenerList javadoc, including comments.
1483

1484    // Guaranteed to return a non-null array
1485
Object JavaDoc[] listeners = eventListeners.getListenerList();
1486    // Process the listeners last to first, notifying
1487
// those that are interested in this event
1488
for (int i = listeners.length-2; i>=0; i-=2) {
1489      if (listeners[i]==ConnectionListener.class) {
1490        ConnectionListener listener = (ConnectionListener) listeners[i+1];
1491        if (e.getType() == ConnectionEvent.CLOSED)
1492          listener.closed(e);
1493        else if (e.getType() == ConnectionEvent.DISCONNECTED)
1494          listener.disconnected(e);
1495        else if (e.getType() == ConnectionEvent.OPENED)
1496          listener.opened(e);
1497      }
1498    }
1499  }
1500
1501  /**
1502   * This handles the changes if the source property is modified.
1503   *
1504   * As defined in net.suberic.util.ValueChangeListener.
1505   */

1506  public void valueChanged(String JavaDoc changedValue) {
1507    if (changedValue.equals(getFolderProperty() + ".folderList")) {
1508      final Runnable JavaDoc runMe = new Runnable JavaDoc() {
1509          public void run() {
1510            ((javax.swing.tree.DefaultTreeModel JavaDoc)(((FolderPanel)folderNode.getParentContainer()).getFolderTree().getModel())).nodeStructureChanged(folderNode);
1511          }
1512        };
1513      // if we don't do the update synchronously on the folder thread,
1514
// then subscribing to subfolders breaks.
1515
if (Thread.currentThread() != getFolderThread()) {
1516        getFolderThread().addToQueue(new javax.swing.AbstractAction JavaDoc() {
1517            public void actionPerformed(java.awt.event.ActionEvent JavaDoc e) {
1518              updateChildren();
1519              if (folderNode != null) {
1520                javax.swing.SwingUtilities.invokeLater(runMe);
1521              }
1522            }
1523          } , new java.awt.event.ActionEvent JavaDoc(this, 0, "open-all"));
1524      } else {
1525        updateChildren();
1526        if (folderNode != null) {
1527          javax.swing.SwingUtilities.invokeLater(runMe);
1528        }
1529      }
1530    } else if (changedValue.equals(getFolderProperty() + ".defaultProfile")) {
1531      String JavaDoc newProfileValue = Pooka.getProperty(changedValue, "");
1532      if (newProfileValue.length() < 1 || newProfileValue.equals(UserProfile.S_DEFAULT_PROFILE_KEY))
1533        defaultProfile = null;
1534      else
1535        defaultProfile = Pooka.getPookaManager().getUserProfileManager().getProfile(newProfileValue);
1536    } else if (changedValue.equals(getFolderProperty() + ".backendFilters")) {
1537      createFilters();
1538
1539    } else if (changedValue.equals(getFolderProperty() + ".displayFilters")) {
1540      createFilters();
1541      unloadMatchingFilters();
1542    } else if (changedValue.equals(getFolderProperty() + ".notifyNewMessagesMain") || changedValue.equals(getFolderProperty() + ".notifyNewMessagesNode")) {
1543      setNotifyNewMessagesMain(!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesMain", "").equalsIgnoreCase("false"));
1544
1545      setNotifyNewMessagesNode(!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesNode", "").equalsIgnoreCase("false"));
1546    }
1547  }
1548
1549  /**
1550   * This creates a folder if it doesn't exist already. If it does exist,
1551   * but is not of the right type, or if there is a problem in creating the
1552   * folder, throws an error.
1553   */

1554  public void createSubFolder(String JavaDoc subFolderName, int type) throws MessagingException {
1555    if ( ! isLoaded()) {
1556      loadFolder();
1557    }
1558
1559    if (folder != null) {
1560      Folder subFolder = folder.getFolder(subFolderName);
1561
1562      if (subFolder == null) {
1563        throw new MessagingException("Store returned null for subfolder " + subFolderName + " of folder " + getFolderName());
1564      }
1565
1566      if (! subFolder.exists())
1567        subFolder.create(type);
1568
1569      subscribeFolder(subFolderName);
1570    } else {
1571      throw new MessagingException("Failed to open folder " + getFolderName() + " to create subfolder " + subFolderName);
1572    }
1573  }
1574
1575  /**
1576   * This subscribes to the FolderInfo indicated by the given String.
1577   * If this defines a subfolder, then that subfolder is added to this
1578   * FolderInfo, if it doesn't already exist.
1579   */

1580  public void subscribeFolder(String JavaDoc folderName) {
1581    getLogger().log(Level.FINE, "Folder " + getFolderID() + " subscribing subfolder " + folderName);
1582
1583    String JavaDoc subFolderName = null;
1584    String JavaDoc childFolderName = null;
1585    int firstSlash = folderName.indexOf('/');
1586    while (firstSlash == 0) {
1587      folderName = folderName.substring(1);
1588      firstSlash = folderName.indexOf('/');
1589    }
1590
1591    if (firstSlash > 0) {
1592      childFolderName = folderName.substring(0, firstSlash);
1593      if (firstSlash < folderName.length() -1)
1594        subFolderName = folderName.substring(firstSlash +1);
1595    } else
1596      childFolderName = folderName;
1597
1598    getLogger().log(Level.FINE, "Folder " + getFolderID() + " subscribing folder " + childFolderName + ", plus subfolder " + subFolderName);
1599
1600    this.addToFolderList(childFolderName);
1601
1602    FolderInfo childFolder = getChild(childFolderName);
1603
1604    getLogger().log(Level.FINE, "got child folder " + childFolder + " from childFolderName " + childFolderName);
1605
1606
1607    if (childFolder != null && subFolderName != null) {
1608      childFolder.subscribeFolder(subFolderName);
1609    }
1610
1611    try {
1612      if (childFolder != null && childFolder.isLoaded() == false)
1613        childFolder.loadFolder();
1614    } catch (MessagingException me) {
1615      // if we get an exception loading a child folder while subscribing a
1616
// folder object, just ignore it.
1617
getLogger().log(Level.FINE, Thread.currentThread() + "loading folder " + getFolderID() + ": caught messaging exception from parentStore getting folder: " + me);
1618      if (getLogger().isLoggable(Level.FINE))
1619        me.printStackTrace();
1620
1621    }
1622
1623    updateChildren();
1624  }
1625
1626  /**
1627   * This adds the given folderString to the folderList of this
1628   * FolderInfo.
1629   */

1630  void addToFolderList(String JavaDoc addFolderName) {
1631    Vector folderNames = Pooka.getResources().getPropertyAsVector(getFolderProperty() + ".folderList", "");
1632
1633    boolean found = false;
1634
1635    for (int i = 0; i < folderNames.size(); i++) {
1636      String JavaDoc folderName = (String JavaDoc) folderNames.elementAt(i);
1637
1638      if (folderName.equals(addFolderName)) {
1639        found=true;
1640      }
1641
1642    }
1643
1644    if (!found) {
1645      String JavaDoc currentValue = Pooka.getProperty(getFolderProperty() + ".folderList", "");
1646      if (currentValue.equals(""))
1647        Pooka.setProperty(getFolderProperty() + ".folderList", addFolderName);
1648      else
1649        Pooka.setProperty(getFolderProperty() + ".folderList", currentValue + ":" + addFolderName);
1650    }
1651
1652  }
1653
1654  /**
1655   * Remove the given String from the folderList property.
1656   *
1657   * Note that because this is also a ValueChangeListener to the
1658   * folderList property, this will also result in the FolderInfo being
1659   * removed from the children Vector.
1660   */

1661  void removeFromFolderList(String JavaDoc removeFolderName) {
1662    Vector folderNames = Pooka.getResources().getPropertyAsVector(getFolderProperty() + ".folderList", "");
1663
1664    boolean first = true;
1665    StringBuffer JavaDoc newValue = new StringBuffer JavaDoc();
1666    String JavaDoc folderName;
1667
1668    for (int i = 0; i < folderNames.size(); i++) {
1669      folderName = (String JavaDoc) folderNames.elementAt(i);
1670
1671      if (! folderName.equals(removeFolderName)) {
1672        if (!first)
1673          newValue.append(":");
1674
1675        newValue.append(folderName);
1676        first = false;
1677      }
1678
1679    }
1680
1681    Pooka.setProperty(getFolderProperty() + ".folderList", newValue.toString());
1682  }
1683
1684  /**
1685   * This unsubscribes this FolderInfo and all of its children, if
1686   * applicable.
1687   *
1688   * This implementation just removes the defining properties from
1689   * the Pooka resources.
1690   */

1691  public void unsubscribe() {
1692
1693    cleanup();
1694
1695    if (parentFolder != null)
1696      parentFolder.removeFromFolderList(getFolderName());
1697    else if (parentStore != null)
1698      parentStore.removeFromFolderList(getFolderName());
1699
1700    try {
1701      if (folder != null)
1702        folder.setSubscribed(false);
1703    } catch (MessagingException me) {
1704      Pooka.getUIFactory().showError(Pooka.getProperty("error.folder.unsubscribe", "Error unsubscribing on server from folder ") + getFolderID(), me);
1705    }
1706  }
1707
1708  /**
1709   * This deletes the underlying Folder.
1710   */

1711  public void delete() throws MessagingException {
1712    if (! isLoaded())
1713      loadFolder();
1714
1715    Folder f = getFolder();
1716    if (f == null)
1717      throw new MessagingException("No folder.");
1718
1719    unsubscribe();
1720
1721    if (f.isOpen())
1722      f.close(true);
1723    f.delete(true);
1724  }
1725
1726  /**
1727   * Cleans up all references to this folder.
1728   */

1729  protected void cleanup() {
1730
1731    Pooka.getResources().removeValueChangeListener(this);
1732    Folder f = getFolder();
1733    if (f != null) {
1734      removeFolderListeners();
1735    }
1736
1737    if (children != null && children.size() > 0) {
1738      for (int i = 0; i < children.size(); i++)
1739        ((FolderInfo)children.elementAt(i)).cleanup();
1740    }
1741
1742    Pooka.getLogManager().removeLogger(getFolderProperty());
1743
1744    if (getFolderDisplayUI() != null)
1745      getFolderDisplayUI().closeFolderDisplay();
1746
1747  }
1748
1749  /**
1750   * This returns whether or not this Folder is set up to use the
1751   * TrashFolder for the Store. If this is a Trash Folder itself,
1752   * then return false. If FolderProperty.useTrashFolder is set,
1753   * return that. else go up the tree, until, in the end,
1754   * Pooka.useTrashFolder is returned.
1755   */

1756  public boolean useTrashFolder() {
1757    if (isTrashFolder())
1758      return false;
1759
1760    String JavaDoc prop = Pooka.getProperty(getFolderProperty() + ".useTrashFolder", "");
1761    if (!prop.equals(""))
1762      return (! prop.equalsIgnoreCase("false"));
1763
1764    if (getParentFolder() != null)
1765      return getParentFolder().useTrashFolder();
1766    else if (getParentStore() != null)
1767      return getParentStore().useTrashFolder();
1768    else
1769      return (! Pooka.getProperty("Pooka.useTrashFolder", "true").equalsIgnoreCase("true"));
1770  }
1771
1772  /**
1773   * This removes all the messages in the folder, if the folder is a
1774   * TrashFolder.
1775   */

1776  public void emptyTrash() {
1777    if (isTrashFolder()) {
1778      try {
1779        Message[] allMessages = getFolder().getMessages();
1780        getFolder().setFlags(allMessages, new Flags(Flags.Flag.DELETED), true);
1781        getFolder().expunge();
1782      } catch (MessagingException me) {
1783        String JavaDoc m = Pooka.getProperty("error.trashFolder.EmptyTrashError", "Error emptying Trash:") +"\n" + me.getMessage();
1784        if (getFolderDisplayUI() != null)
1785          getFolderDisplayUI().showError(m);
1786        else
1787          getLogger().log(Level.FINE, m);
1788      }
1789    }
1790  }
1791
1792  /**
1793   * This resets the defaultActions. Useful when this goes to and from
1794   * being a trashFolder, since only trash folders have emptyTrash
1795   * actions.
1796   */

1797  public void resetDefaultActions() {
1798    if (isTrashFolder()) {
1799      defaultActions = new Action JavaDoc[] {
1800        new net.suberic.util.thread.ActionWrapper(new UpdateCountAction(), getFolderThread()),
1801        new net.suberic.util.thread.ActionWrapper(new EmptyTrashAction(), getFolderThread()),
1802        new EditPropertiesAction()
1803      };
1804    } else if (isOutboxFolder()) {
1805      defaultActions = new Action JavaDoc[] {
1806        new net.suberic.util.thread.ActionWrapper(new UpdateCountAction(), getFolderThread()),
1807        new net.suberic.util.thread.ActionWrapper(new SendAllAction(), getFolderThread()),
1808        new EditPropertiesAction()
1809      };
1810
1811    } else {
1812      defaultActions = new Action JavaDoc[] {
1813        new net.suberic.util.thread.ActionWrapper(new UpdateCountAction(), getFolderThread()),
1814        new EditPropertiesAction()
1815      };
1816    }
1817  }
1818
1819  // semi-accessor methods.
1820

1821  public MessageProxy getMessageProxy(int rowNumber) {
1822    return getFolderTableModel().getMessageProxy(rowNumber);
1823  }
1824
1825  public MessageInfo getMessageInfo(Message m) {
1826    return (MessageInfo)messageToInfoTable.get(m);
1827  }
1828
1829  public void addMessageCountListener(MessageCountListener newListener) {
1830    eventListeners.add(MessageCountListener.class, newListener);
1831  }
1832
1833  public void removeMessageCountListener(MessageCountListener oldListener) {
1834    eventListeners.remove(MessageCountListener.class, oldListener);
1835  }
1836
1837  public void fireMessageCountEvent(MessageCountEvent mce) {
1838
1839    // from the EventListenerList javadoc, including comments.
1840

1841    // Guaranteed to return a non-null array
1842
Object JavaDoc[] listeners = eventListeners.getListenerList();
1843    // Process the listeners last to first, notifying
1844
// those that are interested in this event
1845

1846    if (mce.getType() == MessageCountEvent.ADDED) {
1847      for (int i = listeners.length-2; i>=0; i-=2) {
1848        if (listeners[i]==MessageCountListener.class) {
1849          ((MessageCountListener)listeners[i+1]).messagesAdded(mce);
1850        }
1851      }
1852    } else if (mce.getType() == MessageCountEvent.REMOVED) {
1853      for (int i = listeners.length-2; i>=0; i-=2) {
1854        if (listeners[i]==MessageCountListener.class) {
1855
1856          ((MessageCountListener)listeners[i+1]).messagesRemoved(mce);
1857        }
1858      }
1859
1860    }
1861  }
1862
1863  public void addMessageChangedListener(MessageChangedListener newListener) {
1864    eventListeners.add(MessageChangedListener.class, newListener);
1865  }
1866
1867  public void removeMessageChangedListener(MessageChangedListener oldListener) {
1868    eventListeners.remove(MessageChangedListener.class, oldListener);
1869  }
1870
1871  // as defined in javax.mail.event.MessageCountListener
1872

1873  public void messagesAdded(MessageCountEvent e) {
1874    getLogger().log(Level.FINE, "Messages added.");
1875
1876    if (Thread.currentThread() == getFolderThread() )
1877      runMessagesAdded(e);
1878    else
1879      getFolderThread().addToQueue(new net.suberic.util.thread.ActionWrapper(new javax.swing.AbstractAction JavaDoc() {
1880
1881          public void actionPerformed(java.awt.event.ActionEvent JavaDoc actionEvent) {
1882            runMessagesAdded((MessageCountEvent)actionEvent.getSource());
1883          }
1884        }, getFolderThread()), new java.awt.event.ActionEvent JavaDoc(e, 1, "message-count-changed"));
1885
1886  }
1887
1888  protected void runMessagesAdded(MessageCountEvent mce) {
1889    getLogger().log(Level.FINE, "running messagesAdded on FolderInfo.");
1890
1891    if (folderTableModel != null) {
1892      Message[] addedMessages = mce.getMessages();
1893
1894      MessageInfo mp;
1895      Vector addedProxies = new Vector();
1896      getLogger().log(Level.FINE, "running messagesAdded: creating " + addedMessages.length + " proxies/MessageInfos.");
1897
1898      for (int i = 0; i < addedMessages.length; i++) {
1899        mp = new MessageInfo(addedMessages[i], FolderInfo.this);
1900        addedProxies.add(new MessageProxy(getColumnValues(), mp));
1901        messageToInfoTable.put(addedMessages[i], mp);
1902      }
1903      getLogger().log(Level.FINE, "filtering proxies.");
1904
1905      addedProxies.removeAll(applyFilters(addedProxies));
1906      if (addedProxies.size() > 0) {
1907        getLogger().log(Level.FINE, "filters run; adding " + addedProxies.size() + " messages.");
1908
1909        getFolderTableModel().addRows(addedProxies);
1910        setNewMessages(true);
1911        resetMessageCounts();
1912
1913        // notify the message loaded thread.
1914
MessageProxy[] addedArray = (MessageProxy[]) addedProxies.toArray(new MessageProxy[0]);
1915        mMessageLoader.loadMessages(addedArray, MessageLoader.HIGH);
1916
1917        fireMessageCountEvent(mce);
1918      }
1919    }
1920
1921  }
1922
1923  /**
1924   * As defined in javax.mail.MessageCountListener.
1925   *
1926   * This runs when we get a notification that messages have been
1927   * removed from the mail server.
1928   *
1929   * This implementation just moves the handling of the event to the
1930   * FolderThread, where it runs runMessagesRemoved().
1931   */

1932  public void messagesRemoved(MessageCountEvent e) {
1933    getLogger().log(Level.FINE, "Messages Removed.");
1934
1935    if (Thread.currentThread() == getFolderThread() ) {
1936      runMessagesRemoved(e);
1937    } else {
1938      getFolderThread().addToQueue(new net.suberic.util.thread.ActionWrapper(new javax.swing.AbstractAction JavaDoc() {
1939          public void actionPerformed(java.awt.event.ActionEvent JavaDoc actionEvent) {
1940            runMessagesRemoved((MessageCountEvent)actionEvent.getSource());
1941          }
1942        }, getFolderThread()), new java.awt.event.ActionEvent JavaDoc(e, 1, "messages-removed"));
1943    }
1944  }
1945
1946  /**
1947   * This does the real work when messages are removed. This can be
1948   * overridden by subclasses.
1949   */

1950  protected void runMessagesRemoved(MessageCountEvent mce) {
1951    getLogger().log(Level.FINE, "running MessagesRemoved on " + getFolderID());
1952
1953    if (folderTableModel != null) {
1954      Message[] removedMessages = mce.getMessages();
1955      getLogger().log(Level.FINE, "removedMessages was of size " + removedMessages.length);
1956      MessageInfo mi;
1957      Vector removedProxies=new Vector();
1958
1959      if (getLogger().isLoggable(Level.FINE)) {
1960        getLogger().log(Level.FINE, "message in info table:");
1961        Enumeration keys = messageToInfoTable.keys();
1962        while (keys.hasMoreElements())
1963          getLogger().log(Level.FINE, (String JavaDoc)keys.nextElement());
1964      }
1965
1966      for (int i = 0; i < removedMessages.length; i++) {
1967        getLogger().log(Level.FINE, "checking for existence of message " + removedMessages[i]);
1968        mi = getMessageInfo(removedMessages[i]);
1969        if (mi != null) {
1970          if (mi.getMessageProxy() != null)
1971            mi.getMessageProxy().close();
1972
1973          getLogger().log(Level.FINE, "message exists--removing");
1974          removedProxies.add(mi.getMessageProxy());
1975          messageToInfoTable.remove(removedMessages[i]);
1976        }
1977      }
1978      if (getFolderDisplayUI() != null) {
1979        if (removedProxies.size() > 0)
1980          getFolderDisplayUI().removeRows(removedProxies);
1981        resetMessageCounts();
1982        fireMessageCountEvent(mce);
1983      } else {
1984        resetMessageCounts();
1985        fireMessageCountEvent(mce);
1986        if (removedProxies.size() > 0)
1987          getFolderTableModel().removeRows(removedProxies);
1988      }
1989    } else {
1990      resetMessageCounts();
1991      fireMessageCountEvent(mce);
1992    }
1993  }
1994
1995  /**
1996   * This updates the TableInfo on the changed messages.
1997   *
1998   * As defined by java.mail.MessageChangedListener.
1999   */

2000  public void messageChanged(MessageChangedEvent e) {
2001    // blech. we really have to do this on the action thread.
2002

2003    if (Thread.currentThread() == getFolderThread() )
2004      runMessageChanged(e);
2005    else
2006      getFolderThread().addToQueue(new net.suberic.util.thread.ActionWrapper(new javax.swing.AbstractAction JavaDoc() {
2007          public void actionPerformed(java.awt.event.ActionEvent JavaDoc actionEvent) {
2008            runMessageChanged((MessageChangedEvent)actionEvent.getSource());
2009          }
2010        }, getFolderThread()), new java.awt.event.ActionEvent JavaDoc(e, 1, "message-changed"));
2011  }
2012
2013
2014  protected void runMessageChanged(MessageChangedEvent mce) {
2015    // if the message is getting deleted, then we don't
2016
// really need to update the table info. for that
2017
// matter, it's likely that we'll get MessagingExceptions
2018
// if we do, anyway.
2019
boolean updateInfo = false;
2020    try {
2021      updateInfo = (!mce.getMessage().isSet(Flags.Flag.DELETED) || ! Pooka.getProperty("Pooka.autoExpunge", "true").equalsIgnoreCase("true"));
2022    } catch (MessagingException me) {
2023      // if we catch a MessagingException, it just means
2024
// that the message has already been expunged. in
2025
// that case, assume it's ok if we don't update; it'll
2026
// happen in the messagesRemoved().
2027
}
2028
2029    if (updateInfo) {
2030      try {
2031        MessageInfo mi = getMessageInfo(mce.getMessage());
2032        MessageProxy mp = mi.getMessageProxy();
2033        if (mp != null) {
2034          mp.unloadTableInfo();
2035          mp.loadTableInfo();
2036          if (mce.getMessageChangeType() == MessageChangedEvent.FLAGS_CHANGED)
2037            mi.refreshFlags();
2038          else if (mce.getMessageChangeType() == MessageChangedEvent.ENVELOPE_CHANGED)
2039            mi.refreshHeaders();
2040        }
2041      } catch (MessagingException me) {
2042        // if we catch a MessagingException, it just means
2043
// that the message has already been expunged.
2044
}
2045
2046      // if we're not just a tableinfochanged event, do a resetmessagecouts.
2047
// don't do this if we're just a delete.
2048
if (! (mce instanceof net.suberic.pooka.event.MessageTableInfoChangedEvent)) {
2049        resetMessageCounts();
2050      }
2051    }
2052
2053    fireMessageChangedEvent(mce);
2054  }
2055
2056  /**
2057   * This puts up the gui for the Search.
2058   */

2059  public void showSearchFolder() {
2060    Pooka.getUIFactory().showSearchForm(new FolderInfo[] { this });
2061  }
2062
2063  /**
2064   * This is a static calls which searches the given FolderInfo objects,
2065   * collects the results into a VirtualFolderInfo, and then displays
2066   * the results of the search in the UI.
2067   */

2068  public static void searchFolders(Vector folderList, javax.mail.search.SearchTerm JavaDoc term) {
2069    final javax.mail.search.SearchTerm JavaDoc searchTerm = term;
2070    final Vector selectedFolders = folderList;
2071
2072    Pooka.getSearchThread().addToQueue(new net.suberic.util.thread.ActionWrapper(new javax.swing.AbstractAction JavaDoc() {
2073        public void actionPerformed(ActionEvent JavaDoc e) {
2074          Vector matchingValues = new Vector();
2075          Logger.getLogger("Pooka.debug").log(Level.FINE, "init: matchingValues.size() = " + matchingValues.size());
2076
2077          net.suberic.util.swing.ProgressDialog dialog = Pooka.getUIFactory().createProgressDialog(0,100,0,"Search","Searching");
2078          dialog.show();
2079          boolean cancelled = dialog.isCancelled();
2080
2081          for (int i = 0; ! cancelled && i < selectedFolders.size(); i++) {
2082            Logger.getLogger("Pooka.debug").log(Level.FINE, "trying selected folder number " + i);
2083            try {
2084              net.suberic.pooka.MessageInfo[] matches = ((FolderInfo) selectedFolders.elementAt(i)).search(searchTerm);
2085              Logger.getLogger("Pooka.debug").log(Level.FINE, "matches.length = " + matches.length);
2086              for (int j = 0; j < matches.length; j++) {
2087                matchingValues.add(matches[j]);
2088                Logger.getLogger("Pooka.debug").log(Level.FINE, "adding " + matches[j] + " to matchingValues.");
2089              }
2090
2091            } catch (MessagingException me) {
2092              Logger.getLogger("Pooka.debug").log(Level.FINE, "caught exception " + me);
2093            }
2094            cancelled = dialog.isCancelled();
2095          }
2096
2097          Logger.getLogger("Pooka.debug").log(Level.FINE, "got " + matchingValues.size() + " matches.");
2098
2099          if (! cancelled) {
2100            FolderInfo[] parentFolders = new FolderInfo[selectedFolders.size()];
2101            for (int i = 0; i < selectedFolders.size(); i++) {
2102              parentFolders[i] = (FolderInfo) selectedFolders.elementAt(i);
2103            }
2104
2105            MessageInfo[] matchingMessages = new MessageInfo[matchingValues.size()];
2106            for (int i = 0; i < matchingValues.size(); i++) {
2107              Logger.getLogger("Pooka.debug").log(Level.FINE, "matchingValues.elementAt(" + i + ") = " + matchingValues.elementAt(i));
2108              matchingMessages[i] = (MessageInfo) matchingValues.elementAt(i);
2109            }
2110
2111            final VirtualFolderInfo sfi = new VirtualFolderInfo(matchingMessages, parentFolders);
2112
2113            Runnable JavaDoc runMe = new Runnable JavaDoc() {
2114                public void run() {
2115                  FolderDisplayUI fdui = Pooka.getUIFactory().createFolderDisplayUI(sfi);
2116                  fdui.openFolderDisplay();
2117                }
2118              };
2119
2120            javax.swing.SwingUtilities.invokeLater(runMe);
2121          }
2122
2123          dialog.dispose();
2124        }
2125      }, Pooka.getSearchThread()), new java.awt.event.ActionEvent JavaDoc(FolderInfo.class, 1, "search"));
2126
2127  }
2128
2129  /**
2130   * Searches for messages in this folder which match the given
2131   * SearchTerm.
2132   *
2133   * Basically wraps the call to Folder.search(), and then wraps the
2134   * returned Message objects as MessageInfos.
2135   */

2136  public MessageInfo[] search(javax.mail.search.SearchTerm JavaDoc term)
2137    throws MessagingException {
2138    if (folderTableModel == null)
2139      loadAllMessages();
2140
2141    Message[] matchingMessages = folder.search(term);
2142    MessageInfo returnValue[] = new MessageInfo[matchingMessages.length];
2143    for (int i = 0; i < matchingMessages.length; i++) {
2144      getLogger().log(Level.FINE, "match " + i + " = " + matchingMessages[i]);
2145      MessageInfo info = getMessageInfo(matchingMessages[i]);
2146      getLogger().log(Level.FINE, "messageInfo " + i + " = " + info);
2147      returnValue[i] = info;
2148    }
2149    getLogger().log(Level.FINE, "got " + returnValue.length + " results.");
2150    return returnValue;
2151  }
2152
2153  /**
2154   * The resource for the default display filters.
2155   */

2156  protected String JavaDoc getDefaultDisplayFiltersResource() {
2157    if (isSentFolder())
2158      return "FolderInfo.sentFolderDefaultDisplayFilters";
2159    else
2160      return "FolderInfo.defaultDisplayFilters";
2161  }
2162
2163  List JavaDoc filterHeaders = null;
2164
2165  /**
2166   * This takes the FolderProperty.backendFilters and
2167   * FolderProperty.displayFilters properties and uses them to populate
2168   * the backendMessageFilters and displayMessageFilters arrays.
2169   */

2170  public void createFilters() {
2171    BackendMessageFilter[] tmpBackendFilters = null;
2172    MessageFilter[] tmpDisplayFilters = null;
2173    Vector backendFilterNames=Pooka.getResources().getPropertyAsVector(getFolderProperty() + ".backendFilters", "");
2174
2175    if (backendFilterNames != null && backendFilterNames.size() > 0) {
2176
2177      tmpBackendFilters = new BackendMessageFilter[backendFilterNames.size()];
2178      for (int i = 0; i < backendFilterNames.size(); i++) {
2179        tmpBackendFilters[i] = new BackendMessageFilter(getFolderProperty() + ".backendFilters." + (String JavaDoc) backendFilterNames.elementAt(i));
2180      }
2181
2182      backendFilters = tmpBackendFilters;
2183    }
2184
2185    Vector foundFilters = new Vector();
2186    Vector defaultFilterNames = Pooka.getResources().getPropertyAsVector(getDefaultDisplayFiltersResource(), "");
2187
2188    for (int i = 0; i < defaultFilterNames.size(); i++) {
2189      foundFilters.add(new MessageFilter("FolderInfo.defaultDisplayFilters." + (String JavaDoc) defaultFilterNames.elementAt(i)));
2190    }
2191
2192    Vector displayFilterNames=Pooka.getResources().getPropertyAsVector(getFolderProperty() + ".displayFilters", "");
2193    for (int i = 0; i < displayFilterNames.size(); i++) {
2194      foundFilters.add(new MessageFilter(getFolderProperty() + ".displayFilters." + (String JavaDoc) displayFilterNames.elementAt(i)));
2195    }
2196
2197    tmpDisplayFilters = new MessageFilter[foundFilters.size()];
2198    for (int i = 0; i < foundFilters.size(); i++)
2199      tmpDisplayFilters[i] = (MessageFilter) foundFilters.elementAt(i);
2200
2201    displayFilters = tmpDisplayFilters;
2202
2203    filterHeaders = new LinkedList();
2204    // update the fetch profile with the headers from the display filters.
2205
for (int i = 0; i < tmpDisplayFilters.length; i++) {
2206      javax.mail.search.SearchTerm JavaDoc filterTerm = tmpDisplayFilters[i].getSearchTerm();
2207      if (filterTerm != null) {
2208        List JavaDoc headers = getHeaders(filterTerm);
2209        filterHeaders.addAll(headers);
2210      }
2211    }
2212
2213    if (fetchProfile != null) {
2214      for (int i = 0; i < filterHeaders.size(); i++) {
2215        fetchProfile.add((String JavaDoc) filterHeaders.get(i));
2216      }
2217    }
2218  }
2219
2220  /**
2221   * Gets all of the header strings for the given search term.
2222   */

2223  private List JavaDoc getHeaders(SearchTerm term) {
2224    List JavaDoc returnValue = new LinkedList();
2225    if (term instanceof HeaderTerm) {
2226      String JavaDoc headerName = ((HeaderTerm) term).getHeaderName();
2227      returnValue.add(headerName);
2228    } else if (term instanceof AndTerm) {
2229      SearchTerm[] terms = ((AndTerm)term).getTerms();
2230      for (int i = 0; i < terms.length; i++) {
2231        returnValue.addAll(getHeaders(terms[i]));
2232      }
2233    } else if (term instanceof OrTerm) {
2234      SearchTerm[] terms = ((OrTerm)term).getTerms();
2235      for (int i = 0; i < terms.length; i++) {
2236        returnValue.addAll(getHeaders(terms[i]));
2237      }
2238    } else if (term instanceof NotTerm) {
2239      SearchTerm otherTerm = ((NotTerm)term).getTerm();
2240      returnValue.addAll(getHeaders(otherTerm));
2241    } else if (term instanceof FromTerm || term instanceof FromStringTerm) {
2242      returnValue.add("From");
2243    } else if (term instanceof RecipientTerm || term instanceof RecipientStringTerm) {
2244      Message.RecipientType type;
2245      if (term instanceof RecipientTerm)
2246        type = ((RecipientTerm) term).getRecipientType();
2247      else
2248        type = ((RecipientStringTerm) term).getRecipientType();
2249      if (type == Message.RecipientType.TO)
2250        returnValue.add("To");
2251      else if (type == Message.RecipientType.CC)
2252        returnValue.add("Cc");
2253      else if (type == Message.RecipientType.BCC)
2254        returnValue.add("Bcc");
2255    }
2256
2257    return returnValue;
2258  }
2259
2260  /**
2261   * This applies each MessageFilter in filters array on the given
2262   * MessageProxy objects.
2263   *
2264   * @return a Vector containing the removed MessageProxy objects.
2265   */

2266  public Vector applyFilters(List JavaDoc messages) {
2267    return applyFilters(messages, null);
2268  }
2269
2270  /**
2271   * This applies each MessageFilter in filters array on the given
2272   * MessageProxy objects.
2273   *
2274   * @return a Vector containing the removed MessageProxy objects.
2275   */

2276  public Vector applyFilters(List JavaDoc messages, net.suberic.util.swing.ProgressDialog pd) {
2277    Vector notRemovedYet = new Vector(messages);
2278    Vector removed = new Vector();
2279    if (backendFilters != null)
2280      for (int i = 0; i < backendFilters.length; i++) {
2281        if (backendFilters[i] != null) {
2282          List JavaDoc justRemoved = backendFilters[i].filterMessages(notRemovedYet, pd);
2283          removed.addAll(justRemoved);
2284          notRemovedYet.removeAll(justRemoved);
2285        }
2286      }
2287
2288    if (removed.size() > 0) {
2289      try {
2290        expunge();
2291      } catch (MessagingException me) {
2292        me.printStackTrace();
2293      }
2294    }
2295
2296    return removed;
2297  }
2298
2299  // Accessor methods.
2300

2301  /**
2302   * Returns the backend filters for this folder.
2303   */

2304  public BackendMessageFilter[] getBackendFilters() {
2305    return backendFilters;
2306  }
2307
2308  public Action JavaDoc[] getActions() {
2309    return defaultActions;
2310  }
2311
2312  public Folder getFolder() {
2313    return folder;
2314  }
2315
2316  protected void setFolder(Folder newValue) {
2317    folder=newValue;
2318  }
2319
2320  /**
2321   * This returns the FolderID, such as "myStore.INBOX".
2322   */

2323  public String JavaDoc getFolderID() {
2324    return folderID;
2325  }
2326
2327  /**
2328   * This sets the folderID.
2329   */

2330  private void setFolderID(String JavaDoc newValue) {
2331    folderID=newValue;
2332  }
2333
2334  /**
2335   * This returns the simple folderName, such as "INBOX".
2336   */

2337  public String JavaDoc getFolderName() {
2338    return mFolderName;
2339  }
2340
2341  /**
2342   * This returns the folder display name, usually the FolderName plus
2343   * the store id.
2344   */

2345  public String JavaDoc getFolderDisplayName() {
2346    return mFolderName + " - " + getParentStore().getStoreID();
2347  }
2348
2349  /**
2350   * This returns the property which defines this FolderInfo, such as
2351   * "Store.myStore.INBOX".
2352   */

2353  public String JavaDoc getFolderProperty() {
2354    return "Store." + getFolderID();
2355  }
2356
2357  public Vector getChildren() {
2358    return children;
2359  }
2360
2361  public FolderNode getFolderNode() {
2362    return folderNode;
2363  }
2364
2365  public void setFolderNode(FolderNode newValue) {
2366    folderNode = newValue;
2367  }
2368
2369  public FolderTableModel getFolderTableModel() {
2370    return folderTableModel;
2371  }
2372
2373  public void setFolderTableModel(FolderTableModel newValue) {
2374    folderTableModel = newValue;
2375  }
2376
2377  public List JavaDoc getColumnValues() {
2378    return columnValues;
2379  }
2380
2381  public void setColumnValues(List JavaDoc newValue) {
2382    columnValues = newValue;
2383  }
2384
2385  public List JavaDoc<String JavaDoc> getColumnIds() {
2386    return columnIds;
2387  }
2388
2389  public void setColumnIds(List JavaDoc<String JavaDoc> newColumnIds) {
2390    columnIds = newColumnIds;
2391  }
2392
2393  public List JavaDoc<String JavaDoc> getColumnNames() {
2394    return columnNames;
2395  }
2396
2397  public void setColumnNames(List JavaDoc<String JavaDoc> newValue) {
2398    columnNames = newValue;
2399  }
2400
2401  public List JavaDoc<String JavaDoc> getColumnSizes() {
2402    return columnSizes;
2403  }
2404
2405  public void setColumnSizes(List JavaDoc<String JavaDoc> newValue) {
2406    columnSizes = newValue;
2407  }
2408
2409  public FolderDisplayUI getFolderDisplayUI() {
2410    return folderDisplayUI;
2411  }
2412
2413  protected void removeFromListeners(FolderDisplayUI display) {
2414    if (display != null) {
2415      removeMessageChangedListener(display);
2416      removeMessageCountListener(display);
2417      //getFolder().removeConnectionListener(display);
2418
}
2419  }
2420
2421  protected void addToListeners(FolderDisplayUI display) {
2422    if (display != null) {
2423      addMessageChangedListener(display);
2424      addMessageCountListener(display);
2425      //getFolder().addConnectionListener(display);
2426
}
2427  }
2428
2429  /**
2430   * This sets the given FolderDisplayUI to be the UI for this
2431   * FolderInfo.
2432   *
2433   * It automatically registers that FolderDisplayUI to be a listener
2434   * to MessageCount, MessageChanged, and Connection events.
2435   */

2436  public void setFolderDisplayUI(FolderDisplayUI newValue) {
2437    removeFromListeners(folderDisplayUI);
2438    folderDisplayUI = newValue;
2439    addToListeners(folderDisplayUI);
2440  }
2441
2442  public int getType() {
2443    return type;
2444  }
2445
2446  public boolean isConnected() {
2447    return (status == CONNECTED);
2448  }
2449
2450  public boolean shouldBeConnected() {
2451    return (status < PASSIVE);
2452  }
2453
2454  public boolean isSortaOpen() {
2455    return (status < CLOSED);
2456  }
2457
2458  public boolean isAvailable() {
2459    return (status < NOT_LOADED);
2460  }
2461
2462  public boolean isLoaded() {
2463    return (folder != null);
2464  }
2465
2466  public boolean isValid() {
2467    return (status != INVALID);
2468  }
2469
2470  public boolean hasUnread() {
2471    return (tracksUnreadMessages() && unreadCount > 0);
2472  }
2473
2474  public int getUnreadCount() {
2475    if (!tracksUnreadMessages())
2476      return 0;
2477    else
2478      return unreadCount;
2479  }
2480
2481  public int getMessageCount() {
2482    return messageCount;
2483  }
2484
2485  public boolean hasNewMessages() {
2486    return newMessages;
2487  }
2488
2489  public void setNewMessages(boolean newValue) {
2490    newMessages = newValue;
2491  }
2492
2493  public FolderTracker getFolderTracker() {
2494    return folderTracker;
2495  }
2496
2497  public void setFolderTracker(FolderTracker newTracker) {
2498    folderTracker = newTracker;
2499  }
2500
2501  public boolean isTrashFolder() {
2502    return trashFolder;
2503  }
2504
2505  /**
2506   * This sets the trashFolder value. it also resets the defaultAction
2507   * list and erases the FolderNode's popupMenu, if there is one.
2508   */

2509  public void setTrashFolder(boolean newValue) {
2510    trashFolder = newValue;
2511    if (newValue) {
2512      setNotifyNewMessagesMain(false);
2513      setNotifyNewMessagesNode(false);
2514    } else {
2515      if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesMain", "").equalsIgnoreCase("false"))
2516        setNotifyNewMessagesMain(true);
2517      else
2518        setNotifyNewMessagesMain(false);
2519
2520      if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesNode", "").equalsIgnoreCase("false"))
2521        setNotifyNewMessagesNode(true);
2522      else
2523        setNotifyNewMessagesNode(false);
2524    }
2525
2526    resetDefaultActions();
2527    if (getFolderNode() != null)
2528      getFolderNode().popupMenu = null;
2529  }
2530
2531
2532  public boolean isSentFolder() {
2533    return sentFolder;
2534  }
2535
2536
2537  public void setSentFolder(boolean newValue) {
2538    sentFolder = newValue;
2539    if (newValue) {
2540      setNotifyNewMessagesMain(false);
2541      setNotifyNewMessagesNode(false);
2542    } else {
2543      if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesMain", "").equalsIgnoreCase("false"))
2544        setNotifyNewMessagesMain(true);
2545      else
2546        setNotifyNewMessagesMain(false);
2547
2548      if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesNode", "").equalsIgnoreCase("false"))
2549        setNotifyNewMessagesNode(true);
2550      else
2551        setNotifyNewMessagesNode(false);
2552    }
2553    setTracksUnreadMessages (! newValue);
2554    createFilters();
2555  }
2556
2557  /**
2558   * Returns whether or not this is an Outbox for an OutgoingMailServer.
2559   */

2560  public boolean isOutboxFolder() {
2561    return (mailServer != null);
2562  }
2563
2564  /**
2565   * Sets this as an Outbox for the given OutgoingMailServer. If this
2566   * is getting removed as an outbox, set the server to null.
2567   */

2568  public void setOutboxFolder(OutgoingMailServer newServer) {
2569    mailServer = newServer;
2570    if (newServer != null) {
2571      setNotifyNewMessagesMain(false);
2572      setNotifyNewMessagesNode(false);
2573    } else {
2574      if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesMain", "").equalsIgnoreCase("false"))
2575        setNotifyNewMessagesMain(true);
2576      else
2577        setNotifyNewMessagesMain(false);
2578
2579      if (!Pooka.getProperty(getFolderProperty() + ".notifyNewMessagesNode", "").equalsIgnoreCase("false"))
2580        setNotifyNewMessagesNode(true);
2581      else
2582        setNotifyNewMessagesNode(false);
2583    }
2584
2585    resetDefaultActions();
2586  }
2587
2588  public boolean notifyNewMessagesMain() {
2589    return notifyNewMessagesMain;
2590  }
2591
2592  public void setNotifyNewMessagesMain(boolean newValue) {
2593    notifyNewMessagesMain = newValue;
2594  }
2595
2596  public boolean notifyNewMessagesNode() {
2597    return notifyNewMessagesNode;
2598  }
2599
2600  public void setNotifyNewMessagesNode(boolean newValue) {
2601    notifyNewMessagesNode = newValue;
2602  }
2603
2604  public void setTracksUnreadMessages(boolean newValue) {
2605    tracksUnreadMessages = newValue;
2606  }
2607
2608  public boolean tracksUnreadMessages() {
2609    return tracksUnreadMessages;
2610  }
2611
2612  public MessageFilter[] getDisplayFilters() {
2613    return displayFilters;
2614  }
2615
2616  /**
2617   * This forces an update of both the total and unread message counts.
2618   */

2619  public void resetMessageCounts() {
2620    try {
2621      if (getFolder() != null)
2622        getLogger().log(Level.FINE, "running resetMessageCounts. unread message count is " + getFolder().getUnreadMessageCount());
2623      else
2624        getLogger().log(Level.FINE, "running resetMessageCounts. getFolder() is null.");
2625
2626      if (tracksUnreadMessages()) {
2627        unreadCount = getFolder().getUnreadMessageCount();
2628      }
2629
2630      messageCount = getFolder().getMessageCount();
2631    } catch (MessagingException me) {
2632      // if we lose the connection to the folder, we'll leave the old
2633
// messageCount and set the unreadCount to zero.
2634
unreadCount = 0;
2635    }
2636    updateNode();
2637  }
2638
2639  /**
2640   * This returns the parentFolder. If this FolderInfo is a direct
2641   * child of a StoreInfo, this method will return null.
2642   */

2643  public FolderInfo getParentFolder() {
2644    return parentFolder;
2645  }
2646
2647  /**
2648   * This method actually returns the parent StoreInfo. If this
2649   * particular FolderInfo is a child of another FolderInfo, this
2650   * method will call getParentStore() on that FolderInfo.
2651   */

2652  public StoreInfo getParentStore() {
2653    if (parentStore == null)
2654      return parentFolder.getParentStore();
2655    else
2656      return parentStore;
2657  }
2658
2659  public UserProfile getDefaultProfile() {
2660    if (defaultProfile != null) {
2661      return defaultProfile;
2662    } else if (parentFolder != null) {
2663      return parentFolder.getDefaultProfile();
2664    } else if (parentStore != null) {
2665      return parentStore.getDefaultProfile();
2666    } else {
2667      return null;
2668    }
2669  }
2670
2671  /**
2672   * sets the status.
2673   */

2674  public void setStatus(int newStatus) {
2675    synchronized(this) {
2676      status = newStatus;
2677    }
2678  }
2679
2680  /**
2681   * gets the status.
2682   */

2683  public int getStatus() {
2684    return status;
2685  }
2686
2687  public ActionThread getFolderThread() {
2688    return getParentStore().getStoreThread();
2689  }
2690
2691  public FolderInfo getTrashFolder() {
2692    return getParentStore().getTrashFolder();
2693  }
2694
2695  public FetchProfile getFetchProfile() {
2696    return fetchProfile;
2697  }
2698
2699  /**
2700   * Returns whether or not this folder is actually a namespace.
2701   */

2702  public boolean isNamespace() {
2703    return mNamespace;
2704  }
2705
2706  /**
2707   * Shows a status message, using the given FolderDisplayUI if not null.
2708   */

2709  public void showStatusMessage(net.suberic.pooka.gui.FolderDisplayUI pUI, String JavaDoc message) {
2710    if (pUI != null)
2711      pUI.showStatusMessage(message);
2712    else
2713      Pooka.getUIFactory().showStatusMessage(message);
2714  }
2715
2716  /**
2717   * Clears the status message.
2718   */

2719  public void clearStatusMessage(net.suberic.pooka.gui.FolderDisplayUI pUI) {
2720    if (pUI != null)
2721      pUI.clearStatusMessage();
2722    else
2723      Pooka.getUIFactory().clearStatus();
2724  }
2725
2726  /**
2727   * Returns the logger for this Folder.
2728   */

2729  public Logger getLogger() {
2730    if (mLogger == null) {
2731      mLogger = Logger.getLogger(getFolderProperty());
2732    }
2733    return mLogger;
2734  }
2735
2736  class EditPropertiesAction extends AbstractAction JavaDoc {
2737
2738    EditPropertiesAction() {
2739      super("file-edit");
2740    }
2741
2742    public void actionPerformed(ActionEvent JavaDoc e) {
2743      Pooka.getUIFactory().showEditorWindow(getFolderProperty(), getFolderProperty(), "Folder.editor");
2744    }
2745  }
2746
2747  class UpdateCountAction extends AbstractAction JavaDoc {
2748
2749    UpdateCountAction() {
2750      super("folder-update");
2751    }
2752
2753    public void actionPerformed(ActionEvent JavaDoc e) {
2754      // should always be on the Folder thread.
2755
try {
2756        checkFolder();
2757      } catch (MessagingException me) {
2758        final MessagingException me2 = me;
2759
2760        javax.swing.SwingUtilities.invokeLater(new Runnable JavaDoc() {
2761            public void run() {
2762              if (getFolderDisplayUI() != null)
2763                getFolderDisplayUI().showError(Pooka.getProperty("error.updatingFolder", "Error updating Folder ") + getFolderID(), me2);
2764              else
2765                Pooka.getUIFactory().showError(Pooka.getProperty("error.updatingFolder", "Error updating Folder ") + getFolderID(), me2);
2766
2767            }
2768          });
2769
2770      }
2771    }
2772  }
2773
2774  class EmptyTrashAction extends AbstractAction JavaDoc {
2775
2776    EmptyTrashAction() {
2777      super("folder-empty");
2778    }
2779
2780    public void actionPerformed(ActionEvent JavaDoc e) {
2781      emptyTrash();
2782    }
2783  }
2784
2785
2786  class SendAllAction extends AbstractAction JavaDoc {
2787
2788    SendAllAction() {
2789      super("folder-send");
2790    }
2791
2792    public void actionPerformed(ActionEvent JavaDoc e) {
2793      if (isOutboxFolder())
2794        mailServer.sendAll();
2795    }
2796  }
2797
2798}
2799
Popular Tags