001    /* ====================================================================
002     * The Apache Software License, Version 1.1
003     *
004     * Copyright (c) 2001-2005 The Apache Software Foundation.  All rights
005     * reserved.
006     *
007     * Redistribution and use in source and binary forms, with or without
008     * modification, are permitted provided that the following conditions
009     * are met:
010     *
011     * 1. Redistributions of source code must retain the above copyright
012     *    notice, this list of conditions and the following disclaimer.
013     *
014     * 2. Redistributions in binary form must reproduce the above copyright
015     *    notice, this list of conditions and the following disclaimer in
016     *    the documentation and/or other materials provided with the
017     *    distribution.
018     *
019     * 3. The end-user documentation included with the redistribution,
020     *    if any, must include the following acknowledgment:
021     *       "This product includes software developed by the
022     *        Apache Software Foundation (http://www.apache.org/)."
023     *    Alternately, this acknowledgment may appear in the software itself,
024     *    if and wherever such third-party acknowledgments normally appear.
025     *
026     * 4. The names "Apache" and "Apache Software Foundation" and
027     *    "Apache Commons" must not be used to endorse or promote products
028     *    derived from this software without prior written permission. For
029     *    written permission, please contact apache@apache.org.
030     *
031     * 5. Products derived from this software may not be called "Apache",
032     *    nor may "Apache" appear in their name, without
033     *    prior written permission of the Apache Software Foundation.
034     *
035     * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036     * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038     * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039     * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041     * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042     * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044     * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046     * SUCH DAMAGE.
047     * ====================================================================
048     *
049     * This software consists of voluntary contributions made by many
050     * individuals on behalf of the Apache Software Foundation.  For more
051     * information on the Apache Software Foundation, please see
052     * <http://www.apache.org/>.
053     */
054     
055    package org.apache.commons.net.nntp;
056    
057    import java.util.ArrayList;
058    import java.util.StringTokenizer;
059    
060    /**
061     * This is a class that contains the basic state needed for message retrieval and threading.
062     * With thanks to Jamie  Zawinski <jwz@jwz.org>
063     * @author rwinston <rwinston@checkfree.com>
064     *
065     */
066    public class Article implements Threadable {
067            private int articleNumber;
068            private String subject;
069            private String date;
070            private String articleId;
071            private String simplifiedSubject;
072            private String from;
073            private StringBuffer header;
074            private StringBuffer references;
075            private boolean isReply = false;
076            
077            public Article kid, next;
078    
079            public Article() {
080                    header = new StringBuffer();
081            }
082    
083            /**
084             * Adds an arbitrary header key and value to this message's header.
085             * @param name the header name
086             * @param val the header value
087             */
088            public void addHeaderField(String name, String val) {
089                    header.append(name);
090                    header.append(": ");
091                    header.append(val);
092                    header.append('\n');
093            }
094            
095            /**
096             * Adds a message-id to the list of messages that this message references (i.e. replies to)
097             * @param msgId
098             */
099            public void addReference(String msgId) {
100                    if (references == null) {
101                            references = new StringBuffer();
102                            references.append("References: ");
103                    }
104                    references.append(msgId);
105                    references.append("\t");
106            }
107    
108            /**
109             * Returns the MessageId references as an array of Strings
110             * @return an array of message-ids
111             */
112            public String[] getReferences() {
113                    if (references == null)
114                            return new String[0];
115                    ArrayList list = new ArrayList();
116                    int terminator = references.toString().indexOf(':');
117                    StringTokenizer st =
118                            new StringTokenizer(references.substring(terminator), "\t");
119                    while (st.hasMoreTokens()) {
120                            list.add(st.nextToken());
121                    }
122                    return (String[]) list.toArray();
123            }
124            
125            /**
126             * Attempts to parse the subject line for some typical reply signatures, and strip them out
127             *
128             */
129            private void simplifySubject() {
130                            int start = 0;
131                            String subject = getSubject();
132                            int len = subject.length();
133    
134                            boolean done = false;
135    
136                            while (!done) {
137                                    done = true;
138    
139                                    // skip whitespace
140                                    // "Re: " breaks this
141                                    while (start < len && subject.charAt(start) == ' ') {
142                                            start++;
143                                    }
144    
145                                    if (start < (len - 2)
146                                            && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
147                                            && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {
148    
149                                            if (subject.charAt(start + 2) == ':') {
150                                                    start += 3; // Skip "Re:"
151                                                    isReply = true;
152                                                    done = false;
153                                            } else if (
154                                                    start < (len - 2) 
155                                                    && 
156                                                    (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {
157                            
158                                                    int i = start + 3;
159    
160                                                    while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9')
161                                                            i++;
162    
163                                                    if (i < (len - 1)
164                                                            && (subject.charAt(i) == ']' || subject.charAt(i) == ')')
165                                                            && subject.charAt(i + 1) == ':') {
166                                                            start = i + 2;
167                                                            isReply = true;
168                                                            done = false;
169                                                    }
170                                            }
171                                    }
172    
173                                    if (simplifiedSubject == "(no subject)")
174                                            simplifiedSubject = "";
175    
176                                    int end = len;
177    
178                                    while (end > start && subject.charAt(end - 1) < ' ')
179                                            end--;
180    
181                                    if (start == 0 && end == len)
182                                            simplifiedSubject = subject;
183                                    else
184                                            simplifiedSubject = subject.substring(start, end);
185                            }
186                    }
187                    
188            /**
189             * Recursive method that traverses a pre-threaded graph (or tree) 
190             * of connected Article objects and prints them out.  
191             * @param article the root of the article 'tree'
192             * @param depth the current tree depth
193             */
194            public static void printThread(Article article, int depth) {
195                            for (int i = 0; i < depth; ++i)
196                                    System.out.print("==>");
197                            System.out.println(article.getSubject() + "\t" + article.getFrom());
198                            if (article.kid != null)
199                                    printThread(article.kid, depth + 1);
200                            if (article.next != null)
201                                    printThread(article.next, depth);
202            }
203    
204            public String getArticleId() {
205                    return articleId;
206            }
207    
208            public int getArticleNumber() {
209                    return articleNumber;
210            }
211    
212            public String getDate() {
213                    return date;
214            }
215    
216            public String getFrom() {
217                    return from;
218            }
219    
220            public String getSubject() {
221                    return subject;
222            }
223    
224            public void setArticleId(String string) {
225                    articleId = string;
226            }
227    
228            public void setArticleNumber(int i) {
229                    articleNumber = i;
230            }
231    
232            public void setDate(String string) {
233                    date = string;
234            }
235    
236            public void setFrom(String string) {
237                    from = string;
238            }
239    
240            public void setSubject(String string) {
241                    subject = string;
242            }
243    
244            
245            public boolean isDummy() {
246                    return (getSubject() == null);
247            }
248    
249            public String messageThreadId() {
250                    return articleId;
251            }
252            
253            public String[] messageThreadReferences() {
254                    return getReferences();
255            }
256            
257            public String simplifiedSubject() {
258                    if(simplifiedSubject == null)
259                            simplifySubject();
260                    return simplifiedSubject;
261            }
262    
263            
264            public boolean subjectIsReply() {
265                    if(simplifiedSubject == null)
266                            simplifySubject();
267                    return isReply;
268            }
269    
270            
271            public void setChild(Threadable child) {
272                    this.kid = (Article) child;
273                    flushSubjectCache();
274            }
275    
276            private void flushSubjectCache() {
277                    simplifiedSubject = null;
278            }
279    
280            
281            public void setNext(Threadable next) {
282                    this.next = (Article)next;
283                    flushSubjectCache();
284            }
285    
286            
287            public Threadable makeDummy() {
288                    return (Threadable)new Article();
289            }
290    }