001 /* 002 * Copyright 2001-2005 The Apache Software Foundation 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.apache.commons.net.io; 017 018 import java.io.IOException; 019 import java.io.PushbackReader; 020 import java.io.Reader; 021 022 /** 023 * DotTerminatedMessageReader is a class used to read messages from a 024 * server that are terminated by a single dot followed by a 025 * <CR><LF> 026 * sequence and with double dots appearing at the begining of lines which 027 * do not signal end of message yet start with a dot. Various Internet 028 * protocols such as NNTP and POP3 produce messages of this type. 029 * <p> 030 * This class handles stripping of the duplicate period at the beginning 031 * of lines starting with a period, converts NETASCII newlines to the 032 * local line separator format, truncates the end of message indicator, 033 * and ensures you cannot read past the end of the message. 034 * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a> 035 * @version $Id: DotTerminatedMessageReader.java 165675 2005-05-02 20:09:55Z rwinston $ 036 */ 037 public final class DotTerminatedMessageReader extends Reader 038 { 039 private static final String LS; 040 private static final char[] LS_CHARS; 041 042 static 043 { 044 LS = System.getProperty("line.separator"); 045 LS_CHARS = LS.toCharArray(); 046 } 047 048 private boolean atBeginning; 049 private boolean eof; 050 private int pos; 051 private char[] internalBuffer; 052 private PushbackReader internalReader; 053 054 /** 055 * Creates a DotTerminatedMessageReader that wraps an existing Reader 056 * input source. 057 * @param reader The Reader input source containing the message. 058 */ 059 public DotTerminatedMessageReader(Reader reader) 060 { 061 super(reader); 062 internalBuffer = new char[LS_CHARS.length + 3]; 063 pos = internalBuffer.length; 064 // Assumes input is at start of message 065 atBeginning = true; 066 eof = false; 067 internalReader = new PushbackReader(reader); 068 } 069 070 /** 071 * Reads and returns the next character in the message. If the end of the 072 * message has been reached, returns -1. Note that a call to this method 073 * may result in multiple reads from the underlying input stream to decode 074 * the message properly (removing doubled dots and so on). All of 075 * this is transparent to the programmer and is only mentioned for 076 * completeness. 077 * @return The next character in the message. Returns -1 if the end of the 078 * message has been reached. 079 * @exception IOException If an error occurs while reading the underlying 080 * stream. 081 */ 082 public int read() throws IOException 083 { 084 int ch; 085 086 synchronized (lock) 087 { 088 if (pos < internalBuffer.length) 089 { 090 return internalBuffer[pos++]; 091 } 092 093 if (eof) 094 { 095 return -1; 096 } 097 098 if ((ch = internalReader.read()) == -1) 099 { 100 eof = true; 101 return -1; 102 } 103 104 if (atBeginning) 105 { 106 atBeginning = false; 107 if (ch == '.') 108 { 109 ch = internalReader.read(); 110 111 if (ch != '.') 112 { 113 // read newline 114 eof = true; 115 internalReader.read(); 116 return -1; 117 } 118 else 119 { 120 return '.'; 121 } 122 } 123 } 124 125 if (ch == '\r') 126 { 127 ch = internalReader.read(); 128 129 if (ch == '\n') 130 { 131 ch = internalReader.read(); 132 133 if (ch == '.') 134 { 135 ch = internalReader.read(); 136 137 if (ch != '.') 138 { 139 // read newline and indicate end of file 140 internalReader.read(); 141 eof = true; 142 } 143 else 144 { 145 internalBuffer[--pos] = (char) ch; 146 } 147 } 148 else 149 { 150 internalReader.unread(ch); 151 } 152 153 pos -= LS_CHARS.length; 154 System.arraycopy(LS_CHARS, 0, internalBuffer, pos, 155 LS_CHARS.length); 156 ch = internalBuffer[pos++]; 157 } 158 else 159 { 160 internalBuffer[--pos] = (char) ch; 161 return '\r'; 162 } 163 } 164 165 return ch; 166 } 167 } 168 169 /** 170 * Reads the next characters from the message into an array and 171 * returns the number of characters read. Returns -1 if the end of the 172 * message has been reached. 173 * @param buffer The character array in which to store the characters. 174 * @return The number of characters read. Returns -1 if the 175 * end of the message has been reached. 176 * @exception IOException If an error occurs in reading the underlying 177 * stream. 178 */ 179 public int read(char[] buffer) throws IOException 180 { 181 return read(buffer, 0, buffer.length); 182 } 183 184 /** 185 * Reads the next characters from the message into an array and 186 * returns the number of characters read. Returns -1 if the end of the 187 * message has been reached. The characters are stored in the array 188 * starting from the given offset and up to the length specified. 189 * @param buffer The character array in which to store the characters. 190 * @param offset The offset into the array at which to start storing 191 * characters. 192 * @param length The number of characters to read. 193 * @return The number of characters read. Returns -1 if the 194 * end of the message has been reached. 195 * @exception IOException If an error occurs in reading the underlying 196 * stream. 197 */ 198 public int read(char[] buffer, int offset, int length) throws IOException 199 { 200 int ch, off; 201 synchronized (lock) 202 { 203 if (length < 1) 204 { 205 return 0; 206 } 207 if ((ch = read()) == -1) 208 { 209 return -1; 210 } 211 off = offset; 212 213 do 214 { 215 buffer[offset++] = (char) ch; 216 } 217 while (--length > 0 && (ch = read()) != -1); 218 219 return (offset - off); 220 } 221 } 222 223 /** 224 * Determines if the message is ready to be read. 225 * @return True if the message is ready to be read, false if not. 226 * @exception IOException If an error occurs while checking the underlying 227 * stream. 228 */ 229 public boolean ready() throws IOException 230 { 231 synchronized (lock) 232 { 233 return (pos < internalBuffer.length || internalReader.ready()); 234 } 235 } 236 237 /** 238 * Closes the message for reading. This doesn't actually close the 239 * underlying stream. The underlying stream may still be used for 240 * communicating with the server and therefore is not closed. 241 * <p> 242 * If the end of the message has not yet been reached, this method 243 * will read the remainder of the message until it reaches the end, 244 * so that the underlying stream may continue to be used properly 245 * for communicating with the server. If you do not fully read 246 * a message, you MUST close it, otherwise your program will likely 247 * hang or behave improperly. 248 * @exception IOException If an error occurs while reading the 249 * underlying stream. 250 */ 251 public void close() throws IOException 252 { 253 synchronized (lock) 254 { 255 if (internalReader == null) 256 { 257 return; 258 } 259 260 if (!eof) 261 { 262 while (read() != -1) 263 { 264 ; 265 } 266 } 267 eof = true; 268 atBeginning = false; 269 pos = internalBuffer.length; 270 internalReader = null; 271 } 272 } 273 }