001 /* ====================================================================
002 * The Apache Software License, Version 1.1
003 *
004 * Copyright (c) 2001-2005 The Apache Software Foundation. All rights
005 * reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met:
010 *
011 * 1. Redistributions of source code must retain the above copyright
012 * notice, this list of conditions and the following disclaimer.
013 *
014 * 2. Redistributions in binary form must reproduce the above copyright
015 * notice, this list of conditions and the following disclaimer in
016 * the documentation and/or other materials provided with the
017 * distribution.
018 *
019 * 3. The end-user documentation included with the redistribution,
020 * if any, must include the following acknowledgment:
021 * "This product includes software developed by the
022 * Apache Software Foundation (http://www.apache.org/)."
023 * Alternately, this acknowledgment may appear in the software itself,
024 * if and wherever such third-party acknowledgments normally appear.
025 *
026 * 4. The names "Apache" and "Apache Software Foundation" and
027 * "Apache Commons" must not be used to endorse or promote products
028 * derived from this software without prior written permission. For
029 * written permission, please contact apache@apache.org.
030 *
031 * 5. Products derived from this software may not be called "Apache",
032 * nor may "Apache" appear in their name, without
033 * prior written permission of the Apache Software Foundation.
034 *
035 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046 * SUCH DAMAGE.
047 * ====================================================================
048 *
049 * This software consists of voluntary contributions made by many
050 * individuals on behalf of the Apache Software Foundation. For more
051 * information on the Apache Software Foundation, please see
052 * <http://www.apache.org/>.
053 */
054
055 package org.apache.commons.net.nntp;
056
057 import java.util.ArrayList;
058 import java.util.StringTokenizer;
059
060 /**
061 * This is a class that contains the basic state needed for message retrieval and threading.
062 * With thanks to Jamie Zawinski <jwz@jwz.org>
063 * @author rwinston <rwinston@checkfree.com>
064 *
065 */
066 public class Article implements Threadable {
067 private int articleNumber;
068 private String subject;
069 private String date;
070 private String articleId;
071 private String simplifiedSubject;
072 private String from;
073 private StringBuffer header;
074 private StringBuffer references;
075 private boolean isReply = false;
076
077 public Article kid, next;
078
079 public Article() {
080 header = new StringBuffer();
081 }
082
083 /**
084 * Adds an arbitrary header key and value to this message's header.
085 * @param name the header name
086 * @param val the header value
087 */
088 public void addHeaderField(String name, String val) {
089 header.append(name);
090 header.append(": ");
091 header.append(val);
092 header.append('\n');
093 }
094
095 /**
096 * Adds a message-id to the list of messages that this message references (i.e. replies to)
097 * @param msgId
098 */
099 public void addReference(String msgId) {
100 if (references == null) {
101 references = new StringBuffer();
102 references.append("References: ");
103 }
104 references.append(msgId);
105 references.append("\t");
106 }
107
108 /**
109 * Returns the MessageId references as an array of Strings
110 * @return an array of message-ids
111 */
112 public String[] getReferences() {
113 if (references == null)
114 return new String[0];
115 ArrayList list = new ArrayList();
116 int terminator = references.toString().indexOf(':');
117 StringTokenizer st =
118 new StringTokenizer(references.substring(terminator), "\t");
119 while (st.hasMoreTokens()) {
120 list.add(st.nextToken());
121 }
122 return (String[]) list.toArray();
123 }
124
125 /**
126 * Attempts to parse the subject line for some typical reply signatures, and strip them out
127 *
128 */
129 private void simplifySubject() {
130 int start = 0;
131 String subject = getSubject();
132 int len = subject.length();
133
134 boolean done = false;
135
136 while (!done) {
137 done = true;
138
139 // skip whitespace
140 // "Re: " breaks this
141 while (start < len && subject.charAt(start) == ' ') {
142 start++;
143 }
144
145 if (start < (len - 2)
146 && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
147 && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {
148
149 if (subject.charAt(start + 2) == ':') {
150 start += 3; // Skip "Re:"
151 isReply = true;
152 done = false;
153 } else if (
154 start < (len - 2)
155 &&
156 (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {
157
158 int i = start + 3;
159
160 while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9')
161 i++;
162
163 if (i < (len - 1)
164 && (subject.charAt(i) == ']' || subject.charAt(i) == ')')
165 && subject.charAt(i + 1) == ':') {
166 start = i + 2;
167 isReply = true;
168 done = false;
169 }
170 }
171 }
172
173 if (simplifiedSubject == "(no subject)")
174 simplifiedSubject = "";
175
176 int end = len;
177
178 while (end > start && subject.charAt(end - 1) < ' ')
179 end--;
180
181 if (start == 0 && end == len)
182 simplifiedSubject = subject;
183 else
184 simplifiedSubject = subject.substring(start, end);
185 }
186 }
187
188 /**
189 * Recursive method that traverses a pre-threaded graph (or tree)
190 * of connected Article objects and prints them out.
191 * @param article the root of the article 'tree'
192 * @param depth the current tree depth
193 */
194 public static void printThread(Article article, int depth) {
195 for (int i = 0; i < depth; ++i)
196 System.out.print("==>");
197 System.out.println(article.getSubject() + "\t" + article.getFrom());
198 if (article.kid != null)
199 printThread(article.kid, depth + 1);
200 if (article.next != null)
201 printThread(article.next, depth);
202 }
203
204 public String getArticleId() {
205 return articleId;
206 }
207
208 public int getArticleNumber() {
209 return articleNumber;
210 }
211
212 public String getDate() {
213 return date;
214 }
215
216 public String getFrom() {
217 return from;
218 }
219
220 public String getSubject() {
221 return subject;
222 }
223
224 public void setArticleId(String string) {
225 articleId = string;
226 }
227
228 public void setArticleNumber(int i) {
229 articleNumber = i;
230 }
231
232 public void setDate(String string) {
233 date = string;
234 }
235
236 public void setFrom(String string) {
237 from = string;
238 }
239
240 public void setSubject(String string) {
241 subject = string;
242 }
243
244
245 public boolean isDummy() {
246 return (getSubject() == null);
247 }
248
249 public String messageThreadId() {
250 return articleId;
251 }
252
253 public String[] messageThreadReferences() {
254 return getReferences();
255 }
256
257 public String simplifiedSubject() {
258 if(simplifiedSubject == null)
259 simplifySubject();
260 return simplifiedSubject;
261 }
262
263
264 public boolean subjectIsReply() {
265 if(simplifiedSubject == null)
266 simplifySubject();
267 return isReply;
268 }
269
270
271 public void setChild(Threadable child) {
272 this.kid = (Article) child;
273 flushSubjectCache();
274 }
275
276 private void flushSubjectCache() {
277 simplifiedSubject = null;
278 }
279
280
281 public void setNext(Threadable next) {
282 this.next = (Article)next;
283 flushSubjectCache();
284 }
285
286
287 public Threadable makeDummy() {
288 return (Threadable)new Article();
289 }
290 }