001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.net.nntp;
019
020import java.util.ArrayList;
021import java.util.StringTokenizer;
022
023/**
024 * This is a class that contains the basic state needed for message retrieval and threading.
025 * With thanks to Jamie  Zawinski <jwz@jwz.org>
026 * @author rwinston <rwinston@apache.org>
027 *
028 */
029public class Article implements Threadable {
030    private int articleNumber;
031    private String subject;
032    private String date;
033    private String articleId;
034    private String simplifiedSubject;
035    private String from;
036    private StringBuffer header;
037    private StringBuffer references;
038    private boolean isReply = false;
039    
040    public Article kid, next;
041
042    public Article() {
043        header = new StringBuffer();
044    }
045
046    /**
047     * Adds an arbitrary header key and value to this message's header.
048     * @param name the header name
049     * @param val the header value
050     */
051    public void addHeaderField(String name, String val) {
052        header.append(name);
053        header.append(": ");
054        header.append(val);
055        header.append('\n');
056    }
057    
058    /**
059     * Adds a message-id to the list of messages that this message references (i.e. replies to)
060     * @param msgId
061     */
062    public void addReference(String msgId) {
063        if (references == null) {
064            references = new StringBuffer();
065            references.append("References: ");
066        }
067        references.append(msgId);
068        references.append("\t");
069    }
070
071    /**
072     * Returns the MessageId references as an array of Strings
073     * @return an array of message-ids
074     */
075    public String[] getReferences() {
076        if (references == null)
077            return new String[0];
078        ArrayList<String> list = new ArrayList<String>();
079        int terminator = references.toString().indexOf(':');
080        StringTokenizer st =
081            new StringTokenizer(references.substring(terminator), "\t");
082        while (st.hasMoreTokens()) {
083            list.add(st.nextToken());
084        }
085        return list.toArray(new String[list.size()]);
086    }
087    
088    /**
089     * Attempts to parse the subject line for some typical reply signatures, and strip them out
090     *
091     */
092    private void simplifySubject() {
093            int start = 0;
094            String subject = getSubject();
095            int len = subject.length();
096
097            boolean done = false;
098
099            while (!done) {
100                done = true;
101
102                // skip whitespace
103                // "Re: " breaks this
104                while (start < len && subject.charAt(start) == ' ') {
105                    start++;
106                }
107
108                if (start < (len - 2)
109                    && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
110                    && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {
111
112                    if (subject.charAt(start + 2) == ':') {
113                        start += 3; // Skip "Re:"
114                        isReply = true;
115                        done = false;
116                    } else if (
117                        start < (len - 2) 
118                        && 
119                        (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {
120                        
121                        int i = start + 3;
122
123                        while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9')
124                            i++;
125
126                        if (i < (len - 1)
127                            && (subject.charAt(i) == ']' || subject.charAt(i) == ')')
128                            && subject.charAt(i + 1) == ':') {
129                            start = i + 2;
130                            isReply = true;
131                            done = false;
132                        }
133                    }
134                }
135
136                if (simplifiedSubject == "(no subject)")
137                    simplifiedSubject = "";
138
139                int end = len;
140
141                while (end > start && subject.charAt(end - 1) < ' ')
142                    end--;
143
144                if (start == 0 && end == len)
145                    simplifiedSubject = subject;
146                else
147                    simplifiedSubject = subject.substring(start, end);
148            }
149        }
150        
151    /**
152     * Recursive method that traverses a pre-threaded graph (or tree) 
153     * of connected Article objects and prints them out.  
154     * @param article the root of the article 'tree'
155     * @param depth the current tree depth
156     */
157    public static void printThread(Article article, int depth) {
158            for (int i = 0; i < depth; ++i)
159                System.out.print("==>");
160            System.out.println(article.getSubject() + "\t" + article.getFrom());
161            if (article.kid != null)
162                printThread(article.kid, depth + 1);
163            if (article.next != null)
164                printThread(article.next, depth);
165    }
166
167    public String getArticleId() {
168        return articleId;
169    }
170
171    public int getArticleNumber() {
172        return articleNumber;
173    }
174
175    public String getDate() {
176        return date;
177    }
178
179    public String getFrom() {
180        return from;
181    }
182
183    public String getSubject() {
184        return subject;
185    }
186
187    public void setArticleId(String string) {
188        articleId = string;
189    }
190
191    public void setArticleNumber(int i) {
192        articleNumber = i;
193    }
194
195    public void setDate(String string) {
196        date = string;
197    }
198
199    public void setFrom(String string) {
200        from = string;
201    }
202
203    public void setSubject(String string) {
204        subject = string;
205    }
206
207    
208    public boolean isDummy() {
209        return (getSubject() == null);
210    }
211
212    public String messageThreadId() {
213        return articleId;
214    }
215    
216    public String[] messageThreadReferences() {
217        return getReferences();
218    }
219    
220    public String simplifiedSubject() {
221        if(simplifiedSubject == null)
222            simplifySubject();
223        return simplifiedSubject;
224    }
225
226    
227    public boolean subjectIsReply() {
228        if(simplifiedSubject == null)
229            simplifySubject();
230        return isReply;
231    }
232
233    
234    public void setChild(Threadable child) {
235        this.kid = (Article) child;
236        flushSubjectCache();
237    }
238
239    private void flushSubjectCache() {
240        simplifiedSubject = null;
241    }
242
243    
244    public void setNext(Threadable next) {
245        this.next = (Article)next;
246        flushSubjectCache();
247    }
248
249    
250    public Threadable makeDummy() {
251        return new Article();
252    }
253}