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 ("(no subject)".equals(simplifiedSubject)) 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 }