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 import java.util.StringTokenizer;
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 * @author rwinston <rwinston@apache.org>
027 *
028 */
029 public class Article implements Threadable {
030 private int articleNumber;
031 private String subject;
032 private String date;
033 private String articleId;
034 private String simplifiedSubject;
035 private String from;
036 private StringBuffer header;
037 private StringBuffer references;
038 private boolean isReply = false;
039
040 public Article kid, next;
041
042 public Article() {
043 header = new StringBuffer();
044 }
045
046 /**
047 * Adds an arbitrary header key and value to this message's header.
048 * @param name the header name
049 * @param val the header value
050 */
051 public void addHeaderField(String name, String val) {
052 header.append(name);
053 header.append(": ");
054 header.append(val);
055 header.append('\n');
056 }
057
058 /**
059 * Adds a message-id to the list of messages that this message references (i.e. replies to)
060 * @param msgId
061 */
062 public void addReference(String msgId) {
063 if (references == null) {
064 references = new StringBuffer();
065 references.append("References: ");
066 }
067 references.append(msgId);
068 references.append("\t");
069 }
070
071 /**
072 * Returns the MessageId references as an array of Strings
073 * @return an array of message-ids
074 */
075 public String[] getReferences() {
076 if (references == null)
077 return new String[0];
078 ArrayList<String> list = new ArrayList<String>();
079 int terminator = references.toString().indexOf(':');
080 StringTokenizer st =
081 new StringTokenizer(references.substring(terminator), "\t");
082 while (st.hasMoreTokens()) {
083 list.add(st.nextToken());
084 }
085 return list.toArray(new String[list.size()]);
086 }
087
088 /**
089 * Attempts to parse the subject line for some typical reply signatures, and strip them out
090 *
091 */
092 private void simplifySubject() {
093 int start = 0;
094 String subject = getSubject();
095 int len = subject.length();
096
097 boolean done = false;
098
099 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 }