KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > columba > mail > gui > table > model > TableModelThreadedView


1 // The contents of this file are subject to the Mozilla Public License Version
2
// 1.1
3
//(the "License"); you may not use this file except in compliance with the
4
//License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
5
//
6
//Software distributed under the License is distributed on an "AS IS" basis,
7
//WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
8
//for the specific language governing rights and
9
//limitations under the License.
10
//
11
//The Original Code is "The Columba Project"
12
//
13
//The Initial Developers of the Original Code are Frederik Dietz and Timo
14
// Stich.
15
//Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
16
//
17
//All Rights Reserved.
18
package org.columba.mail.gui.table.model;
19
20 import java.text.Collator JavaDoc;
21 import java.util.Collections JavaDoc;
22 import java.util.Comparator JavaDoc;
23 import java.util.Date JavaDoc;
24 import java.util.Enumeration JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.StringTokenizer JavaDoc;
29 import java.util.logging.Logger JavaDoc;
30
31 import org.columba.mail.message.ColumbaHeader;
32 import org.columba.mail.message.IColumbaHeader;
33
34 /**
35  * Threaded model using Message-Id:, In-Reply-To: and References: headers to
36  * create a tree structure of discussions.
37  * <p>
38  * The algorithm works this way:
39  * <ul>
40  * <li>save every header in hashmap, use Message-Id: as key, MessageNode as
41  * value</li>
42  * <li>for each header, check if In-Reply-To:, or References: points to a
43  * matching Message-Id:. If match was found, add header as child</li>
44  * </ul>
45  * <p>
46  *
47  * @author fdietz
48  */

