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 private void simplifySubject() { 233 int start = 0; 234 final String subject = getSubject(); 235 final int len = subject.length(); 236 237 boolean done = false; 238 239 while (!done) { 240 done = true; 241 242 // skip whitespace 243 // "Re: " breaks this 244 while (start < len && subject.charAt(start) == ' ') { 245 start++; 246 } 247 248 if (start < len - 2 && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R') 249 && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) { 250 251 if (subject.charAt(start + 2) == ':') { 252 start += 3; // Skip "Re:" 253 done = false; 254 } else if (start < len - 2 && (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) { 255 256 int i = start + 3; 257 258 while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') { 259 i++; 260 } 261 262 if (i < len - 1 && (subject.charAt(i) == ']' || subject.charAt(i) == ')') && subject.charAt(i + 1) == ':') { 263 start = i + 2; 264 done = false; 265 } 266 } 267 } 268 269 if ("(no subject)".equals(simplifiedSubject)) { 270 simplifiedSubject = ""; 271 } 272 273 int end = len; 274 275 while (end > start && subject.charAt(end - 1) < ' ') { 276 end--; 277 } 278 279 if (start == 0 && end == len) { 280 simplifiedSubject = subject; 281 } else { 282 simplifiedSubject = subject.substring(start, end); 283 } 284 } 285 } 286 287 @Override 288 public boolean subjectIsReply() { 289 return isReply; 290 } 291 292 @Override 293 public String toString() { // Useful for Eclipse debugging 294 return articleNumber + " " + articleId + " " + subject; 295 } 296 297}