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.io.PrintStream;
021import java.util.ArrayList;
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 */
027public 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 the message id to add
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     * @since 3.4
142     */
143    public static void printThread(Article article) {
144        printThread(article, 0, System.out);
145    }
146
147    /**
148     * Recursive method that traverses a pre-threaded graph (or tree)
149     * of connected Article objects and prints them out.
150     * @param article the root of the article 'tree'
151     * @param ps the PrintStream to use
152     * @since 3.4
153     */
154    public static void printThread(Article article, PrintStream ps) {
155        printThread(article, 0, ps);
156    }
157
158    /**
159     * Recursive method that traverses a pre-threaded graph (or tree)
160     * of connected Article objects and prints them out.
161     * @param article the root of the article 'tree'
162     * @param depth the current tree depth
163     */
164    public static void printThread(Article article, int depth) {
165        printThread(article, depth, System.out);
166    }
167
168    /**
169     * Recursive method that traverses a pre-threaded graph (or tree)
170     * of connected Article objects and prints them out.
171     * @param article the root of the article 'tree'
172     * @param depth the current tree depth
173     * @param ps the PrintStream to use
174     * @since 3.4
175     */
176    public static void printThread(Article article, int depth, PrintStream ps) {
177            for (int i = 0; i < depth; ++i) {
178                ps.print("==>");
179            }
180            ps.println(article.getSubject() + "\t" + article.getFrom()+"\t"+article.getArticleId());
181            if (article.kid != null) {
182                printThread(article.kid, depth + 1);
183            }
184            if (article.next != null) {
185                printThread(article.next, depth);
186            }
187    }
188
189    public String getArticleId() {
190        return articleId;
191    }
192
193    public long getArticleNumberLong() {
194        return articleNumber;
195    }
196
197    public String getDate() {
198        return date;
199    }
200
201    public String getFrom() {
202        return from;
203    }
204
205    public String getSubject() {
206        return subject;
207    }
208
209    public void setArticleId(String string) {
210        articleId = string;
211    }
212
213    public void setArticleNumber(long l) {
214        articleNumber = l;
215    }
216
217    public void setDate(String string) {
218        date = string;
219    }
220
221    public void setFrom(String string) {
222        from = string;
223    }
224
225    public void setSubject(String string) {
226        subject = string;
227    }
228
229
230    @Override
231    public boolean isDummy() {
232        return (articleNumber == -1);
233    }
234
235    @Override
236    public String messageThreadId() {
237        return articleId;
238    }
239
240    @Override
241    public String[] messageThreadReferences() {
242        return getReferences();
243    }
244
245    @Override
246    public String simplifiedSubject() {
247        if(simplifiedSubject == null) {
248            simplifySubject();
249        }
250        return simplifiedSubject;
251    }
252
253
254    @Override
255    public boolean subjectIsReply() {
256        return isReply;
257    }
258
259
260    @Override
261    public void setChild(Threadable child) {
262        this.kid = (Article) child;
263        flushSubjectCache();
264    }
265
266    private void flushSubjectCache() {
267        simplifiedSubject = null;
268    }
269
270
271    @Override
272    public void setNext(Threadable next) {
273        this.next = (Article)next;
274        flushSubjectCache();
275    }
276
277
278    @Override
279    public Threadable makeDummy() {
280        return new Article();
281    }
282
283    @Override
284    public String toString(){ // Useful for Eclipse debugging
285        return articleNumber + " " +articleId + " " + subject;
286    }
287
288    // DEPRECATED METHODS - for API compatibility only - DO NOT USE
289
290    @Deprecated
291    public int getArticleNumber() {
292        return (int) articleNumber;
293    }
294
295    @Deprecated
296    public void setArticleNumber(int a) {
297        articleNumber = a;
298    }
299    @Deprecated
300
301    public void addHeaderField(String name, String val) {
302    }
303
304}