pjs/grendel/view/FolderViewBase.java

583 строки
16 KiB
Java

/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Grendel mail/news client.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are Copyright (C) 1997
* Netscape Communications Corporation. All Rights Reserved.
*
* Created: Terry Weissman <terry@netscape.com>, 25 Aug 1997.
*/
package grendel.view;
import calypso.util.ArrayEnumeration;
import calypso.util.Assert;
import calypso.util.Comparer;
import calypso.util.PrefetchEnumeration;
import calypso.util.QSort;
import java.util.Enumeration;
import java.util.Date;
import java.util.Vector;
import javax.mail.FetchProfile;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.event.MessageChangedEvent;
import javax.mail.event.MessageChangedListener;
import javax.mail.event.MessageCountEvent;
import javax.mail.event.MessageCountListener;
import grendel.storage.MessageExtraFactory;
class FolderViewBase implements FolderView, MessageChangedListener,
MessageCountListener
{
ViewedMessageBase fRoot;
Folder fFolder;
Vector observers = new Vector();
boolean threaded;
int sortorder[] = {NUMBER};
public void loadFrom(Folder f) {
if (fFolder != null) {
fFolder.removeMessageChangedListener(this);
fFolder.removeMessageCountListener(this);
}
fFolder = f;
fFolder.addMessageChangedListener(this);
fFolder.addMessageCountListener(this);
Message messages[] = null;
try {
if (!fFolder.isOpen()) {
fFolder.open(Folder.READ_WRITE);
}
messages = fFolder.getMessages();
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
f.fetch(messages, fp);
} catch (MessagingException e) {
}
if (messages != null) {
for (int i=0 ; i<messages.length ; i++) {
ViewedMessageBase v = new ViewedMessageBase(this, messages[i]);
v.setNext(fRoot);
fRoot = v;
}
}
reThread();
}
protected int generateSortKeys(ViewedMessageBase msg, int key) {
for ( ; msg != null ; msg = (ViewedMessageBase) msg.getNext()) {
msg.sortkey = key++;
Message m = msg.getMessage();
key = generateSortKeys((ViewedMessageBase) msg.getChild(), key);
}
return key;
}
protected void flattenList(ViewedMessageBase msg) {
while (msg != null) {
ViewedMessageBase next = (ViewedMessageBase) msg.getNext();
ViewedMessageBase child = (ViewedMessageBase) msg.getChild();
msg.setNext(fRoot);
msg.setChild(null);
if (!msg.isDummy()) {
fRoot = msg;
}
flattenList(child);
msg = next;
}
}
public void reThread() {
// ### What a hack. Tell our observers that everything has gone away;
// in a while we'll tell them that everything has reappeared.
for (int i=observers.size() - 1 ; i>=0 ; i--) {
((MessageSetViewObserver) observers.elementAt(i)).messagesChanged
(null, new ViewedMessageEnumeration(fRoot), null);
}
if (threaded) {
Threader t = new Threader();
fRoot = (ViewedMessageBase) t.thread(fRoot);
} else {
// Flatten out the list, and remove any dummies.
ViewedMessageBase base = fRoot;
fRoot = null;
flattenList(base);
}
if (fRoot == null) return; // empty folder
int nummsgs = fRoot.getMessageCount();
int numkeys = sortorder.length;
int n = 0;
int count = generateSortKeys(fRoot, 0);
Assert.Assertion(count == nummsgs);
Comparer comp = new NullComparer();
for (int i=sortorder.length - 1 ; i>=0 ; i--) {
switch (sortorder[i]) {
case NUMBER:
comp = new NumberComparer(nummsgs, comp);
break;
case DATE:
comp = new DateComparer(nummsgs, comp);
break;
case SUBJECT:
comp = new SubjectComparer(nummsgs, comp);
break;
case AUTHOR:
comp = new AuthorComparer(nummsgs, comp);
break;
case READ:
comp = new ReadComparer(nummsgs, comp);
break;
case FLAGGED:
comp = new FlaggedComparer(nummsgs, comp);
break;
case SIZE:
comp = new SizeComparer(nummsgs, comp);
break;
case DELETED:
comp = new DeletedComparer(nummsgs, comp);
break;
default:
throw new IllegalArgumentException("Bad sort key " + sortorder[i]);
}
}
Sorter s = new Sorter(comp);
DummyThreadable dummy = new DummyThreadable(null, null);
dummy.fChildren = fRoot;
s.sortMessageChildren(dummy);
fRoot = (ViewedMessageBase) dummy.fChildren;
dummy = null;
fRoot.setParentPointers(null);
// ### What a hack, part two. Tell our observers that everything
// reappeared.
for (int i=observers.size() - 1 ; i>=0 ; i--) {
((MessageSetViewObserver) observers.elementAt(i)).messagesChanged
(new ViewedMessageEnumeration(fRoot), null, null);
}
}
public void setSortOrder(int value[]) {
sortorder = new int[value.length];
int j=0;
// Strip out duplicates in the sortorder array.
NEXTI:
for (int i=0 ; i<value.length ; i++) {
int v = value[i];
for (int k=0 ; k<i ; k++) {
if (value[k] == v) continue NEXTI;
}
sortorder[j++] = v;
if (v == NUMBER) break; // Message number is always unique, so don't
// bother sorting beyond that.
}
if (j < value.length) {
int t[] = new int[j];
for (int k=0 ; k<j ; k++) {
t[k] = sortorder[k];
}
sortorder = t;
}
System.out.println("New sort order is " + sortorder);
}
public void prependSortOrder(int k) {
int value[] = new int[sortorder.length + 1];
value[0] = k;
System.arraycopy(sortorder, 0, value, 1, sortorder.length);
setSortOrder(value);
}
public int[] getSortOrder() {
return sortorder;
}
public int getNumMessages() {
Assert.NotYetImplemented("getNumMessages");
return 0;
}
public ViewedMessage getMessageRoot() {
return fRoot;
}
public void setIsThreaded(boolean b) {
threaded = b;
}
public boolean isThreaded() {
return threaded;
}
public Folder getFolder() {
return fFolder;
}
public void dumpMessages() {
fRoot.dump();
}
public void messageChanged(MessageChangedEvent event) {
ViewedMessage viewed[] = new ViewedMessage[1];
viewed[0] = findViewedMessage(event.getMessage());
for (int i=observers.size() - 1 ; i>=0 ; i--) {
((MessageSetViewObserver) observers.elementAt(i)).messagesChanged
(null, null, new ArrayEnumeration(viewed));
}
}
public void messagesAdded(MessageCountEvent event) {
Message[] messages = event.getMessages();
ViewedMessage viewed[] = new ViewedMessage[messages.length];
for (int i=0 ; i<messages.length ; i++) {
viewed[i] = findViewedMessage(messages[i]);
}
for (int i=observers.size() - 1 ; i>=0 ; i--) {
((MessageSetViewObserver) observers.elementAt(i)).messagesChanged
(new ArrayEnumeration(viewed), null, null);
}
}
public void messagesRemoved(MessageCountEvent event) {
Message[] messages = event.getMessages();
ViewedMessage viewed[] = new ViewedMessage[messages.length];
for (int i=0 ; i<messages.length ; i++) {
viewed[i] = findViewedMessage(messages[i]);
}
for (int i=observers.size() - 1 ; i>=0 ; i--) {
((MessageSetViewObserver) observers.elementAt(i)).messagesChanged
(null, new ArrayEnumeration(viewed), null);
}
}
protected ViewedMessage findViewedMessage(Message m, ViewedMessage v) {
for ( ; v != null ; v = v.getNext()) {
if (v.getMessage() == m) return v;
ViewedMessage result = findViewedMessage(m, v.getChild());
if (result != null) return result;
}
return null;
}
protected ViewedMessage findViewedMessage(Message m) {
ViewedMessage result = findViewedMessage(m, fRoot);
if (result == null) {
result = new ViewedMessageBase(this, m);
}
return result;
}
public void addObserver(MessageSetViewObserver obs) {
observers.addElement(obs);
}
public void removeObserver(MessageSetViewObserver obs) {
observers.removeElement(obs);
}
final class NullComparer implements Comparer {
public int compare(Object a, Object b) {
return 0;
}
}
final class NumberComparer implements Comparer {
Comparer next;
int cache[];
NumberComparer(int nummsgs, Comparer next) {
cache = new int[nummsgs];
this.next = next;
}
int getMessageNumber(Object a) {
ViewedMessageBase v = (ViewedMessageBase) a;
int key = v.sortkey;
cache[key] = v.getMessage().getMessageNumber();
return cache[key];
}
public int compare(Object a, Object b) {
int k1 = cache[((ViewedMessageBase)a).sortkey];
int k2 = cache[((ViewedMessageBase)b).sortkey];
if (k1 == 0) k1 = getMessageNumber(a);
if (k2 == 0) k2 = getMessageNumber(b);
if (k1 != k2) return k2 - k1;
return next.compare(a, b);
}
}
final class DateComparer implements Comparer {
Comparer next;
long cache[];
DateComparer(int nummsgs, Comparer next) {
cache = new long[nummsgs];
this.next = next;
}
long getTime(Object a) {
ViewedMessageBase v = (ViewedMessageBase) a;
int key = v.sortkey;
try {
cache[key] = v.getMessage().getSentDate().getTime();
} catch (MessagingException e) {
} catch (NullPointerException e) {
}
if (cache[key] == 0) {
cache[key] = 1;
}
return cache[key];
}
public int compare(Object a, Object b) {
long k1 = cache[((ViewedMessageBase)a).sortkey];
long k2 = cache[((ViewedMessageBase)b).sortkey];
if (k1 == 0) k1 = getTime(a);
if (k2 == 0) k2 = getTime(b);
if (k1 != k2) {
// I'd like to just cast the difference to an int and return that, but
// I'm afraid that sign conversion will screw up. So we do it by hand.
if (k1 < k2) return 1;
else return -1;
}
return next.compare(a, b);
}
}
final class SubjectComparer implements Comparer {
Comparer next;
String cache[];
SubjectComparer(int nummsgs, Comparer next) {
cache = new String[nummsgs];
this.next = next;
}
String getSubject(Object a) {
ViewedMessageBase v = (ViewedMessageBase) a;
int key = v.sortkey;
try {
cache[key] =
MessageExtraFactory.Get(v.getMessage()).simplifiedSubject();
} catch (MessagingException e) {
}
if (cache[key] == null) {
cache[key] = "";
}
return cache[key];
}
public int compare(Object a, Object b) {
String k1 = cache[((ViewedMessageBase)a).sortkey];
String k2 = cache[((ViewedMessageBase)b).sortkey];
if (k1 == null) k1 = getSubject(a);
if (k2 == null) k2 = getSubject(b);
int result = k1.compareTo(k2);
if (result != 0) return result;
return next.compare(a, b);
}
}
final class AuthorComparer implements Comparer {
Comparer next;
String cache[];
AuthorComparer(int nummsgs, Comparer next) {
cache = new String[nummsgs];
this.next = next;
}
String getAuthor(Object a) {
ViewedMessageBase v = (ViewedMessageBase) a;
int key = v.sortkey;
try {
cache[key] =
MessageExtraFactory.Get(v.getMessage()).getAuthor();
} catch (MessagingException e) {
}
if (cache[key] == null) {
cache[key] = "";
}
return cache[key];
}
public int compare(Object a, Object b) {
String k1 = cache[((ViewedMessageBase)a).sortkey];
String k2 = cache[((ViewedMessageBase)b).sortkey];
if (k1 == null) k1 = getAuthor(a);
if (k2 == null) k2 = getAuthor(b);
int result = k1.compareTo(k2);
if (result != 0) return result;
return next.compare(a, b);
}
}
final class ReadComparer implements Comparer {
Comparer next;
int cache[];
ReadComparer(int nummsgs, Comparer next) {
cache = new int[nummsgs];
this.next = next;
}
int getRead(Object a) {
ViewedMessageBase v = (ViewedMessageBase) a;
int key = v.sortkey;
try {
cache[key] =
MessageExtraFactory.Get(v.getMessage()).isRead() ? 1 : 2;
} catch (MessagingException e) {
cache[key] = 2; // False by default.
}
return cache[key];
}
public int compare(Object a, Object b) {
int k1 = cache[((ViewedMessageBase)a).sortkey];
int k2 = cache[((ViewedMessageBase)b).sortkey];
if (k1 == 0) k1 = getRead(a);
if (k2 == 0) k2 = getRead(b);
if (k1 != k2) return k2 - k1;
return next.compare(a, b);
}
}
final class FlaggedComparer implements Comparer {
Comparer next;
int cache[];
FlaggedComparer(int nummsgs, Comparer next) {
cache = new int[nummsgs];
this.next = next;
}
int getFlagged(Object a) {
ViewedMessageBase v = (ViewedMessageBase) a;
int key = v.sortkey;
try {
cache[key] =
MessageExtraFactory.Get(v.getMessage()).isFlagged() ? 1 : 2;
} catch (MessagingException e) {
cache[key] = 2; // False by default.
}
return cache[key];
}
public int compare(Object a, Object b) {
int k1 = cache[((ViewedMessageBase)a).sortkey];
int k2 = cache[((ViewedMessageBase)b).sortkey];
if (k1 == 0) k1 = getFlagged(a);
if (k2 == 0) k2 = getFlagged(b);
if (k1 != k2) return k2 - k1;
return next.compare(a, b);
}
}
final class SizeComparer implements Comparer {
Comparer next;
int cache[];
SizeComparer(int nummsgs, Comparer next) {
cache = new int[nummsgs];
this.next = next;
}
int getSize(Object a) {
ViewedMessageBase v = (ViewedMessageBase) a;
int key = v.sortkey;
try {
cache[key] = v.getMessage().getSize();
} catch (MessagingException e) {
}
if (cache[key] == 0) cache[key] = -1;
return cache[key];
}
public int compare(Object a, Object b) {
int k1 = cache[((ViewedMessageBase)a).sortkey];
int k2 = cache[((ViewedMessageBase)b).sortkey];
if (k1 == 0) k1 = getSize(a);
if (k2 == 0) k2 = getSize(b);
if (k1 != k2) return k2 - k1;
return next.compare(a, b);
}
}
final class DeletedComparer implements Comparer {
Comparer next;
int cache[];
DeletedComparer(int nummsgs, Comparer next) {
cache = new int[nummsgs];
this.next = next;
}
int getDeleted(Object a) {
ViewedMessageBase v = (ViewedMessageBase) a;
int key = v.sortkey;
try {
cache[key] =
MessageExtraFactory.Get(v.getMessage()).isDeleted() ? 1 : 2;
} catch (MessagingException e) {
cache[key] = 2; // False by default.
}
return cache[key];
}
public int compare(Object a, Object b) {
int k1 = cache[((ViewedMessageBase)a).sortkey];
int k2 = cache[((ViewedMessageBase)b).sortkey];
if (k1 == 0) k1 = getDeleted(a);
if (k2 == 0) k2 = getDeleted(b);
if (k1 != k2) return k2 - k1;
return next.compare(a, b);
}
}
final class ViewedMessageEnumeration extends PrefetchEnumeration {
ViewedMessage cur;
ViewedMessageEnumeration(ViewedMessage obj) {
cur = obj;
}
protected Object fetch() {
Object result = cur;
if (cur != null) {
ViewedMessage tmp = cur.getChild();
if (tmp != null) cur = tmp;
else {
tmp = cur.getNext();
if (tmp != null) cur = tmp;
else {
tmp = cur.getParent();
while (tmp != null && tmp.getNext() == null) {
tmp = tmp.getParent();
}
if (tmp != null) cur = tmp.getNext();
else cur = null;
}
}
}
return result;
}
}
}