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.util.ArrayList;
21  import java.util.StringTokenizer;
22  
23  /**
24   * This is a class that contains the basic state needed for message retrieval and threading.
25   * With thanks to Jamie  Zawinski <jwz@jwz.org>
26   * @author rwinston <rwinston@apache.org>
27   *
28   */
29  public class Article implements Threadable {
30      private int articleNumber;
31      private String subject;
32      private String date;
33      private String articleId;
34      private String simplifiedSubject;
35      private String from;
36      private StringBuffer header;
37      private StringBuffer references;
38      private boolean isReply = false;
39      
40      public Article kid, next;
41  
42      public Article() {
43          header = new StringBuffer();
44      }
45  
46      /**
47       * Adds an arbitrary header key and value to this message's header.
48       * @param name the header name
49       * @param val the header value
50       */
51      public void addHeaderField(String name, String val) {
52          header.append(name);
53          header.append(": ");
54          header.append(val);
55          header.append('\n');
56      }
57      
58      /**
59       * Adds a message-id to the list of messages that this message references (i.e. replies to)
60       * @param msgId
61       */
62      public void addReference(String msgId) {
63          if (references == null) {
64              references = new StringBuffer();
65              references.append("References: ");
66          }
67          references.append(msgId);
68          references.append("\t");
69      }
70  
71      /**
72       * Returns the MessageId references as an array of Strings
73       * @return an array of message-ids
74       */
75      public String[] getReferences() {
76          if (references == null)
77              return new String[0];
78          ArrayList<String> list = new ArrayList<String>();
79          int terminator = references.toString().indexOf(':');
80          StringTokenizer st =
81              new StringTokenizer(references.substring(terminator), "\t");
82          while (st.hasMoreTokens()) {
83              list.add(st.nextToken());
84          }
85          return list.toArray(new String[list.size()]);
86      }
87      
88      /**
89       * Attempts to parse the subject line for some typical reply signatures, and strip them out
90       *
91       */
92      private void simplifySubject() {
93              int start = 0;
94              String subject = getSubject();
95              int len = subject.length();
96  
97              boolean done = false;
98  
99              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 }