49 public class TableModelThreadedView implements ModelVisitor {
50
51     /** JDK 1.4+ logging framework logger, used for logging. */
52     private static final Logger JavaDoc LOG = Logger
53             .getLogger("org.columba.mail.gui.table.model");
54
55     private boolean enabled;
56
57     private HashMap JavaDoc hashtable;
58
59     private int idCount = 0;
60
61     private Collator JavaDoc collator;
62
63     public TableModelThreadedView() {
64
65         enabled = false;
66
67         collator = Collator.getInstance();
68     }
69
70     public boolean isEnabled() {
71         return enabled;
72     }
73
74     public void setEnabled(boolean b) {
75         enabled = b;
76     }
77
78     /**
79      * Parse References: header value and save every found Message-Id: in array.
80      * <p>
81      * TODO (@author tistch): cleanup tokenizer, this could be much faster using
82      * regexp
83      *
84      * @param references
85      * References: header value
86      *
87      * @return array containing Message-Id: header values
88      */

89     public static String JavaDoc[] parseReferences(String JavaDoc references) {
90
91         StringTokenizer JavaDoc tk = new StringTokenizer JavaDoc(references, ">");
92         String JavaDoc[] list = new String JavaDoc[tk.countTokens()];
93         int i = 0;
94
95         while (tk.hasMoreTokens()) {
96             String JavaDoc str = (String JavaDoc) tk.nextToken();
97             str = str.trim();
98             str = str + ">";
99             list[i++] = str;
100
101         }
102
103         return list;
104     }
105
106     protected boolean add(MessageNode node, MessageNode rootNode) {
107         IColumbaHeader header = node.getHeader();
108         String JavaDoc references = (String JavaDoc) header.get("References");
109         String JavaDoc inReply = (String JavaDoc) header.get("In-Reply-To");
110
111         if (inReply != null) {
112             inReply = inReply.trim();
113
114             if (hashtable.containsKey(inReply)) {
115
116                 MessageNode parent = (MessageNode) hashtable.get(inReply);
117                 if ( !parent.isNodeAncestor(node))
118                     parent.add(node);
119
120                 return true;
121             }
122         } else if (references != null) {
123             references = references.trim();
124
125             String JavaDoc[] referenceList = parseReferences(references);
126             int count = referenceList.length;
127
128             if (count >= 1) {
129                 // the last element is the direct parent
130
MessageNode parent = (MessageNode) hashtable
131                         .get(referenceList[referenceList.length - 1].trim());
132                 if (parent != null) {
133                     parent.add(node);
134                     return true;
135                 }
136             }
137         }
138
139         return false;
140     }
141
142     // create tree structure
143
protected void thread(MessageNode rootNode) {
144         idCount = 0;
145
146         if (rootNode == null) {
147             return;
148         }
149
150         // save every MessageNode in hashmap for later reference
151
createHashmap(rootNode);
152
153         // for each element in the message-header-reference or in-reply-to
154
// headerfield: - find a container whose message-id matches and add
155
// message, otherwise create empty container
156
Iterator JavaDoc it = hashtable.keySet().iterator();
157         while (it.hasNext()) {
158             String JavaDoc key = (String JavaDoc) it.next();
159
160             MessageNode node = (MessageNode) hashtable.get(key);
161             add(node, rootNode);
162         }
163
164     }
165
166     private String JavaDoc getMessageID(MessageNode node) {
167         IColumbaHeader header = node.getHeader();
168
169         String JavaDoc id = (String JavaDoc) header.get("Message-ID");
170
171         if (id == null) {
172             id = (String JavaDoc) header.get("Message-Id");
173         }
174
175         // if no Message-Id: available create bogus
176
if (id == null) {
177             id = new String JavaDoc("<bogus-id:" + (idCount++) + ">");
178             header.set("Message-ID", id);
179         }
180
181         id = id.trim();
182
183         return id;
184     }
185
186     /**
187      * Save every MessageNode in HashMap for later reference.
188      * <p>
189      * Message-Id: is key, MessageNode is value
190      *
191      * @param rootNode
192      * root node
193      */

194     private void createHashmap(MessageNode rootNode) {
195         hashtable = new HashMap JavaDoc(rootNode.getChildCount());
196
197         // save every message-id in hashtable for later reference
198
for (Enumeration JavaDoc enumeration = rootNode.children(); enumeration
199                 .hasMoreElements();) {
200             MessageNode node = (MessageNode) enumeration.nextElement();
201             String JavaDoc id = getMessageID(node);
202
203             hashtable.put(id, node);
204
205         }
206     }
207
208     /**
209      *
210      * sort all children after date
211      *
212      * @param node
213      * root MessageNode
214      */

215     protected void sort(int columnNumber, MessageNode node) {
216         for (int i = 0; i < node.getChildCount(); i++) {
217             MessageNode child = (MessageNode) node.getChildAt(i);
218
219             //if ( ( child.isLeaf() == false ) && ( !child.getParent().equals(
220
// node ) ) )
221
if (!child.isLeaf()) {
222                 // has children
223
List JavaDoc v = child.getVector();
224                 Collections.sort(v, new MessageHeaderComparator(columnNumber,
225                         true));
226
227                 // check if there are messages marked as recent
228
// -> in case underline parent node
229
boolean contains = containsRecentChildren(child);
230                 child.setHasRecentChildren(contains);
231             }
232         }
233     }
234
235     protected boolean containsRecentChildren(MessageNode parent) {
236         for (int i = 0; i < parent.getChildCount(); i++) {
237             MessageNode child = (MessageNode) parent.getChildAt(i);
238
239             if (((ColumbaHeader) child.getHeader()).getFlags().getSeen() == false) {
240                 // recent found
241
LOG.info("found recent message");
242
243                 return true;
244             } else {
245                 containsRecentChildren(child);
246             }
247         }
248
249         return false;
250     }
251
252     class MessageHeaderComparator implements Comparator JavaDoc {
253
254         protected int column;
255
256         protected boolean ascending;
257
258         private String JavaDoc columnName;
259
260         public MessageHeaderComparator(int sortCol, boolean sortAsc) {
261             column = sortCol;
262             ascending = sortAsc;
263
264             this.columnName = "Date";
265         }
266
267         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
268
269             MessageNode node1 = (MessageNode) o1;
270             MessageNode node2 = (MessageNode) o2;
271
272             IColumbaHeader header1 = node1.getHeader();
273             IColumbaHeader header2 = node2.getHeader();
274
275             if ((header1 == null) || (header2 == null)) {
276                 return 0;
277             }
278
279             int result = 0;
280
281             if (columnName.equals("Date")) {
282                 Date JavaDoc d1 = (Date JavaDoc) header1.get("columba.date");
283                 Date JavaDoc d2 = (Date JavaDoc) header2.get("columba.date");
284
285                 if ((d1 == null) || (d2 == null)) {
286                     result = 0;
287                 } else {
288                     result = d1.compareTo(d2);
289                 }
290             } else {
291                 Object JavaDoc item1 = header1.get(columnName);
292                 Object JavaDoc item2 = header2.get(columnName);
293
294                 if ((item1 != null) && (item2 == null)) {
295                     result = 1;
296                 } else if ((item1 == null) && (item2 != null)) {
297                     result = -1;
298                 } else if ((item1 == null) && (item2 == null)) {
299                     result = 0;
300                 } else if (item1 instanceof String JavaDoc) {
301                     result = collator.compare((String JavaDoc) item1, (String JavaDoc) item2);
302                 }
303             }
304
305             if (!ascending) {
306                 result = -result;
307             }
308
309             return result;
310         }
311
312         public boolean equals(Object JavaDoc obj) {
313             if (obj instanceof MessageHeaderComparator) {
314                 MessageHeaderComparator compObj = (MessageHeaderComparator) obj;
315
316                 return (compObj.column == column)
317                         && (compObj.ascending == ascending);
318             }
319
320             return false;
321         }
322     }
323
324     /**
325      * @see org.columba.mail.gui.table.model.ModelVisitor#visit(org.columba.mail.gui.table.model.TreeTableModelInterface)
326      */

327     public void visit(HeaderTableModel realModel) {
328         if ( enabled == false ) return;
329         
330         thread(realModel.getRootNode());
331
332         // go through whole tree and sort the siblings after date
333
sort(realModel.getColumnNumber("Date"), realModel.getRootNode());
334     }
335 }
Popular Tags