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 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 * This is a class that contains the basic state needed for message retrieval and threading. With thanks to Jamie Zawinski (jwz@jwz.org) 028 */ 029public class Article implements Threadable { 030 /** 031 * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. 032 * 033 * @param article the root of the article 'tree' 034 * @since 3.4 035 */ 036 public static void printThread(final Article article) { 037 printThread(article, 0, System.out); 038 } 039 040 /** 041 * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. 042 * 043 * @param article the root of the article 'tree' 044 * @param depth the current tree depth 045 */ 046 public static void printThread(final Article article, final int depth) { 047 printThread(article, depth, System.out); 048 } 049 050 /** 051 * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. 052 * 053 * @param article the root of the article 'tree' 054 * @param depth the current tree depth 055 * @param ps the PrintStream to use 056 * @since 3.4 057 */ 058 public static void printThread(final Article article, final int depth, final PrintStream ps) { 059 for (int i = 0; i < depth; ++i) { 060 ps.print("==>"); 061 } 062 ps.println(article.getSubject() + "\t" + article.getFrom() + "\t" + article.getArticleId()); 063 if (article.kid != null) { 064 printThread(article.kid, depth + 1); 065 } 066 if (article.next != null) { 067 printThread(article.next, depth); 068 } 069 } 070 071 /** 072 * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. 073 * 074 * @param article the root of the article 'tree' 075 * @param ps the PrintStream to use 076 * @since 3.4 077 */ 078 public static void printThread(final Article article, final PrintStream ps) { 079 printThread(article, 0, ps); 080 } 081 082 private long articleNumber; 083 private String subject; 084 private String date; 085 private String articleId; 086 087 private String simplifiedSubject; 088 089 private String from; 090 private ArrayList<String> references; 091 092 private boolean isReply; 093 094 public Article kid, next; 095 096 public Article() { 097 articleNumber = -1; // isDummy 098 } 099 100 @Deprecated 101 102 public void addHeaderField(final String name, final String val) { 103 } 104 105 /** 106 * Adds a message-id to the list of messages that this message references (i.e. replies to) 107 * 108 * @param msgId the message id to add 109 */ 110 public void addReference(final String msgId) { 111 if (msgId == null || msgId.isEmpty()) { 112 return; 113 } 114 if (references == null) { 115 references = new ArrayList<>(); 116 } 117 isReply = true; 118 Collections.addAll(references, msgId.split(" ")); 119 } 120 121 private void flushSubjectCache() { 122 simplifiedSubject = null; 123 } 124 125 public String getArticleId() { 126 return articleId; 127 } 128 129 @Deprecated 130 public int getArticleNumber() { 131 return (int) articleNumber; 132 } 133 134 public long getArticleNumberLong() { 135 return articleNumber; 136 } 137 138 public String getDate() { 139 return date; 140 } 141 142 public String getFrom() { 143 return from; 144 } 145 146 /** 147 * Returns the MessageId references as an array of Strings 148 * 149 * @return an array of message-ids 150 */ 151 public String[] getReferences() { 152 if (references == null) { 153 return NetConstants.EMPTY_STRING_ARRAY; 154 } 155 return references.toArray(NetConstants.EMPTY_STRING_ARRAY); 156 } 157 158 public String getSubject() { 159 return subject; 160 } 161 162 @Override 163 public boolean isDummy() { 164 return articleNumber == -1; 165 } 166 167 @Override 168 public Threadable makeDummy() { 169 return new Article(); 170 } 171 172 @Override 173 public String messageThreadId() { 174 return articleId; 175 } 176 177 @Override 178 public String[] messageThreadReferences() { 179 return getReferences(); 180 } 181 182 public void setArticleId(final String string) { 183 articleId = string; 184 } 185 186 @Deprecated 187 public void setArticleNumber(final int a) { 188 articleNumber = a; 189 } 190 191 public void setArticleNumber(final long l) { 192 articleNumber = l; 193 } 194 195 @Override 196 public void setChild(final Threadable child) { 197 this.kid = (Article) child; 198 flushSubjectCache(); 199 } 200 201 public void setDate(final String string) { 202 date = string; 203 } 204 205 public void setFrom(final String string) { 206 from = string; 207 } 208 209 @Override 210 public void setNext(final Threadable next) { 211 this.next = (Article) next; 212 flushSubjectCache(); 213 } 214 215 public void setSubject(final String string) { 216 subject = string; 217 } 218 219 @Override 220 public String simplifiedSubject() { 221 if (simplifiedSubject == null) { 222 simplifySubject(); 223 } 224 return simplifiedSubject; 225 } 226 227 // DEPRECATED METHODS - for API compatibility only - DO NOT USE 228 229 /** 230 * Attempts to parse the subject line for some typical reply signatures, and strip them out 231 * 232 */ 233 private void simplifySubject() { 234 int start = 0; 235 final String subject = getSubject(); 236 final int len = subject.length(); 237 238 boolean done = false; 239 240 while (!done) { 241 done = true; 242 243 // skip whitespace 244 // "Re: " breaks this 245 while (start < len && subject.charAt(start) == ' ') { 246 start++; 247 } 248 249 if (start < len - 2 && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R') 250 && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) { 251 252 if (subject.charAt(start + 2) == ':') { 253 start += 3; // Skip "Re:" 254 done = false; 255 } else if (start < len - 2 && (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) { 256 257 int i = start + 3; 258 259 while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') { 260 i++; 261 } 262 263 if (i < len - 1 && (subject.charAt(i) == ']' || subject.charAt(i) == ')') && subject.charAt(i + 1) == ':') { 264 start = i + 2; 265 done = false; 266 } 267 } 268 } 269 270 if ("(no subject)".equals(simplifiedSubject)) { 271 simplifiedSubject = ""; 272 } 273 274 int end = len; 275 276 while (end > start && subject.charAt(end - 1) < ' ') { 277 end--; 278 } 279 280 if (start == 0 && end == len) { 281 simplifiedSubject = subject; 282 } else { 283 simplifiedSubject = subject.substring(start, end); 284 } 285 } 286 } 287 288 @Override 289 public boolean subjectIsReply() { 290 return isReply; 291 } 292 293 @Override 294 public String toString() { // Useful for Eclipse debugging 295 return articleNumber + " " + articleId + " " + subject; 296 } 297 298}