You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by rw...@apache.org on 2004/09/23 14:12:00 UTC

cvs commit: jakarta-commons/net/src/java/org/apache/commons/net/nntp Article.java Threadable.java Threader.java

rwinston    2004/09/23 05:12:00

  Added:       net/src/java/org/apache/commons/net/nntp Article.java
                        Threadable.java Threader.java
  Log:
  PR: 26282
  Added classes to facilitate message threading
  
  Revision  Changes    Path
  1.1                  jakarta-commons/net/src/java/org/apache/commons/net/nntp/Article.java
  
  Index: Article.java
  ===================================================================
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001-2004 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   *    "Apache Commons" must not be used to endorse or promote products
   *    derived from this software without prior written permission. For
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
   
  package org.apache.commons.net.nntp;
  
  import java.util.ArrayList;
  import java.util.StringTokenizer;
  
  /**
   * This is a class that contains the basic state needed for message retrieval and threading.
   * With thanks to Jamie  Zawinski <jw...@jwz.org>
   * @author rwinston <rw...@checkfree.com>
   *
   */
  public class Article implements Threadable {
  	private int articleNumber;
  	private String subject;
  	private String date;
  	private String articleId;
  	private String simplifiedSubject;
  	private String from;
  	private StringBuffer header;
  	private StringBuffer references;
  	private boolean isReply = false;
  	
  	public Article kid, next;
  
  	public Article() {
  		header = new StringBuffer();
  	}
  
  	/**
  	 * Adds an arbitrary header key and value to this message's header.
  	 * @param name the header name
  	 * @param val the header value
  	 */
  	public void addHeaderField(String name, String val) {
  		header.append(name);
  		header.append(": ");
  		header.append(val);
  		header.append('\n');
  	}
  	
  	/**
  	 * Adds a message-id to the list of messages that this message references (i.e. replies to)
  	 * @param msgId
  	 */
  	public void addReference(String msgId) {
  		if (references == null) {
  			references = new StringBuffer();
  			references.append("References: ");
  		}
  		references.append(msgId);
  		references.append("\t");
  	}
  
  	/**
  	 * Returns the MessageId references as an array of Strings
  	 * @return an array of message-ids
  	 */
  	public String[] getReferences() {
  		if (references == null)
  			return new String[0];
  		ArrayList list = new ArrayList();
  		int terminator = references.indexOf(":");
  		StringTokenizer st =
  			new StringTokenizer(references.substring(terminator), "\t");
  		while (st.hasMoreTokens()) {
  			list.add(st.nextToken());
  		}
  		return (String[]) list.toArray();
  	}
  	
  	/**
  	 * Attempts to parse the subject line for some typical reply signatures, and strip them out
  	 *
  	 */
  	private void simplifySubject() {
  			int start = 0;
  			String subject = getSubject();
  			int len = subject.length();
  
  			boolean done = false;
  
  			while (!done) {
  				done = true;
  
  				// skip whitespace
  				// "Re: " breaks this
  				while (start < len && subject.charAt(start) == ' ') {
  					start++;
  				}
  
  				if (start < (len - 2)
  					&& (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
  					&& (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {
  
  					if (subject.charAt(start + 2) == ':') {
  						start += 3; // Skip "Re:"
  						isReply = true;
  						done = false;
  					} else if (
  						start < (len - 2) 
  						&& 
  						(subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {
                      	
  						int i = start + 3;
  
  						while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9')
  							i++;
  
  						if (i < (len - 1)
  							&& (subject.charAt(i) == ']' || subject.charAt(i) == ')')
  							&& subject.charAt(i + 1) == ':') {
  							start = i + 2;
  							isReply = true;
  							done = false;
  						}
  					}
  				}
  
  				if (simplifiedSubject == "(no subject)")
  					simplifiedSubject = "";
  
  				int end = len;
  
  				while (end > start && subject.charAt(end - 1) < ' ')
  					end--;
  
  				if (start == 0 && end == len)
  					simplifiedSubject = subject;
  				else
  					simplifiedSubject = subject.substring(start, end);
  			}
  		}
  		
  	/**
  	 * Recursive method that traverses a pre-threaded graph (or tree) 
  	 * of connected Article objects and prints them out.  
  	 * @param article the root of the article 'tree'
  	 * @param depth the current tree depth
  	 */
  	public static void printThread(Article article, int depth) {
  			for (int i = 0; i < depth; ++i)
  				System.out.print("==>");
  			System.out.println(article.getSubject() + "\t" + article.getFrom());
  			if (article.kid != null)
  				printThread(article.kid, depth + 1);
  			if (article.next != null)
  				printThread(article.next, depth);
  	}
  
  	public String getArticleId() {
  		return articleId;
  	}
  
  	public int getArticleNumber() {
  		return articleNumber;
  	}
  
  	public String getDate() {
  		return date;
  	}
  
  	public String getFrom() {
  		return from;
  	}
  
  	public String getSubject() {
  		return subject;
  	}
  
  	public void setArticleId(String string) {
  		articleId = string;
  	}
  
  	public void setArticleNumber(int i) {
  		articleNumber = i;
  	}
  
  	public void setDate(String string) {
  		date = string;
  	}
  
  	public void setFrom(String string) {
  		from = string;
  	}
  
  	public void setSubject(String string) {
  		subject = string;
  	}
  
  	
  	public boolean isDummy() {
  		return (getSubject() == null);
  	}
  
  	public String messageThreadId() {
  		return articleId;
  	}
  	
  	public String[] messageThreadReferences() {
  		return getReferences();
  	}
  	
  	public String simplifiedSubject() {
  		if(simplifiedSubject == null)
  			simplifySubject();
  		return simplifiedSubject;
  	}
  
  	
  	public boolean subjectIsReply() {
  		if(simplifiedSubject == null)
  			simplifySubject();
  		return isReply;
  	}
  
  	
  	public void setChild(Threadable child) {
  		this.kid = (Article) child;
  		flushSubjectCache();
  	}
  
  	private void flushSubjectCache() {
  		simplifiedSubject = null;
  	}
  
  	
  	public void setNext(Threadable next) {
  		this.next = (Article)next;
  		flushSubjectCache();
  	}
  
  	
  	public Threadable makeDummy() {
  		return (Threadable)new Article();
  	}
  }
  
  
  
  1.1                  jakarta-commons/net/src/java/org/apache/commons/net/nntp/Threadable.java
  
  Index: Threadable.java
  ===================================================================
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001-2004 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   *    "Apache Commons" must not be used to endorse or promote products
   *    derived from this software without prior written permission. For
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
   
  package org.apache.commons.net.nntp; 
  
  /**
   * A placeholder interface for threadable message objects
   * Author: Rory Winston <rw...@checkfree.com>
   *
   */
  public interface Threadable {
  	public boolean isDummy();
  	public String messageThreadId();
  	public String[] messageThreadReferences();
  	public String simplifiedSubject();
  	public boolean subjectIsReply();
  	public void setChild(Threadable child); 
  	public void setNext(Threadable next);
  	public Threadable makeDummy();
  }
  
  
  1.1                  jakarta-commons/net/src/java/org/apache/commons/net/nntp/Threader.java
  
  Index: Threader.java
  ===================================================================
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001-2004 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   *    "Apache Commons" must not be used to endorse or promote products
   *    derived from this software without prior written permission. For
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  package org.apache.commons.net.nntp;
  
  /**
   * This is an implementation of a message threading algorithm, as originally devised by Zamie Zawinski.
   * See <a href="http://www.jwz.org/doc/threading.html">http://www.jwz.org/doc/threading.html</a> for details.
   * For his Java implementation, see <a href="http://lxr.mozilla.org/mozilla/source/grendel/sources/grendel/view/Threader.java">http://lxr.mozilla.org/mozilla/source/grendel/sources/grendel/view/Threader.java</a>
   *  
   * @author rwinston <rw...@checkfree.com>
   *
   */
  
  import java.util.HashMap;
  import java.util.Iterator;
  
  public class Threader {
  	private ThreadContainer root;
  	private HashMap idTable;
  	private int bogusIdCount = 0;
  
  	/**
  	 * The main threader entry point - The client passes in an array of Threadable objects, and 
  	 * the Threader constructs a connected 'graph' of messages
  	 * @param messages
  	 * @return
  	 */
  	public Threadable thread(Threadable[] messages) {
  		if (messages == null)
  			return null;
  
  		idTable = new HashMap();
  
  		// walk through each Threadable element
  		for (int i = 0; i < messages.length; ++i) {
  			if (!messages[i].isDummy())
  				buildContainer(messages[i]);
  		}
  
  		root = findRootSet();
  		idTable.clear();
  		idTable = null;
  
  		pruneEmptyContainers(root);
  
  		root.reverseChildren();
  		gatherSubjects();
  
  		if (root.next != null)
  			throw new RuntimeException("root node has a next:" + root);
  
  		for (ThreadContainer r = root.child; r != null; r = r.next) {
  			if (r.threadable == null)
  				r.threadable = r.child.threadable.makeDummy();
  		}
  
  		Threadable result = (root.child == null ? null : root.child.threadable);
  		root.flush();
  		root = null;
  
  		return result;
  	}
  
  	/**
  	 * 
  	 * @param threadable
  	 */
  	private void buildContainer(Threadable threadable) {
  		String id = threadable.messageThreadId();
  		ThreadContainer container = (ThreadContainer) idTable.get(id);
  
  		// A ThreadContainer exists for this id already. This should be a forward reference, but may 
  		// be a duplicate id, in which case we will need to generate a bogus placeholder id
  		if (container != null) {
  			if (container.threadable != null) { // oops! duplicate ids...
  				id = "<Bogus-id:" + (bogusIdCount++) + ">";
  				container = null;
  			} else {
  				// The container just contained a forward reference to this message, so let's
  				// fill in the threadable field of the container with this message
  				container.threadable = threadable;
  			}
  		}
  
  		// No container exists for that message Id. Create one and insert it into the hash table.
  		if (container == null) {
  			container = new ThreadContainer();
  			container.threadable = threadable;
  			idTable.put(id, container);
  		}
  
  		// Iterate through all of the references and create ThreadContainers for any references that
  		// don't have them.
  		ThreadContainer parentRef = null;
  		{
  			String[] references = threadable.messageThreadReferences();
  			for (int i = 0; i < references.length; ++i) {
  				String refString = references[i];
  				ThreadContainer ref = (ThreadContainer) idTable.get(refString);
  
  				// if this id doesnt have a container, create one
  				if (ref == null) {
  					ref = new ThreadContainer();
  					idTable.put(refString, ref);
  				}
  
  				// Link references together in the order they appear in the References: header,
  				// IF they dont have a have a parent already &&
  				// IF it will not cause a circular reference
  				if ((parentRef != null)
  					&& (ref.parent == null)
  					&& (parentRef != ref)
  					&& !(parentRef.findChild(ref))) {
  					// Link ref into the parent's child list
  					ref.parent = parentRef;
  					ref.next = parentRef.child;
  					parentRef.child = ref;
  				}
  				parentRef = ref;
  			}
  		}
  
  		// parentRef is now set to the container of the last element in the references field. make that 
  		// be the parent of this container, unless doing so causes a circular reference
  		if (parentRef != null
  			&& (parentRef == container || container.findChild(parentRef)))
  			parentRef = null;
  
  		// if it has a parent already, its because we saw this message in a References: field, and presumed
  		// a parent based on the other entries in that field. Now that we have the actual message, we can
  		// throw away the old parent and use this new one
  		if (container.parent != null) {
  			ThreadContainer rest, prev;
  
  			for (prev = null, rest = container.parent.child;
  				rest != null;
  				prev = rest, rest = rest.next) {
  				if (rest == container)
  					break;
  			}
  
  			if (rest == null) {
  				throw new RuntimeException(
  					"Didnt find "
  						+ container
  						+ " in parent"
  						+ container.parent);
  			}
  
  			// Unlink this container from the parent's child list
  			if (prev == null)
  				container.parent.child = container.next;
  			else
  				prev.next = container.next;
  
  			container.next = null;
  			container.parent = null;
  		}
  
  		// If we have a parent, link container into the parents child list
  		if (parentRef != null) {
  			container.parent = parentRef;
  			container.next = parentRef.child;
  			parentRef.child = container;
  		}
  	}
  
  	/**
  	 * Find the root set of all existing ThreadContainers
  	 * @return root the ThreadContainer representing the root node
  	 */
  	private ThreadContainer findRootSet() {
  		ThreadContainer root = new ThreadContainer();
  		Iterator iter = idTable.keySet().iterator();
  
  		while (iter.hasNext()) {
  			Object key = iter.next();
  			ThreadContainer c = (ThreadContainer) idTable.get(key);
  			if (c.parent == null) {
  				if (c.next != null)
  					throw new RuntimeException(
  						"c.next is " + c.next.toString());
  				c.next = root.child;
  				root.child = c;
  			}
  		}
  		return root;
  	}
  
  	/**
  	 * Delete any empty or dummy ThreadContainers
  	 * @param parent
  	 */
  	private void pruneEmptyContainers(ThreadContainer parent) {
  		ThreadContainer container, prev, next;
  		for (prev = null, container = parent.child, next = container.next;
  			container != null;
  			prev = container,
  				container = next,
  				next = (container == null ? null : container.next)) {
  
  			// Is it empty and without any children? If so,delete it 
  			if (container.threadable == null && container.child == null) {
  				if (prev == null)
  					parent.child = container.next;
  				else
  					prev.next = container.next;
  
  				// Set container to prev so that prev keeps its same value the next time through the loop	
  				container = prev;
  			}
  
  			// Else if empty, with kids, and (not at root or only one kid)
  			else if (
  				container.threadable == null
  					&& container.child != null
  					&& (container.parent != null
  						|| container.child.next == null)) {
  				// We have an invalid/expired message with kids. Promote the kids to this level. 
  				ThreadContainer tail;
  				ThreadContainer kids = container.child;
  
  				// Remove this container and replace with 'kids'.
  				if (prev == null)
  					parent.child = kids;
  				else
  					prev.next = kids;
  
  				// Make each child's parent be this level's parent -> i.e. promote the children. Make the last child's next point to this container's next
  				// i.e. splice kids into the list in place of container
  				for (tail = kids; tail.next != null; tail = tail.next)
  					tail.parent = container.parent;
  
  				tail.parent = container.parent;
  				tail.next = container.next;
  
  				// next currently points to the item after the inserted items in the chain - reset that so we process the newly
  				// promoted items next time round
  				next = kids;
  
  				// Set container to prev so that prev keeps its same value the next time through the loop
  				container = prev;
  			} else if (container.child != null) {
  				// A real message , with kids
  				// Iterate over the children
  				pruneEmptyContainers(container);
  			}
  		}
  	}
  
  	/**
  	 *  If any two members of the root set have the same subject, merge them. This is to attempt to accomodate messages without References: headers. 
  	 */
  	private void gatherSubjects() {
  
  		int count = 0;
  
  		for (ThreadContainer c = root.child; c != null; c = c.next)
  			count++;
  
  		// TODO verify this will avoid rehashing
  		HashMap subjectTable = new HashMap((int) (count * 1.2), (float) 0.9);
  		count = 0;
  
  		for (ThreadContainer c = root.child; c != null; c = c.next) {
  			Threadable threadable = c.threadable;
  
  			// No threadable? If so, it is a dummy node in the root set.
  			// Only root set members may be dummies, and they alway have at least 2 kids
  			// Take the first kid as representative of the subject
  			if (threadable == null)
  				threadable = c.child.threadable;
  
  			String subj = threadable.simplifiedSubject();
  
  			if (subj == null || subj == "")
  				continue;
  
  			ThreadContainer old = (ThreadContainer) subjectTable.get(subj);
  
  			// Add this container to the table iff:
  			// - There exists no container with this subject
  			// - or this is a dummy container and the old one is not - the dummy one is
  			// more interesting as a root, so put it in the table instead
  			// - The container in the table has a "Re:" version of this subject, and 
  			// this container has a non-"Re:" version of this subject. The non-"Re:" version
  			// is the more interesting of the two.
  			if (old == null
  				|| (c.threadable == null && old.threadable != null)
  				|| (old.threadable != null
  					&& old.threadable.subjectIsReply()
  					&& c.threadable != null
  					&& !c.threadable.subjectIsReply())) {
  				subjectTable.put(subj, c);
  				count++;
  			}
  		}
  
  		// If the table is empty, we're done
  		if (count == 0)
  			return;
  
  		// subjectTable is now populated with one entry for each subject which occurs in the 
  		// root set. Iterate over the root set, and gather together the difference.
  		ThreadContainer prev, c, rest;
  		for (prev = null, c = root.child, rest = c.next;
  			c != null;
  			prev = c, c = rest, rest = (rest == null ? null : rest.next)) {
  			Threadable threadable = c.threadable;
  
  			// is it a dummy node?
  			if (threadable == null)
  				threadable = c.child.threadable;
  
  			String subj = threadable.simplifiedSubject();
  
  			// Dont thread together all subjectless messages
  			if (subj == null || subj == "")
  				continue;
  
  			ThreadContainer old = (ThreadContainer) subjectTable.get(subj);
  
  			if (old == c) // That's us
  				continue;
  
  			// We have now found another container in the root set with the same subject
  			// Remove the "second" message from the root set
  			if (prev == null)
  				root.child = c.next;
  			else
  				prev.next = c.next;
  			c.next = null;
  
  			if (old.threadable == null && c.threadable == null) {
  				// both dummies - merge them
  				ThreadContainer tail;
  				for (tail = old.child;
  					tail != null && tail.next != null;
  					tail = tail.next);
  
  				tail.next = c.child;
  
  				for (tail = c.child; tail != null; tail = tail.next)
  					tail.parent = old;
  
  				c.child = null;
  			} else if (
  				old.threadable == null
  					|| (c.threadable != null
  						&& c.threadable.subjectIsReply()
  						&& !old.threadable.subjectIsReply())) {
  				// Else if old is empty, or c has "Re:" and old does not  ==> make this message a child of old
  				c.parent = old;
  				c.next = old.child;
  				old.child = c;
  			} else {
  				//	else make the old and new messages be children of a new dummy container.
  				// We create a new container object for old.msg and empty the old container
  				ThreadContainer newc = new ThreadContainer();
  				newc.threadable = old.threadable;
  				newc.child = old.child;
  
  				for (ThreadContainer tail = newc.child;
  					tail != null;
  					tail = tail.next)
  					tail.parent = newc;
  
  				old.threadable = null;
  				old.child = null;
  
  				c.parent = old;
  				newc.parent = old;
  
  				// Old is now a dummy- give it 2 kids , c and newc
  				old.child = c;
  				c.next = newc;
  			}
  			// We've done a merge, so keep the same prev
  			c = prev;
  		}
  
  		subjectTable.clear();
  		subjectTable = null;
  
  	}
  }
  
  /**
   * A placeholder utility class, used for constructing a tree of Threadables
   * Originall implementation by Jamie Zawinski. 
   * See the Grendel source for more details <a href="http://lxr.mozilla.org/mozilla/source/grendel/sources/grendel/view/Threader.java#511">here</a>
   * Threadable objects
   * @author Rory Winston <rw...@checkfree.com>
   */
  class ThreadContainer {
  	Threadable threadable;
  	ThreadContainer parent;
  	ThreadContainer prev;
  	ThreadContainer next;
  	ThreadContainer child;
  
  	/**
  	 * 
  	 * @param container
  	 * @return true if child is under self's tree. Detects circular references
  	 */
  	boolean findChild(ThreadContainer target) {
  		if (child == null)
  			return false;
  
  		else if (child == target)
  			return true;
  		else
  			return child.findChild(target);
  	}
  
  	// Copy the ThreadContainer tree structure down into the underlying Threadable objects
  	// (Make the Threadable tree look like the ThreadContainer tree)
  	void flush() {
  		if (parent != null && threadable == null)
  			throw new RuntimeException("no threadable in " + this.toString());
  
  		parent = null;
  
  		if (threadable != null)
  			threadable.setChild(child == null ? null : child.threadable);
  
  		if (child != null) {
  			child.flush();
  			child = null;
  		}
  
  		if (threadable != null)
  			threadable.setNext(next == null ? null : next.threadable);
  
  		if (next != null) {
  			next.flush();
  			next = null;
  		}
  
  		threadable = null;
  	}
  
  	/**
  	 * Reverse the entire set of children
  	 *
  	 */
  	void reverseChildren() {
  		if (child != null) {
  			ThreadContainer kid, prev, rest;
  			for (prev = null, kid = child, rest = kid.next;
  				kid != null;
  				prev = kid,
  					kid = rest,
  					rest = (rest == null ? null : rest.next))
  				kid.next = prev;
  
  			child = prev;
  
  			// Do it for the kids 
  			for (kid = child; kid != null; kid = kid.next)
  				kid.reverseChildren();
  		}
  	}
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org