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 }