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    
022    /**
023     * This is a class that contains the basic state needed for message retrieval and threading.
024     * With thanks to Jamie  Zawinski <jwz@jwz.org>
025     * @author rwinston <rwinston@apache.org>
026     */
027    public class Article implements Threadable {
028        private long articleNumber;
029        private String subject;
030        private String date;
031        private String articleId;
032        private String simplifiedSubject;
033        private String from;
034        private ArrayList<String> references;
035        private boolean isReply = false;
036    
037        public Article kid, next;
038    
039        public Article() {
040            articleNumber = -1; // isDummy
041        }
042    
043        /**
044         * Adds a message-id to the list of messages that this message references (i.e. replies to)
045         * @param msgId
046         */
047        public void addReference(String msgId) {
048            if (msgId == null || msgId.length() == 0) {
049                return;
050            }
051            if (references == null) {
052                references = new ArrayList<String>();
053            }
054            isReply = true;
055            for(String s : msgId.split(" ")) {
056                references.add(s);
057            }
058        }
059    
060        /**
061         * Returns the MessageId references as an array of Strings
062         * @return an array of message-ids
063         */
064        public String[] getReferences() {
065            if (references == null) {
066                return new String[0];
067            }
068            return references.toArray(new String[references.size()]);
069        }
070    
071        /**
072         * Attempts to parse the subject line for some typical reply signatures, and strip them out
073         *
074         */
075        private void simplifySubject() {
076                int start = 0;
077                String subject = getSubject();
078                int len = subject.length();
079    
080                boolean done = false;
081    
082                while (!done) {
083                    done = true;
084    
085                    // skip whitespace
086                    // "Re: " breaks this
087                    while (start < len && subject.charAt(start) == ' ') {
088                        start++;
089                    }
090    
091                    if (start < (len - 2)
092                        && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
093                        && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {
094    
095                        if (subject.charAt(start + 2) == ':') {
096                            start += 3; // Skip "Re:"
097                            done = false;
098                        } else if (
099                            start < (len - 2)
100                            &&
101                            (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {
102    
103                            int i = start + 3;
104    
105                            while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') {
106                                i++;
107                            }
108    
109                            if (i < (len - 1)
110                                && (subject.charAt(i) == ']' || subject.charAt(i) == ')')
111                                && subject.charAt(i + 1) == ':') 
112                            {
113                                start = i + 2;
114                                done = false;
115                            }
116                        }
117                    }
118    
119                    if ("(no subject)".equals(simplifiedSubject)) {
120                        simplifiedSubject = "";
121                    }
122    
123                    int end = len;
124    
125                    while (end > start && subject.charAt(end - 1) < ' ') {
126                        end--;
127                    }
128    
129                    if (start == 0 && end == len) {
130                        simplifiedSubject = subject;
131                    } else {
132                        simplifiedSubject = subject.substring(start, end);
133                    }
134                }
135            }
136    
137        /**
138         * Recursive method that traverses a pre-threaded graph (or tree)
139         * of connected Article objects and prints them out.
140         * @param article the root of the article 'tree'
141         * @param depth the current tree depth
142         */
143        public static void printThread(Article article, int depth) {
144                for (int i = 0; i < depth; ++i) {
145                    System.out.print("==>");
146                }
147                System.out.println(article.getSubject() + "\t" + article.getFrom()+"\t"+article.getArticleId());            
148                if (article.kid != null) {
149                    printThread(article.kid, depth + 1);
150                }
151                if (article.next != null) {
152                    printThread(article.next, depth);
153                }
154        }
155    
156        public String getArticleId() {
157            return articleId;
158        }
159    
160        public long getArticleNumberLong() {
161            return articleNumber;
162        }
163    
164        public String getDate() {
165            return date;
166        }
167    
168        public String getFrom() {
169            return from;
170        }
171    
172        public String getSubject() {
173            return subject;
174        }
175    
176        public void setArticleId(String string) {
177            articleId = string;
178        }
179    
180        public void setArticleNumber(long l) {
181            articleNumber = l;
182        }
183    
184        public void setDate(String string) {
185            date = string;
186        }
187    
188        public void setFrom(String string) {
189            from = string;
190        }
191    
192        public void setSubject(String string) {
193            subject = string;
194        }
195    
196    
197        public boolean isDummy() {
198            return (articleNumber == -1);
199        }
200    
201        public String messageThreadId() {
202            return articleId;
203        }
204    
205        public String[] messageThreadReferences() {
206            return getReferences();
207        }
208    
209        public String simplifiedSubject() {
210            if(simplifiedSubject == null) {
211                simplifySubject();
212            }
213            return simplifiedSubject;
214        }
215    
216    
217        public boolean subjectIsReply() {
218            return isReply;
219        }
220    
221    
222        public void setChild(Threadable child) {
223            this.kid = (Article) child;
224            flushSubjectCache();
225        }
226    
227        private void flushSubjectCache() {
228            simplifiedSubject = null;
229        }
230    
231    
232        public void setNext(Threadable next) {
233            this.next = (Article)next;
234            flushSubjectCache();
235        }
236    
237    
238        public Threadable makeDummy() {
239            return new Article();
240        }
241    
242        @Override
243        public String toString(){ // Useful for Eclipse debugging
244            return articleNumber + " " +articleId + " " + subject;
245        }
246    
247        // DEPRECATED METHODS - for API compatibility only - DO NOT USE
248    
249        @Deprecated
250        public int getArticleNumber() {
251            return (int) articleNumber;
252        }
253    
254        @Deprecated
255        public void setArticleNumber(int a) {
256            articleNumber = a;
257        }
258        @Deprecated
259    
260        public void addHeaderField(String name, String val) {
261        }
262    
263    }