1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.net.nntp;
19
20 import java.util.Arrays;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24
25
26
27
28
29
30
31
32 public class Threader {
33
34
35
36
37 public Threader() {
38
39 }
40
41
42
43
44
45
46 private void buildContainer(final Threadable threadable, final HashMap<String, NntpThreadContainer> idTable) {
47 String id = threadable.messageThreadId();
48 NntpThreadContainer container = idTable.get(id);
49 int bogusIdCount = 0;
50
51
52
53 if (container != null) {
54 if (container.threadable != null) {
55 bogusIdCount++;
56 id = "<Bogus-id:" + bogusIdCount + ">";
57 container = null;
58 } else {
59
60
61 container.threadable = threadable;
62 }
63 }
64
65
66 if (container == null) {
67 container = new NntpThreadContainer();
68 container.threadable = threadable;
69 idTable.put(id, container);
70 }
71
72
73
74 NntpThreadContainer parentRef = null;
75 {
76 final String[] references = threadable.messageThreadReferences();
77 for (final String refString : references) {
78 NntpThreadContainer ref = idTable.get(refString);
79
80
81 if (ref == null) {
82 ref = new NntpThreadContainer();
83 idTable.put(refString, ref);
84 }
85
86
87
88
89 if (parentRef != null && ref.parent == null && parentRef != ref && !ref.findChild(parentRef)) {
90
91 ref.parent = parentRef;
92 ref.next = parentRef.child;
93 parentRef.child = ref;
94 }
95 parentRef = ref;
96 }
97 }
98
99
100
101 if (parentRef != null && (parentRef == container || container.findChild(parentRef))) {
102 parentRef = null;
103 }
104
105
106
107
108 if (container.parent != null) {
109 NntpThreadContainer rest;
110 NntpThreadContainer prev;
111
112 for (prev = null, rest = container.parent.child; rest != null; prev = rest, rest = rest.next) {
113 if (rest == container) {
114 break;
115 }
116 }
117
118 if (rest == null) {
119 throw new IllegalStateException("Didnt find " + container + " in parent " + container.parent);
120 }
121
122
123 if (prev == null) {
124 container.parent.child = container.next;
125 } else {
126 prev.next = container.next;
127 }
128
129 container.next = null;
130 container.parent = null;
131 }
132
133
134 if (parentRef != null) {
135 container.parent = parentRef;
136 container.next = parentRef.child;
137 parentRef.child = container;
138 }
139 }
140
141
142
143
144
145
146
147 private NntpThreadContainer findRootSet(final HashMap<String, NntpThreadContainer> idTable) {
148 final NntpThreadContainer root = new NntpThreadContainer();
149 for (final Map.Entry<String, NntpThreadContainer> entry : idTable.entrySet()) {
150 final NntpThreadContainer c = entry.getValue();
151 if (c.parent == null) {
152 if (c.next != null) {
153 throw new IllegalStateException("c.next is " + c.next.toString());
154 }
155 c.next = root.child;
156 root.child = c;
157 }
158 }
159 return root;
160 }
161
162
163
164
165
166
167 private void gatherSubjects(final NntpThreadContainer root) {
168
169 int count = 0;
170
171 for (NntpThreadContainer c = root.child; c != null; c = c.next) {
172 count++;
173 }
174
175
176 HashMap<String, NntpThreadContainer> subjectTable = new HashMap<>((int) (count * 1.2), (float) 0.9);
177 count = 0;
178
179 for (NntpThreadContainer c = root.child; c != null; c = c.next) {
180 Threadable threadable = c.threadable;
181
182
183
184
185 if (threadable == null) {
186 threadable = c.child.threadable;
187 }
188
189 final String subj = threadable.simplifiedSubject();
190
191 if (subj == null || subj.isEmpty()) {
192 continue;
193 }
194
195 final NntpThreadContainer old = subjectTable.get(subj);
196
197
198
199
200
201
202
203
204 if (old == null || c.threadable == null && old.threadable != null
205 || old.threadable != null && old.threadable.subjectIsReply() && c.threadable != null && !c.threadable.subjectIsReply()) {
206 subjectTable.put(subj, c);
207 count++;
208 }
209 }
210
211
212 if (count == 0) {
213 return;
214 }
215
216
217
218 NntpThreadContainer prev;
219 NntpThreadContainer c;
220 NntpThreadContainer rest;
221 for (prev = null, c = root.child, rest = c.next; c != null; prev = c, c = rest, rest = rest == null ? null : rest.next) {
222 Threadable threadable = c.threadable;
223
224
225 if (threadable == null) {
226 threadable = c.child.threadable;
227 }
228
229 final String subj = threadable.simplifiedSubject();
230
231
232 if (subj == null || subj.isEmpty()) {
233 continue;
234 }
235
236 final NntpThreadContainer old = subjectTable.get(subj);
237
238 if (old == c) {
239 continue;
240 }
241
242
243
244 if (prev == null) {
245 root.child = c.next;
246 } else {
247 prev.next = c.next;
248 }
249 c.next = null;
250
251 if (old.threadable == null && c.threadable == null) {
252
253 NntpThreadContainer tail;
254 for (tail = old.child; tail != null && tail.next != null; tail = tail.next) {
255
256 }
257
258 if (tail != null) {
259 tail.next = c.child;
260 }
261
262 for (tail = c.child; tail != null; tail = tail.next) {
263 tail.parent = old;
264 }
265
266 c.child = null;
267 } else if (old.threadable == null || c.threadable != null && c.threadable.subjectIsReply() && !old.threadable.subjectIsReply()) {
268
269 c.parent = old;
270 c.next = old.child;
271 old.child = c;
272 } else {
273
274
275 final NntpThreadContainer newc = new NntpThreadContainer();
276 newc.threadable = old.threadable;
277 newc.child = old.child;
278
279 for (NntpThreadContainer tail = newc.child; tail != null; tail = tail.next) {
280 tail.parent = newc;
281 }
282
283 old.threadable = null;
284 old.child = null;
285
286 c.parent = old;
287 newc.parent = old;
288
289
290 old.child = c;
291 c.next = newc;
292 }
293
294 c = prev;
295 }
296
297 subjectTable.clear();
298 subjectTable = null;
299
300 }
301
302
303
304
305
306
307 private void pruneEmptyContainers(final NntpThreadContainer parent) {
308 NntpThreadContainer container;
309 NntpThreadContainer prev;
310 NntpThreadContainer next;
311 for (prev = null, container = parent.child, next = container.next; container != null; prev = container, container = next, next = container == null
312 ? null
313 : container.next) {
314
315
316 if (container.threadable == null && container.child == null) {
317 if (prev == null) {
318 parent.child = container.next;
319 } else {
320 prev.next = container.next;
321 }
322
323 container = prev;
324
325 } else if (container.threadable == null && (container.parent != null || container.child.next == null)) {
326
327 NntpThreadContainer tail;
328 final NntpThreadContainer kids = container.child;
329
330
331 if (prev == null) {
332 parent.child = kids;
333 } else {
334 prev.next = kids;
335 }
336
337
338
339
340 for (tail = kids; tail.next != null; tail = tail.next) {
341 tail.parent = container.parent;
342 }
343
344 tail.parent = container.parent;
345 tail.next = container.next;
346
347
348
349 next = kids;
350
351
352 container = prev;
353 } else if (container.child != null) {
354
355
356 pruneEmptyContainers(container);
357 }
358 }
359 }
360
361
362
363
364
365
366
367
368 public Threadable thread(final Iterable<? extends Threadable> messages) {
369 if (messages == null) {
370 return null;
371 }
372
373 HashMap<String, NntpThreadContainer> idTable = new HashMap<>();
374
375
376 for (final Threadable t : messages) {
377 if (!t.isDummy()) {
378 buildContainer(t, idTable);
379 }
380 }
381
382 if (idTable.isEmpty()) {
383 return null;
384 }
385
386 final NntpThreadContainer root = findRootSet(idTable);
387 idTable.clear();
388 idTable = null;
389
390 pruneEmptyContainers(root);
391
392 root.reverseChildren();
393 gatherSubjects(root);
394
395 if (root.next != null) {
396 throw new IllegalStateException("root node has a next:" + root);
397 }
398
399 for (NntpThreadContainer r = root.child; r != null; r = r.next) {
400 if (r.threadable == null) {
401 r.threadable = r.child.threadable.makeDummy();
402 }
403 }
404
405 final Threadable result = root.child == null ? null : root.child.threadable;
406 root.flush();
407
408 return result;
409 }
410
411
412
413
414
415
416
417
418 public Threadable thread(final List<? extends Threadable> messages) {
419 return thread((Iterable<? extends Threadable>) messages);
420 }
421
422
423
424
425
426
427
428
429
430
431 @Deprecated
432 public Threadable thread(final Threadable[] messages) {
433 if (messages == null) {
434 return null;
435 }
436 return thread(Arrays.asList(messages));
437 }
438
439 }