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    
018    package org.apache.commons.net.nntp;
019    
020    import java.util.ArrayList;
021    import 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     */
029    public 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 ("(no subject)".equals(simplifiedSubject))
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    }