source: trunk/LogicMail/src/org/logicprobe/LogicMail/model/AccountNode.java @ 950

Revision 950, 20.8 KB checked in by octorian, 9 months ago (diff)

Refactored the folder refresh processes from awkward services-layer implementations into direct mail store request implementations.

Line 
1/*-
2 * Copyright (c) 2008, Derek Konigsberg
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the project nor the names of its
15 *    contributors may be used to endorse or promote products derived
16 *    from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31package org.logicprobe.LogicMail.model;
32
33import org.logicprobe.LogicMail.mail.FolderEvent;
34import org.logicprobe.LogicMail.mail.FolderExpungedEvent;
35import org.logicprobe.LogicMail.mail.FolderListener;
36import org.logicprobe.LogicMail.mail.FolderMessagesEvent;
37import org.logicprobe.LogicMail.mail.FolderTreeItem;
38import org.logicprobe.LogicMail.mail.MailStoreEvent;
39import org.logicprobe.LogicMail.mail.MailStoreListener;
40import org.logicprobe.LogicMail.mail.MessageEvent;
41import org.logicprobe.LogicMail.mail.MessageListener;
42import org.logicprobe.LogicMail.mail.MessageToken;
43import org.logicprobe.LogicMail.util.EventListenerList;
44
45import java.util.Enumeration;
46import java.util.Hashtable;
47import java.util.Vector;
48
49/**
50 * Account node for the mail data model.
51 * This node contains the root <code>MailboxNode</code> instance and delegates
52 * events from the underlying mail store to the appropriate
53 * <code>MailboxNode</code> or <code>MessageNode</code>.
54 */
55public abstract class AccountNode implements Node {
56    public final static int STATUS_LOCAL = 0;
57    public final static int STATUS_OFFLINE = 1;
58    public final static int STATUS_ONLINE = 2;
59   
60    protected final MailStoreServices mailStoreServices;
61    private MailRootNode parent;
62    private MailboxNode rootMailbox;
63    private final Hashtable pathMailboxMap;
64    private final Object rootMailboxLock = new Object();
65    private final EventListenerList listenerList = new EventListenerList();
66   
67    protected int status;
68   
69    /**
70     * Construct a new node for a network account.
71     *
72     * @param mailStoreServices Services for interacting with the mail store.
73     */
74    protected AccountNode(MailStoreServices mailStoreServices) {
75        this.rootMailbox = null;
76        this.pathMailboxMap = new Hashtable();
77
78        this.mailStoreServices = mailStoreServices;
79
80        addMailStoreListeners();
81    }
82
83    private void addMailStoreListeners() {
84        this.mailStoreServices.addMailStoreListener(new MailStoreListener() {
85            public void folderTreeUpdated(FolderEvent e) {
86                mailStoreFolderTreeUpdated(e);
87            }
88            public void refreshRequired(MailStoreEvent e) {
89                mailStoreRefreshRequired(e);
90            }
91        });
92
93        this.mailStoreServices.addFolderListener(new FolderListener() {
94            public void folderStatusChanged(FolderEvent e) {
95                MailboxNode mailboxNode = getMailboxNodeForEvent(e);
96                mailboxNode.mailStoreFolderStatusChanged(e);
97            }
98
99            public void folderMessagesAvailable(FolderMessagesEvent e) {
100                MailboxNode mailboxNode = getMailboxNodeForEvent(e);
101                mailboxNode.mailStoreFolderMessagesAvailable(e);
102            }
103
104            public void folderExpunged(FolderExpungedEvent e) {
105                MailboxNode mailboxNode = getMailboxNodeForEvent(e);
106                mailboxNode.mailStoreFolderExpunged(e);
107            }
108
109            public void folderRefreshRequired(FolderEvent e) { }
110        });
111
112        this.mailStoreServices.addMessageListener(new MessageListener() {
113            public void messageAvailable(MessageEvent e) {
114                MailboxNode mailboxNode = getMailboxNodeForEvent(e);
115                if(mailboxNode != null) {
116                    mailboxNode.mailStoreMessageAvailable(e);
117                }
118            }
119
120            public void messageFlagsChanged(MessageEvent e) {
121                MailboxNode mailboxNode = getMailboxNodeForEvent(e);
122                if(mailboxNode != null) {
123                    mailboxNode.mailStoreMessageFlagsChanged(e);
124                }
125            }
126        });
127    }
128
129    protected MailboxNode getMailboxNodeForFolder(FolderTreeItem folder) {
130        MailboxNode mailboxNode = (MailboxNode) pathMailboxMap.get(folder.getPath());
131        return mailboxNode;
132    }
133   
134    private MailboxNode getMailboxNodeForEvent(FolderEvent e) {
135        MailboxNode mailboxNode = getMailboxNodeForFolder(e.getFolder());
136        return mailboxNode;
137    }
138   
139    private MailboxNode getMailboxNodeForEvent(MessageEvent e) {
140        MessageToken messageToken = e.getMessageToken();
141        if(messageToken == null) { return null; }
142        Enumeration en = pathMailboxMap.elements();
143        while(en.hasMoreElements()) {
144            MailboxNode currentMailbox = (MailboxNode)en.nextElement();
145            if(messageToken.containedWithin(currentMailbox.getFolderTreeItem())) {
146                return currentMailbox;
147            }
148        }
149        return null;
150    }
151
152    protected MailboxNode[] getAllMailboxNodes() {
153        Vector resultVector = new Vector();
154        Enumeration e = pathMailboxMap.elements();
155        while(e.hasMoreElements()) {
156            MailboxNode mailbox = (MailboxNode)e.nextElement();
157            if(mailbox.getType() == MailboxNode.TYPE_INBOX) {
158                resultVector.insertElementAt(mailbox, 0);
159            }
160            else {
161                resultVector.addElement(mailbox);
162            }
163        }
164
165        MailboxNode[] result = new MailboxNode[resultVector.size()];
166        resultVector.copyInto(result);
167        return result;
168    }
169   
170    /**
171     * The name of the protocol behind this account.
172     */
173    public String getProtocolName() {
174        return "";
175    }
176   
177    public void accept(NodeVisitor visitor) {
178        visitor.visit(this);
179    }
180
181    /**
182     * Sets the root node which is the parent of this account.
183     *
184     * @param parent The root node.
185     */
186    void setParent(MailRootNode parent) {
187        this.parent = parent;
188    }
189
190    /**
191     * Gets the root node which is the parent of this account.
192     *
193     * @return The root node.
194     */
195    public MailRootNode getParent() {
196        return this.parent;
197    }
198
199    /**
200     * Get the top-level mailbox contained within this account.
201     * This mailbox typically exists only for the purpose of
202     * containing other mailboxes, and is not normally shown
203     * to the user.
204     *
205     * @return Root mailbox node.
206     */
207    public MailboxNode getRootMailbox() {
208        synchronized (rootMailboxLock) {
209            return this.rootMailbox;
210        }
211    }
212
213    /**
214     * Gets the mail store services associated with this account.
215     *
216     * @return The mail store services.
217     */
218    MailStoreServices getMailStoreServices() {
219        return this.mailStoreServices;
220    }
221
222    /**
223     * Sets the status of this account.
224     *
225     * @param status The status.
226     */
227    void setStatus(int status) {
228        if (this.status != status) {
229            this.status = status;
230            mailStoreServices.setConnected(status == AccountNode.STATUS_ONLINE);
231            fireAccountStatusChanged(AccountNodeEvent.TYPE_CONNECTION);
232        }
233    }
234
235    /**
236     * Gets the status of this account.
237     *
238     * @return The status.
239     */
240    public int getStatus() {
241        return this.status;
242    }
243
244    /**
245     * Gets whether this account supports folders.
246     * If folders are not supported, then this account will automatically
247     * present a single "INBOX" folder.  However, no other folder-related
248     * operations will have any relevance.
249     *
250     * @return True if supported, false otherwise.
251     */
252    public boolean hasFolders() {
253        return this.mailStoreServices.hasFolders();
254    }
255
256    /**
257     * Gets whether this account supports retrieval of individual message parts.
258     *
259     * @return True if supported, false otherwise.
260     */
261    public boolean hasMessageParts() {
262        return this.mailStoreServices.hasMessageParts();
263    }
264   
265    /**
266     * Gets whether this account supports undelete.
267     *
268     * @return True if supported, false otherwise.
269     */
270    public boolean hasUndelete() {
271        return this.mailStoreServices.hasUndelete();
272    }
273
274    /**
275     * Gets whether this account supports expunging deleted messages.
276     *
277     * @return True if supported, false otherwise.
278     */
279    public boolean hasExpunge() {
280        return this.mailStoreServices.hasExpunge();
281    }
282
283    /**
284     * Called to trigger a refresh of the mailboxes under
285     * this account.  Completion is signaled by an
286     * AccountStatusChanged event.
287     */
288    public void refreshMailboxes() {
289        if (mailStoreServices.hasFolders()) {
290            mailStoreServices.requestFolderTree();
291        }
292    }
293
294    /**
295     * Called to trigger a refresh of message count status
296     * for mailboxes under this account.  Completion is
297     * signaled by MailboxStatusChanged events on the
298     * updated mailboxes.
299     */
300    public void refreshMailboxStatus() {
301        FolderTreeItem[] folders = getFolderTreeItems();
302
303        mailStoreServices.requestFolderStatus(folders);
304    }
305
306    /**
307     * Gets a flag list of the folder tree items for all the mailboxes
308     * contained within this account.
309     *
310     * @return the folder tree item array
311     */
312    protected FolderTreeItem[] getFolderTreeItems() {
313        int size = pathMailboxMap.size();
314        FolderTreeItem[] folders = new FolderTreeItem[size];
315        Enumeration e = pathMailboxMap.keys();
316
317        for (int i = 0; i < size; i++) {
318            folders[i] = ((MailboxNode) pathMailboxMap.get(e.nextElement())).getFolderTreeItem();
319        }
320        return folders;
321    }
322
323    /**
324     * Handles folder tree updates.
325     *
326     * @param e Event data.
327     */
328    private void mailStoreFolderTreeUpdated(FolderEvent e) {
329        FolderTreeItem rootFolder = e.getFolder();
330
331        synchronized (rootMailboxLock) {
332            Hashtable remainingMailboxMap = new Hashtable();
333
334            if (rootMailbox != null) {
335                // Disassemble the model tree into a flat collection of nodes
336                Vector flatMailboxes = new Vector();
337                populateFlatMailboxes(flatMailboxes, rootMailbox);
338                rootMailbox = null;
339
340                // Prune the collection to only include nodes that are still valid,
341                // and make them reference the new FolderTreeItem objects.
342                Hashtable folderPathMap = new Hashtable();
343                populateFolderPathMap(folderPathMap, rootFolder);
344
345                Vector removedFolders = null;
346                int size = flatMailboxes.size();
347                for (int i = 0; i < size; i++) {
348                    MailboxNode mailboxNode = (MailboxNode) flatMailboxes.elementAt(i);
349                    FolderTreeItem oldFolderItem = mailboxNode.getFolderTreeItem();
350                    String path = oldFolderItem.getPath();
351
352                    if (folderPathMap.containsKey(path)) {
353                        // We found a duplicate item in the new folder tree
354                        FolderTreeItem newFolderItem = (FolderTreeItem) folderPathMap.get(path);
355                        mailboxNode.setFolderTreeItem(newFolderItem);
356
357                        // Need to copy over the UID to preserve cache validity.
358                        // This needs to be done after setting it on the
359                        // mailbox, since the setter copies the item before
360                        // adding it.
361                        mailboxNode.getFolderTreeItem().setUniqueId(
362                                oldFolderItem.getUniqueId());
363                       
364                        remainingMailboxMap.put(path, mailboxNode);
365                    }
366                    else {
367                        // This mailbox was removed
368                        if(removedFolders == null) {
369                            removedFolders = new Vector();
370                        }
371                        removedFolders.addElement(oldFolderItem);
372                    }
373                }
374
375                // Clear the saved data for any orphaned folders
376                if(removedFolders != null) {
377                    FolderTreeItem[] orphanedItems = new FolderTreeItem[removedFolders.size()];
378                    removedFolders.copyInto(orphanedItems);
379                    mailStoreServices.removeSavedData(orphanedItems);
380                }
381            }
382
383            // Build a new tree from the FolderTreeItem, using the collected
384            // nodes where possible, and new nodes when necessary.
385            this.pathMailboxMap.clear();
386            this.rootMailbox = new MailboxNode(rootFolder, false, -1);
387            populateMailboxNodes(rootFolder, rootMailbox, remainingMailboxMap);
388        }
389
390        save();
391        fireAccountStatusChanged(AccountNodeEvent.TYPE_MAILBOX_TREE);
392    }
393   
394    protected void mailStoreRefreshRequired(MailStoreEvent e) {
395        // Default empty implementation
396    }
397   
398    private void populateFlatMailboxes(Vector flatMailboxes,
399        MailboxNode currentMailbox) {
400        flatMailboxes.addElement(currentMailbox);
401
402        MailboxNode[] childNodes = currentMailbox.getMailboxes();
403
404        for (int i = 0; i < childNodes.length; i++) {
405            populateFlatMailboxes(flatMailboxes, childNodes[i]);
406        }
407
408        currentMailbox.clearMailboxes();
409    }
410
411    private void populateFolderPathMap(Hashtable folderPathMap,
412        FolderTreeItem folderTreeItem) {
413        if (folderTreeItem != null) {
414            folderPathMap.put(folderTreeItem.getPath(), folderTreeItem);
415
416            if (folderTreeItem.hasChildren()) {
417                FolderTreeItem[] children = folderTreeItem.children();
418
419                for (int i = 0; i < children.length; i++) {
420                    populateFolderPathMap(folderPathMap, children[i]);
421                }
422            }
423        }
424    }
425
426    private void populateMailboxNodes(FolderTreeItem folderTreeItem,
427        MailboxNode currentMailbox, Hashtable remainingMailboxMap) {
428        pathMailboxMap.put(folderTreeItem.getPath(), currentMailbox);
429
430        if (folderTreeItem.hasChildren()) {
431            FolderTreeItem[] folderTreeItemChildren = folderTreeItem.children();
432
433            for (int i = 0; i < folderTreeItemChildren.length; i++) {
434                MailboxNode childMailbox;
435
436                if (remainingMailboxMap.containsKey(
437                            folderTreeItemChildren[i].getPath())) {
438                    childMailbox = (MailboxNode) remainingMailboxMap.get(folderTreeItemChildren[i].getPath());
439                } else {
440                        int mailboxType = getMailboxType(folderTreeItemChildren[i]);
441                        if(mailboxType == MailboxNode.TYPE_OUTBOX) {
442                            childMailbox = new OutboxMailboxNode(folderTreeItemChildren[i]);
443                        }
444                        else {
445                            childMailbox = new MailboxNode(folderTreeItemChildren[i],
446                                        folderTreeItemChildren[i].isAppendable(),
447                                        mailboxType);
448                        }
449                    childMailbox.setParentAccount(this);
450                }
451
452                populateMailboxNodes(folderTreeItemChildren[i], childMailbox,
453                    remainingMailboxMap);
454                currentMailbox.addMailbox(childMailbox);
455            }
456        }
457    }
458
459    /**
460     * Attempts to determine the folder type based on its name,
461     * and any configuration options.
462     * <p>
463     * This approach is only necessary to for local folders and general
464     * defaults.  Explicitly configured special folders have their type set
465     * later in the account loading process.
466     * </p>
467     * @param folderTreeItem Source folder tree item.
468     * @return Mailbox type
469     */
470    protected int getMailboxType(FolderTreeItem folderTreeItem) {
471        int mailboxType;
472        if (folderTreeItem.getPath().equalsIgnoreCase("INBOX")) {
473                mailboxType = MailboxNode.TYPE_INBOX;
474        }
475        else {
476            mailboxType = MailboxNode.TYPE_NORMAL;
477        }
478        return mailboxType;
479    }
480
481    /**
482    * Adds a <tt>AccountNodeListener</tt> to the account node.
483    *
484    * @param l The <tt>AccountNodeListener</tt> to be added.
485    */
486    public void addAccountNodeListener(AccountNodeListener l) {
487        listenerList.add(AccountNodeListener.class, l);
488    }
489
490    /**
491     * Removes a <tt>AccountNodeListener</tt> from the account node.
492     *
493     * @param l The <tt>AccountNodeListener</tt> to be removed.
494     */
495    public void removeAccountNodeListener(AccountNodeListener l) {
496        listenerList.remove(AccountNodeListener.class, l);
497    }
498
499    /**
500     * Returns an array of all <tt>AccountNodeListener</tt>s
501     * that have been added to this account node.
502     *
503     * @return All the <tt>AccountNodeListener</tt>s that have been added,
504     * or an empty array if no listeners have been added.
505     */
506    public AccountNodeListener[] getAccountNodeListeners() {
507        return (AccountNodeListener[]) listenerList.getListeners(AccountNodeListener.class);
508    }
509
510    /**
511     * Notifies all registered <tt>AccountNodeListener</tt>s that
512     * the account status has changed.
513     *
514     * @param type Event type.
515     */
516    protected void fireAccountStatusChanged(int type) {
517        Object[] listeners = listenerList.getListeners(AccountNodeListener.class);
518        AccountNodeEvent e = null;
519
520        for (int i = 0; i < listeners.length; i++) {
521            if (e == null) {
522                e = new AccountNodeEvent(this, type);
523            }
524
525            ((AccountNodeListener) listeners[i]).accountStatusChanged(e);
526        }
527    }
528
529    /**
530     * Saves the mailbox tree to persistent storage.
531     */
532    abstract void save();
533
534    /**
535     * Loads the mailbox tree from persistent storage.
536     */
537    abstract void load();
538
539    /**
540     * Clear any persistent data associated with this account node.
541     *
542     * <p>
543     * When this account node removed from the model tree because the
544     * underlying account has been deleted, this method needs to be called to
545     * ensure that persistent data does not linger on the device.
546     * </p>
547     */
548    protected void removeSavedData() {
549        FolderTreeItem[] folderTreeItems = this.getFolderTreeItems();
550        mailStoreServices.removeSavedData(folderTreeItems);
551    }
552
553    /**
554     * Sets the top-level mailbox contained within this account.
555     * This method should only be called by subclasses when loading saved
556     * account data.
557     */
558    protected void setRootMailbox(MailboxNode mailboxNode) {
559        synchronized (rootMailboxLock) {
560            this.rootMailbox = mailboxNode;
561            prepareDeserializedMailboxNode(rootMailbox);
562        }
563    }
564   
565    /**
566     * Traverses the deserialized mailbox nodes, populates any necessary
567     * data structures in the account node, and sets the mailbox parent
568     * account references.
569     *
570     * @param mailboxNode The mailbox node.
571     */
572    private void prepareDeserializedMailboxNode(MailboxNode mailboxNode) {
573        mailboxNode.setParentAccount(this);
574
575        FolderTreeItem item = mailboxNode.getFolderTreeItem();
576
577        if ((item != null) && (item.getPath().length() > 0)) {
578            this.pathMailboxMap.put(item.getPath(), mailboxNode);
579        }
580
581        MailboxNode[] children = mailboxNode.getMailboxes();
582
583        for (int i = 0; i < children.length; i++) {
584            prepareDeserializedMailboxNode(children[i]);
585        }
586    }
587}
Note: See TracBrowser for help on using the repository browser.