1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.net.io;
  17. import java.io.IOException;
  18. import java.io.PushbackReader;
  19. import java.io.Reader;
  20. /**
  21. * DotTerminatedMessageReader is a class used to read messages from a
  22. * server that are terminated by a single dot followed by a
  23. * <CR><LF>
  24. * sequence and with double dots appearing at the begining of lines which
  25. * do not signal end of message yet start with a dot. Various Internet
  26. * protocols such as NNTP and POP3 produce messages of this type.
  27. * <p>
  28. * This class handles stripping of the duplicate period at the beginning
  29. * of lines starting with a period, converts NETASCII newlines to the
  30. * local line separator format, truncates the end of message indicator,
  31. * and ensures you cannot read past the end of the message.
  32. * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a>
  33. * @version $Id: DotTerminatedMessageReader.java,v 1.10 2004/04/21 23:30:34 scohen Exp $
  34. */
  35. public final class DotTerminatedMessageReader extends Reader
  36. {
  37. private static final String LS;
  38. private static final char[] LS_CHARS;
  39. static
  40. {
  41. LS = System.getProperty("line.separator");
  42. LS_CHARS = LS.toCharArray();
  43. }
  44. private boolean atBeginning;
  45. private boolean eof;
  46. private int pos;
  47. private char[] internalBuffer;
  48. private PushbackReader internalReader;
  49. /**
  50. * Creates a DotTerminatedMessageReader that wraps an existing Reader
  51. * input source.
  52. * @param reader The Reader input source containing the message.
  53. */
  54. public DotTerminatedMessageReader(Reader reader)
  55. {
  56. super(reader);
  57. internalBuffer = new char[LS_CHARS.length + 3];
  58. pos = internalBuffer.length;
  59. // Assumes input is at start of message
  60. atBeginning = true;
  61. eof = false;
  62. internalReader = new PushbackReader(reader);
  63. }
  64. /**
  65. * Reads and returns the next character in the message. If the end of the
  66. * message has been reached, returns -1. Note that a call to this method
  67. * may result in multiple reads from the underlying input stream to decode
  68. * the message properly (removing doubled dots and so on). All of
  69. * this is transparent to the programmer and is only mentioned for
  70. * completeness.
  71. * @return The next character in the message. Returns -1 if the end of the
  72. * message has been reached.
  73. * @exception IOException If an error occurs while reading the underlying
  74. * stream.
  75. */
  76. public int read() throws IOException
  77. {
  78. int ch;
  79. synchronized (lock)
  80. {
  81. if (pos < internalBuffer.length)
  82. {
  83. return internalBuffer[pos++];
  84. }
  85. if (eof)
  86. {
  87. return -1;
  88. }
  89. if ((ch = internalReader.read()) == -1)
  90. {
  91. eof = true;
  92. return -1;
  93. }
  94. if (atBeginning)
  95. {
  96. atBeginning = false;
  97. if (ch == '.')
  98. {
  99. ch = internalReader.read();
  100. if (ch != '.')
  101. {
  102. // read newline
  103. eof = true;
  104. internalReader.read();
  105. return -1;
  106. }
  107. else
  108. {
  109. return '.';
  110. }
  111. }
  112. }
  113. if (ch == '\r')
  114. {
  115. ch = internalReader.read();
  116. if (ch == '\n')
  117. {
  118. ch = internalReader.read();
  119. if (ch == '.')
  120. {
  121. ch = internalReader.read();
  122. if (ch != '.')
  123. {
  124. // read newline and indicate end of file
  125. internalReader.read();
  126. eof = true;
  127. }
  128. else
  129. {
  130. internalBuffer[--pos] = (char) ch;
  131. }
  132. }
  133. else
  134. {
  135. internalReader.unread(ch);
  136. }
  137. pos -= LS_CHARS.length;
  138. System.arraycopy(LS_CHARS, 0, internalBuffer, pos,
  139. LS_CHARS.length);
  140. ch = internalBuffer[pos++];
  141. }
  142. else
  143. {
  144. internalBuffer[--pos] = (char) ch;
  145. return '\r';
  146. }
  147. }
  148. return ch;
  149. }
  150. }
  151. /**
  152. * Reads the next characters from the message into an array and
  153. * returns the number of characters read. Returns -1 if the end of the
  154. * message has been reached.
  155. * @param buffer The character array in which to store the characters.
  156. * @return The number of characters read. Returns -1 if the
  157. * end of the message has been reached.
  158. * @exception IOException If an error occurs in reading the underlying
  159. * stream.
  160. */
  161. public int read(char[] buffer) throws IOException
  162. {
  163. return read(buffer, 0, buffer.length);
  164. }
  165. /**
  166. * Reads the next characters from the message into an array and
  167. * returns the number of characters read. Returns -1 if the end of the
  168. * message has been reached. The characters are stored in the array
  169. * starting from the given offset and up to the length specified.
  170. * @param buffer The character array in which to store the characters.
  171. * @param offset The offset into the array at which to start storing
  172. * characters.
  173. * @param length The number of characters to read.
  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, int offset, int length) throws IOException
  180. {
  181. int ch, off;
  182. synchronized (lock)
  183. {
  184. if (length < 1)
  185. {
  186. return 0;
  187. }
  188. if ((ch = read()) == -1)
  189. {
  190. return -1;
  191. }
  192. off = offset;
  193. do
  194. {
  195. buffer[offset++] = (char) ch;
  196. }
  197. while (--length > 0 && (ch = read()) != -1);
  198. return (offset - off);
  199. }
  200. }
  201. /**
  202. * Determines if the message is ready to be read.
  203. * @return True if the message is ready to be read, false if not.
  204. * @exception IOException If an error occurs while checking the underlying
  205. * stream.
  206. */
  207. public boolean ready() throws IOException
  208. {
  209. synchronized (lock)
  210. {
  211. return (pos < internalBuffer.length || internalReader.ready());
  212. }
  213. }
  214. /**
  215. * Closes the message for reading. This doesn't actually close the
  216. * underlying stream. The underlying stream may still be used for
  217. * communicating with the server and therefore is not closed.
  218. * <p>
  219. * If the end of the message has not yet been reached, this method
  220. * will read the remainder of the message until it reaches the end,
  221. * so that the underlying stream may continue to be used properly
  222. * for communicating with the server. If you do not fully read
  223. * a message, you MUST close it, otherwise your program will likely
  224. * hang or behave improperly.
  225. * @exception IOException If an error occurs while reading the
  226. * underlying stream.
  227. */
  228. public void close() throws IOException
  229. {
  230. synchronized (lock)
  231. {
  232. if (internalReader == null)
  233. {
  234. return;
  235. }
  236. if (!eof)
  237. {
  238. while (read() != -1)
  239. {
  240. ;
  241. }
  242. }
  243. eof = true;
  244. atBeginning = false;
  245. pos = internalBuffer.length;
  246. internalReader = null;
  247. }
  248. }
  249. }