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 * https://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 018package org.apache.commons.net.nntp; 019 020import java.io.PrintStream; 021import java.util.ArrayList; 022import java.util.Collections; 023 024import org.apache.commons.net.util.NetConstants; 025 026/** 027 * Basic state needed for message retrieval and threading. With thanks to Jamie Zawinski (jwz@jwz.org) 028 */ 029public class Article implements Threadable<Article> { 030 031 /** 032 * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. 033 * 034 * @param article the root of the article 'tree' 035 * @since 3.4 036 */ 037 public static void printThread(final Article article) { 038 printThread(article, 0, System.out); 039 } 040 041 /** 042 * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. 043 * 044 * @param article the root of the article 'tree' 045 * @param depth the current tree depth 046 */ 047 public static void printThread(final Article article, final int depth) { 048 printThread(article, depth, System.out); 049 } 050 051 /** 052 * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. 053 * 054 * @param article the root of the article 'tree' 055 * @param depth the current tree depth 056 * @param ps the PrintStream to use 057 * @since 3.4 058 */ 059 public static void printThread(final Article article, final int depth, final PrintStream ps) { 060 for (int i = 0; i < depth; ++i) { 061 ps.print("==>"); 062 } 063 ps.println(article.getSubject() + "\t" + article.getFrom() + "\t" + article.getArticleId()); 064 if (article.kid != null) { 065 printThread(article.kid, depth + 1); 066 } 067 if (article.next != null) { 068 printThread(article.next, depth); 069 } 070 } 071 072 /** 073 * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. 074 * 075 * @param article the root of the article 'tree' 076 * @param ps the PrintStream to use 077 * @since 3.4 078 */ 079 public static void printThread(final Article article, final PrintStream ps) { 080 printThread(article, 0, ps); 081 } 082 083 private long articleNumber; 084 private String subject; 085 private String date; 086 private String articleId; 087 088 private String simplifiedSubject; 089 090 private String from; 091 private ArrayList<String> references; 092 093 private boolean isReply; 094 095 /** 096 * Will be private in 4.0. 097 * 098 * @deprecated Use {@link #setChild(Article)} and {@link #getChild()}. 099 */ 100 @Deprecated 101 public Article kid; 102 103 /** 104 * Will be private in 4.0. 105 * 106 * @deprecated Use {@link #setNext(Article)} and {@link #getNext()}. 107 */ 108 @Deprecated 109 public Article next; 110 111 /** 112 * Constructs a new instance. 113 */ 114 public Article() { 115 articleNumber = -1; // isDummy 116 } 117 118 /** 119 * Does nothing. 120 * 121 * @param name Ignored. 122 * @param val Ignored. 123 */ 124 @Deprecated 125 public void addHeaderField(final String name, final String val) { 126 // empty 127 } 128 129 /** 130 * Adds a message-id to the list of messages that this message references (i.e. replies to) 131 * 132 * @param msgId the message id to add 133 */ 134 public void addReference(final String msgId) { 135 if (msgId == null || msgId.isEmpty()) { 136 return; 137 } 138 if (references == null) { 139 references = new ArrayList<>(); 140 } 141 isReply = true; 142 Collections.addAll(references, msgId.split(" ")); 143 } 144 145 private void flushSubjectCache() { 146 simplifiedSubject = null; 147 } 148 149 /** 150 * Gets the article ID. 151 * 152 * @return the article ID. 153 */ 154 public String getArticleId() { 155 return articleId; 156 } 157 158 /** 159 * Gets the article number. 160 * 161 * @return the article number. 162 */ 163 @Deprecated 164 public int getArticleNumber() { 165 return (int) articleNumber; 166 } 167 168 /** 169 * Gets the article number. 170 * 171 * @return the article number. 172 */ 173 public long getArticleNumberLong() { 174 return articleNumber; 175 } 176 177 /** 178 * Gets the child article. 179 * 180 * @return the child article. 181 * @since 3.12.0 182 */ 183 public Article getChild() { 184 return kid; 185 } 186 187 /** 188 * Gets the article date header. 189 * 190 * @return the article date header. 191 */ 192 public String getDate() { 193 return date; 194 } 195 196 /** 197 * Gets the article from header. 198 * 199 * @return the article from header. 200 */ 201 public String getFrom() { 202 return from; 203 } 204 205 /** 206 * Gets the next article. 207 * 208 * @return the next article. 209 * @since 3.12.0 210 */ 211 public Article getNext() { 212 return next; 213 } 214 215 /** 216 * Returns the MessageId references as an array of Strings 217 * 218 * @return an array of message-ids 219 */ 220 public String[] getReferences() { 221 if (references == null) { 222 return NetConstants.EMPTY_STRING_ARRAY; 223 } 224 return references.toArray(NetConstants.EMPTY_STRING_ARRAY); 225 } 226 227 /** 228 * Gets the article subject. 229 * 230 * @return the article subject. 231 */ 232 public String getSubject() { 233 return subject; 234 } 235 236 @Override 237 public boolean isDummy() { 238 return articleNumber == -1; 239 } 240 241 @Override 242 public Article makeDummy() { 243 return new Article(); 244 } 245 246 @Override 247 public String messageThreadId() { 248 return articleId; 249 } 250 251 @Override 252 public String[] messageThreadReferences() { 253 return getReferences(); 254 } 255 256 /** 257 * Sets the article ID. 258 * 259 * @param string the article ID. 260 */ 261 public void setArticleId(final String string) { 262 articleId = string; 263 } 264 265 /** 266 * Sets the article number. 267 * 268 * @param articleNumber the article number. 269 */ 270 @Deprecated 271 public void setArticleNumber(final int articleNumber) { 272 this.articleNumber = articleNumber; 273 } 274 275 /** 276 * Sets the article number. 277 * 278 * @param articleNumber the article number. 279 */ 280 public void setArticleNumber(final long articleNumber) { 281 this.articleNumber = articleNumber; 282 } 283 284 @Override 285 public void setChild(final Article child) { 286 this.kid = child; 287 flushSubjectCache(); 288 } 289 290 /** 291 * Sets the article date header. 292 * 293 * @param date the article date header. 294 */ 295 public void setDate(final String date) { 296 this.date = date; 297 } 298 299 /** 300 * Sets the article from header. 301 * 302 * @param from the article from header. 303 */ 304 public void setFrom(final String from) { 305 this.from = from; 306 } 307 308 @Override 309 public void setNext(final Article next) { 310 this.next = next; 311 flushSubjectCache(); 312 } 313 314 /** 315 * Sets the article subject. 316 * 317 * @param subject the article subject. 318 */ 319 public void setSubject(final String subject) { 320 this.subject = subject; 321 } 322 323 @Override 324 public String simplifiedSubject() { 325 if (simplifiedSubject == null) { 326 simplifySubject(); 327 } 328 return simplifiedSubject; 329 } 330 331 /** 332 * Attempts to parse the subject line for some typical reply signatures, and strip them out 333 */ 334 private void simplifySubject() { 335 int start = 0; 336 final String subject = getSubject(); 337 final int len = subject.length(); 338 339 boolean done = false; 340 341 while (!done) { 342 done = true; 343 344 // skip whitespace 345 // "Re: " breaks this 346 while (start < len && subject.charAt(start) == ' ') { 347 start++; 348 } 349 350 if (start < len - 2 && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R') 351 && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) { 352 353 if (subject.charAt(start + 2) == ':') { 354 start += 3; // Skip "Re:" 355 done = false; 356 } else if (start < len - 2 && (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) { 357 358 int i = start + 3; 359 360 while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') { 361 i++; 362 } 363 364 if (i < len - 1 && (subject.charAt(i) == ']' || subject.charAt(i) == ')') && subject.charAt(i + 1) == ':') { 365 start = i + 2; 366 done = false; 367 } 368 } 369 } 370 371 if ("(no subject)".equals(simplifiedSubject)) { 372 simplifiedSubject = ""; 373 } 374 375 int end = len; 376 377 while (end > start && subject.charAt(end - 1) < ' ') { 378 end--; 379 } 380 381 if (start == 0 && end == len) { 382 simplifiedSubject = subject; 383 } else { 384 simplifiedSubject = subject.substring(start, end); 385 } 386 } 387 } 388 389 @Override 390 public boolean subjectIsReply() { 391 return isReply; 392 } 393 394 @Override 395 public String toString() { // Useful for Eclipse debugging 396 return articleNumber + " " + articleId + " " + subject; 397 } 398 399}