View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.nntp;
19  
20  import java.io.PrintStream;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  
24  import org.apache.commons.net.util.NetConstants;
25  
26  /**
27   * This is a class that contains the basic state needed for message retrieval and threading. With thanks to Jamie Zawinski (jwz@jwz.org)
28   */
29  public class Article implements Threadable {
30      /**
31       * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
32       *
33       * @param article the root of the article 'tree'
34       * @since 3.4
35       */
36      public static void printThread(final Article article) {
37          printThread(article, 0, System.out);
38      }
39  
40      /**
41       * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
42       *
43       * @param article the root of the article 'tree'
44       * @param depth   the current tree depth
45       */
46      public static void printThread(final Article article, final int depth) {
47          printThread(article, depth, System.out);
48      }
49  
50      /**
51       * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
52       *
53       * @param article the root of the article 'tree'
54       * @param depth   the current tree depth
55       * @param ps      the PrintStream to use
56       * @since 3.4
57       */
58      public static void printThread(final Article article, final int depth, final PrintStream ps) {
59          for (int i = 0; i < depth; ++i) {
60              ps.print("==>");
61          }
62          ps.println(article.getSubject() + "\t" + article.getFrom() + "\t" + article.getArticleId());
63          if (article.kid != null) {
64              printThread(article.kid, depth + 1);
65          }
66          if (article.next != null) {
67              printThread(article.next, depth);
68          }
69      }
70  
71      /**
72       * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
73       *
74       * @param article the root of the article 'tree'
75       * @param ps      the PrintStream to use
76       * @since 3.4
77       */
78      public static void printThread(final Article article, final PrintStream ps) {
79          printThread(article, 0, ps);
80      }
81  
82      private long articleNumber;
83      private String subject;
84      private String date;
85      private String articleId;
86  
87      private String simplifiedSubject;
88  
89      private String from;
90      private ArrayList<String> references;
91  
92      private boolean isReply;
93  
94      public Article kid, next;
95  
96      public Article() {
97          articleNumber = -1; // isDummy
98      }
99  
100     @Deprecated
101 
102     public void addHeaderField(final String name, final String val) {
103     }
104 
105     /**
106      * Adds a message-id to the list of messages that this message references (i.e. replies to)
107      *
108      * @param msgId the message id to add
109      */
110     public void addReference(final String msgId) {
111         if (msgId == null || msgId.isEmpty()) {
112             return;
113         }
114         if (references == null) {
115             references = new ArrayList<>();
116         }
117         isReply = true;
118         Collections.addAll(references, msgId.split(" "));
119     }
120 
121     private void flushSubjectCache() {
122         simplifiedSubject = null;
123     }
124 
125     public String getArticleId() {
126         return articleId;
127     }
128 
129     @Deprecated
130     public int getArticleNumber() {
131         return (int) articleNumber;
132     }
133 
134     public long getArticleNumberLong() {
135         return articleNumber;
136     }
137 
138     public String getDate() {
139         return date;
140     }
141 
142     public String getFrom() {
143         return from;
144     }
145 
146     /**
147      * Returns the MessageId references as an array of Strings
148      *
149      * @return an array of message-ids
150      */
151     public String[] getReferences() {
152         if (references == null) {
153             return NetConstants.EMPTY_STRING_ARRAY;
154         }
155         return references.toArray(NetConstants.EMPTY_STRING_ARRAY);
156     }
157 
158     public String getSubject() {
159         return subject;
160     }
161 
162     @Override
163     public boolean isDummy() {
164         return articleNumber == -1;
165     }
166 
167     @Override
168     public Threadable makeDummy() {
169         return new Article();
170     }
171 
172     @Override
173     public String messageThreadId() {
174         return articleId;
175     }
176 
177     @Override
178     public String[] messageThreadReferences() {
179         return getReferences();
180     }
181 
182     public void setArticleId(final String string) {
183         articleId = string;
184     }
185 
186     @Deprecated
187     public void setArticleNumber(final int a) {
188         articleNumber = a;
189     }
190 
191     public void setArticleNumber(final long l) {
192         articleNumber = l;
193     }
194 
195     @Override
196     public void setChild(final Threadable child) {
197         this.kid = (Article) child;
198         flushSubjectCache();
199     }
200 
201     public void setDate(final String string) {
202         date = string;
203     }
204 
205     public void setFrom(final String string) {
206         from = string;
207     }
208 
209     @Override
210     public void setNext(final Threadable next) {
211         this.next = (Article) next;
212         flushSubjectCache();
213     }
214 
215     public void setSubject(final String string) {
216         subject = string;
217     }
218 
219     @Override
220     public String simplifiedSubject() {
221         if (simplifiedSubject == null) {
222             simplifySubject();
223         }
224         return simplifiedSubject;
225     }
226 
227     // DEPRECATED METHODS - for API compatibility only - DO NOT USE
228 
229     /**
230      * Attempts to parse the subject line for some typical reply signatures, and strip them out
231      *
232      */
233     private void simplifySubject() {
234         int start = 0;
235         final String subject = getSubject();
236         final int len = subject.length();
237 
238         boolean done = false;
239 
240         while (!done) {
241             done = true;
242 
243             // skip whitespace
244             // "Re: " breaks this
245             while (start < len && subject.charAt(start) == ' ') {
246                 start++;
247             }
248 
249             if (start < len - 2 && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
250                     && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {
251 
252                 if (subject.charAt(start + 2) == ':') {
253                     start += 3; // Skip "Re:"
254                     done = false;
255                 } else if (start < len - 2 && (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {
256 
257                     int i = start + 3;
258 
259                     while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') {
260                         i++;
261                     }
262 
263                     if (i < len - 1 && (subject.charAt(i) == ']' || subject.charAt(i) == ')') && subject.charAt(i + 1) == ':') {
264                         start = i + 2;
265                         done = false;
266                     }
267                 }
268             }
269 
270             if ("(no subject)".equals(simplifiedSubject)) {
271                 simplifiedSubject = "";
272             }
273 
274             int end = len;
275 
276             while (end > start && subject.charAt(end - 1) < ' ') {
277                 end--;
278             }
279 
280             if (start == 0 && end == len) {
281                 simplifiedSubject = subject;
282             } else {
283                 simplifiedSubject = subject.substring(start, end);
284             }
285         }
286     }
287 
288     @Override
289     public boolean subjectIsReply() {
290         return isReply;
291     }
292 
293     @Override
294     public String toString() { // Useful for Eclipse debugging
295         return articleNumber + " " + articleId + " " + subject;
296     }
297 
298 }