001: /*
002: * SortByThread.java
003: *
004: * Copyright (C) 2002-2003 Peter Graves
005: * $Id: SortByThread.java,v 1.2 2003/05/30 14:59:57 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j.mail;
023:
024: import java.util.ArrayList;
025: import java.util.Collections;
026: import java.util.Comparator;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Map;
031: import javax.swing.tree.DefaultMutableTreeNode;
032: import org.armedbear.j.Debug;
033: import org.armedbear.j.Log;
034:
035: public final class SortByThread {
036: private final List entries;
037:
038: private final Node root = new Node();
039: private final Map idMap = new HashMap();
040:
041: public SortByThread(List entries) {
042: this .entries = Collections.unmodifiableList(entries);
043: }
044:
045: public void run() {
046: final int count = entries.size();
047: // Create all the nodes and populate the ID map.
048: ArrayList nodes = new ArrayList(count);
049: for (int i = 0; i < count; i++) {
050: final MailboxEntry entry = (MailboxEntry) entries.get(i);
051: Node node = new Node(entry);
052: nodes.add(node);
053: String messageId = entry.getMessageId();
054: if (messageId != null && messageId.length() > 0)
055: idMap.put(messageId, node);
056: else
057: Debug.bug();
058: }
059:
060: for (int i = 0; i < count; i++) {
061: final MailboxEntry entry = (MailboxEntry) entries.get(i);
062: // References.
063: String[] references = entry.getReferences();
064: if (references != null) {
065: for (int j = 0; j < references.length; j++) {
066: String reference = references[j];
067: if (reference == null || reference.length() == 0)
068: continue;
069: Node node = (Node) idMap.get(reference);
070: if (node == null) {
071: node = new Node(reference, entry
072: .getBaseSubject());
073: idMap.put(reference, node);
074: nodes.add(node);
075: }
076: }
077: }
078: // In-Reply-To.
079: String inReplyTo = entry.getInReplyTo();
080: if (inReplyTo != null && inReplyTo.length() > 0) {
081: Node node = (Node) idMap.get(inReplyTo);
082: if (node == null) {
083: node = new Node(inReplyTo, entry.getBaseSubject());
084: idMap.put(inReplyTo, node);
085: nodes.add(node);
086: }
087: }
088: }
089:
090: Iterator iter = nodes.iterator();
091: while (iter.hasNext()) {
092: Node node = (Node) iter.next();
093: if (node.getParent() == null) {
094: Node parent = findParentForNode(node);
095: if (parent != null)
096: parent.add(node);
097: else
098: root.add(node);
099: }
100: }
101:
102: removeEmptyContainers(root);
103:
104: groupMessagesBySubject();
105:
106: // Walk the tree and sort the entries by date (or whatever).
107: sort(root);
108: }
109:
110: private void removeEmptyContainers(final Node parent) {
111: for (int i = 0; i < parent.getChildCount(); i++) {
112: Node node = (Node) parent.getChildAt(i);
113: if (node.getMailboxEntry() == null
114: && node.getChildCount() == 0) {
115: // An empty container with no children.
116: parent.remove(i);
117: --i;
118: continue;
119: }
120: if (node.getMailboxEntry() == null
121: && node.getChildCount() > 0) {
122: Debug.assertTrue(node.getParent() == parent);
123: if (parent != root || node.getChildCount() == 1) {
124: for (int j = node.getChildCount(); j-- > 0;) {
125: Node child = (Node) node.getChildAt(j);
126: Debug.assertTrue(child.getParent() == node);
127: node.remove(j);
128: Debug.assertTrue(child.getParent() == null);
129: parent.add(child);
130: Debug.assertTrue(child.getParent() == parent);
131: }
132: Debug.assertTrue(node.getChildCount() == 0);
133: parent.remove(i);
134: --i;
135: continue;
136: }
137: }
138: if (node.getMailboxEntry() != null) {
139: removeEmptyContainers(node);
140: }
141: }
142: }
143:
144: private void groupMessagesBySubject() {
145: HashMap subjectMap = createSubjectMap();
146:
147: // Iterate through top-level nodes.
148: for (int i = root.getChildCount(); i-- > 0;) {
149: final Node node = (Node) root.getChildAt(i);
150: MailboxEntry entry = node.getMailboxEntry();
151: if (entry == null) {
152: if (node.getChildCount() > 0)
153: entry = ((Node) node.getChildAt(0))
154: .getMailboxEntry();
155: if (entry == null)
156: continue;
157: }
158: final String baseSubject = entry.getBaseSubject();
159: if (baseSubject == null || baseSubject.length() == 0)
160: continue;
161: Node oldNode = (Node) subjectMap.get(baseSubject);
162: if (oldNode == node)
163: continue;
164:
165: if (oldNode.isDummy() && node.isDummy()) {
166: // Add node's children to old node.
167: for (int j = node.getChildCount(); j-- > 0;) {
168: Node child = (Node) node.getChildAt(j);
169: node.remove(j);
170: oldNode.add(child);
171: }
172: // Remove node.
173: root.remove(i);
174: continue;
175: }
176:
177: if (oldNode.isDummy()) {
178: Debug.assertTrue(!node.isDummy());
179: // Make this node be a child of the other.
180: root.remove(i);
181: oldNode.add(node);
182: continue;
183: }
184:
185: Debug.assertTrue(!oldNode.isDummy());
186: final MailboxEntry oldEntry = oldNode.getMailboxEntry();
187: Debug.assertTrue(oldEntry != null);
188: if (!node.isDummy()) {
189: if (entry.subjectIsReply()
190: && !oldEntry.subjectIsReply()) {
191: // Make this node be a child of the other.
192: root.remove(i);
193: oldNode.add(node);
194: continue;
195: }
196: }
197:
198: // If neither entry is a reply, leave them both at top level.
199: if (!entry.subjectIsReply() && !oldEntry.subjectIsReply())
200: continue;
201:
202: // Make old and new nodes be children of a new dummy node.
203: // Create a new container for the old message.
204: Node c = new Node(oldEntry);
205: for (int j = oldNode.getChildCount(); j-- > 0;) {
206: Node child = (Node) oldNode.getChildAt(j);
207: oldNode.remove(j);
208: c.add(child);
209: }
210: // Make old container into a dummy by emptying it.
211: oldNode.setUserObject(null);
212: oldNode.setBaseSubject(baseSubject);
213: // Add old and new messages to new dummy node.
214: oldNode.add(c);
215: oldNode.add(node);
216: }
217:
218: subjectMap.clear();
219: subjectMap = null;
220: }
221:
222: private HashMap createSubjectMap() {
223: HashMap subjectMap = new HashMap();
224: for (int i = 0, limit = root.getChildCount(); i < limit; i++) {
225: final Node node = (Node) root.getChildAt(i);
226: MailboxEntry entry = node.getMailboxEntry();
227: if (entry == null) {
228: // Dummy node.
229: if (node.getChildCount() > 0)
230: entry = ((Node) node.getChildAt(0))
231: .getMailboxEntry();
232: else {
233: Log.debug("dummy node child count is zero");
234: continue;
235: }
236: }
237: if (entry != null) {
238: String baseSubject = entry.getBaseSubject();
239: if (baseSubject != null && baseSubject.length() > 0) {
240: Node oldNode = (Node) subjectMap.get(baseSubject);
241: if (oldNode == null) {
242: subjectMap.put(baseSubject, node);
243: } else {
244: MailboxEntry oldEntry = oldNode
245: .getMailboxEntry();
246: if (oldEntry != null
247: && oldEntry.subjectIsReply()
248: && !entry.subjectIsReply()) {
249: subjectMap.put(baseSubject, node);
250: } else if (node.getMailboxEntry() == null
251: && oldEntry != null) {
252: subjectMap.put(baseSubject, node);
253: }
254: }
255: }
256: }
257: }
258: return subjectMap;
259: }
260:
261: private void sort(Node node) {
262: // Depth first!
263: for (int i = 0, limit = node.getChildCount(); i < limit; i++)
264: sort((Node) node.getChildAt(i));
265: if (node.getChildCount() > 1)
266: node.sortChildren();
267: }
268:
269: private Node findParentForNode(Node node) {
270: final MailboxEntry entry = node.getMailboxEntry();
271: if (entry == null)
272: return null;
273: final String inReplyTo = entry.getInReplyTo();
274: if (inReplyTo != null) {
275: Node parent = (Node) idMap.get(inReplyTo);
276: if (parent != null)
277: if (!parent.isNodeAncestor(node))
278: return parent;
279: }
280: final String[] references = entry.getReferences();
281: if (references != null) {
282: for (int i = references.length; i-- > 0;) {
283: Node parent = (Node) idMap.get(references[i]);
284: if (parent != null)
285: if (!parent.isNodeAncestor(node))
286: return parent;
287: }
288: }
289: return null;
290: }
291:
292: private int sequenceNumber;
293:
294: public void addEntries(Mailbox mb, MailboxFilter filter) {
295: sequenceNumber = 1;
296: addEntriesForNode(root, mb, filter, 0);
297: }
298:
299: private void addEntriesForNode(Node node, Mailbox mb,
300: MailboxFilter filter, int depth) {
301: if (node != root) {
302: MailboxEntry entry = node.getMailboxEntry();
303: if (entry != null) {
304: entry.setSequenceNumber(sequenceNumber++);
305: if (filter == null || filter.accept(entry))
306: mb.appendLine(entry, depth);
307: } else {
308: if (node.getChildCount() > 0) {
309: Node child = (Node) node.getChildAt(0);
310: MailboxEntry childEntry = child.getMailboxEntry();
311: if (childEntry != null)
312: childEntry.setOrphan(true);
313: }
314: }
315: }
316: for (int i = 0, limit = node.getChildCount(); i < limit; i++)
317: addEntriesForNode((Node) node.getChildAt(i), mb, filter,
318: depth + 1);
319: }
320: }
321:
322: class Node extends DefaultMutableTreeNode {
323: private String messageId;
324: private String baseSubject;
325:
326: Node() {
327: super ();
328: }
329:
330: Node(MailboxEntry entry) {
331: super (entry);
332: }
333:
334: Node(String messageId, String baseSubject) {
335: this .messageId = messageId;
336: this .baseSubject = baseSubject;
337: }
338:
339: MailboxEntry getMailboxEntry() {
340: return (MailboxEntry) getUserObject();
341: }
342:
343: String getBaseSubject() {
344: return baseSubject;
345: }
346:
347: void setBaseSubject(String s) {
348: baseSubject = s;
349: }
350:
351: String getMessageId() {
352: return messageId;
353: }
354:
355: RFC822Date getDate() {
356: MailboxEntry entry = getMailboxEntry();
357: if (entry != null)
358: return entry.getDate();
359: if (getChildCount() > 0) {
360: Node child = (Node) getChildAt(0);
361: entry = child.getMailboxEntry();
362: if (entry != null)
363: return entry.getDate();
364: }
365: Log.debug("getDate no date");
366: return null;
367: }
368:
369: boolean isDummy() {
370: return getUserObject() == null;
371: }
372:
373: void sortChildren() {
374: if (children != null)
375: sortEntriesByDate(children);
376: }
377:
378: private static final Comparator comparator = new Comparator() {
379: public int compare(Object o1, Object o2) {
380: return RFC822Date.compare(((Node) o1).getDate(),
381: ((Node) o2).getDate());
382: }
383: };
384:
385: private static final void sortEntriesByDate(List list) {
386: Collections.sort(list, comparator);
387: }
388: }
|