Author: sebb Date: Wed Mar 16 23:12:34 2011 New Revision: 1082341 URL: http://svn.apache.org/viewvc?rev=1082341&view=rev Log: NET-375 DotTerminatedMessageReader should extend BufferedReader, rather than Reader
Modified: commons/proper/net/trunk/src/changes/changes.xml commons/proper/net/trunk/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java commons/proper/net/trunk/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java Modified: commons/proper/net/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/changes/changes.xml?rev=1082341&r1=1082340&r2=1082341&view=diff ============================================================================== --- commons/proper/net/trunk/src/changes/changes.xml (original) +++ commons/proper/net/trunk/src/changes/changes.xml Wed Mar 16 23:12:34 2011 @@ -57,6 +57,9 @@ The <action> type attribute can be add,u <body> <release version="3.0" date="TBA" description="TBA"> + <action issue="NET-375" dev="sebb" type="fix"> + DotTerminatedMessageReader should extend BufferedReader, rather than Reader. + </action> <action issue="NET-374" dev="sebb" type="update"> ParserInitializationException doesn't use standard JDK exception chaining </action> Modified: commons/proper/net/trunk/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java?rev=1082341&r1=1082340&r2=1082341&view=diff ============================================================================== --- commons/proper/net/trunk/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java (original) +++ commons/proper/net/trunk/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java Wed Mar 16 23:12:34 2011 @@ -17,8 +17,8 @@ package org.apache.commons.net.io; +import java.io.BufferedReader; import java.io.IOException; -import java.io.PushbackReader; import java.io.Reader; /** @@ -30,22 +30,23 @@ import java.io.Reader; * protocols such as NNTP and POP3 produce messages of this type. * <p> * This class handles stripping of the duplicate period at the beginning - * of lines starting with a period, converts NETASCII newlines to the - * local line separator format, truncates the end of message indicator, - * and ensures you cannot read past the end of the message. + * of lines starting with a period, and ensures you cannot read past the end of the message. + * <p> + * Note: versions since 3.0 extend BufferedReader rather than Reader, + * and no longer change the CRLF into the local EOL. Also only DOT CR LF + * acts as EOF. * @author <a href="mailto:savar...@apache.org">Daniel F. Savarese</a> * @version $Id$ */ -public final class DotTerminatedMessageReader extends Reader +public final class DotTerminatedMessageReader extends BufferedReader { - private static final String LS = System.getProperty("line.separator"); - char[] LS_CHARS; + private static final char LF = '\n'; + private static final char CR = '\r'; + private static final int DOT = '.'; private boolean atBeginning; private boolean eof; - private int pos; - private char[] internalBuffer; - private PushbackReader internalReader; + private boolean seenCR; // was last character CR? /** * Creates a DotTerminatedMessageReader that wraps an existing Reader @@ -55,13 +56,9 @@ public final class DotTerminatedMessageR public DotTerminatedMessageReader(Reader reader) { super(reader); - LS_CHARS = LS.toCharArray(); - internalBuffer = new char[LS_CHARS.length + 3]; - pos = internalBuffer.length; // Assumes input is at start of message atBeginning = true; eof = false; - internalReader = new PushbackReader(reader); } /** @@ -77,96 +74,66 @@ public final class DotTerminatedMessageR * stream. */ @Override - public int read() throws IOException - { - int ch; - - synchronized (lock) - { - if (pos < internalBuffer.length) - { - return internalBuffer[pos++]; + public int read() throws IOException { + synchronized (lock) { + if (eof) { + return -1; // Don't allow read past EOF } - - if (eof) - { - return -1; - } - - if ((ch = internalReader.read()) == -1) - { + int chint = super.read(); + if (chint == -1) { // True EOF eof = true; return -1; } - - if (atBeginning) - { + if (atBeginning) { atBeginning = false; - if (ch == '.') - { - ch = internalReader.read(); - - if (ch != '.') - { - // read newline + if (chint == DOT) { // Have DOT + mark(2); // need to check for CR LF or DOT + chint = super.read(); + if (chint == -1) { // Should not happen + // new Throwable("Trailing DOT").printStackTrace(); eof = true; - internalReader.read(); - return -1; - } - else - { - return '.'; + return DOT; // return the trailing DOT } - } - } - - if (ch == '\r') - { - ch = internalReader.read(); - - if (ch == '\n') - { - ch = internalReader.read(); - - if (ch == '.') - { - ch = internalReader.read(); - - if (ch != '.') - { - // read newline and indicate end of file - internalReader.read(); - eof = true; + if (chint == DOT) { // Have DOT DOT + // no need to reset as we want to lose the first DOT + return chint; // i.e. DOT + } + if (chint == CR) { // Have DOT CR + chint = super.read(); + if (chint == -1) { // Still only DOT CR - should not happen + //new Throwable("Trailing DOT CR").printStackTrace(); + reset(); // So CR is picked up next time + return DOT; // return the trailing DOT } - else - { - internalBuffer[--pos] = (char) ch; + if (chint == LF) { // DOT CR LF + atBeginning = true; + eof = true; + // Do we need to clear the mark somehow? + return -1; } } - else - { - internalReader.unread(ch); - } - - pos -= LS_CHARS.length; - System.arraycopy(LS_CHARS, 0, internalBuffer, pos, - LS_CHARS.length); - ch = internalBuffer[pos++]; - } - else if (ch == '\r') { - internalReader.unread(ch); - } - else - { - internalBuffer[--pos] = (char) ch; - return '\r'; + // Should not happen - lone DOT at beginning + //new Throwable("Lone DOT followed by "+(char)chint).printStackTrace(); + reset(); + return DOT; + } // have DOT + } // atBeginning + + // Handle CRLF in normal flow + if (seenCR) { + seenCR = false; + if (chint == LF) { + atBeginning = true; } } - - return ch; + if (chint == CR) { + seenCR = true; + } + return chint; } } + /** * Reads the next characters from the message into an array and * returns the number of characters read. Returns -1 if the end of the @@ -224,21 +191,6 @@ public final class DotTerminatedMessageR } /** - * Determines if the message is ready to be read. - * @return True if the message is ready to be read, false if not. - * @exception IOException If an error occurs while checking the underlying - * stream. - */ - @Override - public boolean ready() throws IOException - { - synchronized (lock) - { - return (pos < internalBuffer.length || internalReader.ready()); - } - } - - /** * Closes the message for reading. This doesn't actually close the * underlying stream. The underlying stream may still be used for * communicating with the server and therefore is not closed. @@ -257,11 +209,6 @@ public final class DotTerminatedMessageR { synchronized (lock) { - if (internalReader == null) - { - return; - } - if (!eof) { while (read() != -1) @@ -271,8 +218,34 @@ public final class DotTerminatedMessageR } eof = true; atBeginning = false; - pos = internalBuffer.length; - internalReader = null; } } + + /** + * Read a line of text. + * A line is considered to be terminated by carriage return followed immediately by a linefeed. + * This contrasts with BufferedReader which also allows other combinations. + * @since 3.0 + */ + @Override + public String readLine() throws IOException { + StringBuilder sb = new StringBuilder(); + int intch; + synchronized(lock) { // make thread-safe (hopefully!) + while((intch = read()) != -1) + { + if (intch == LF && atBeginning) { + return sb.substring(0, sb.length()-1); + } + sb.append((char) intch); + } + } + String string = sb.toString(); + if (string.length() == 0) { // immediate EOF + return null; + } + // Should not happen - EOF without CRLF + //new Throwable(string).printStackTrace(); + return string; + } } Modified: commons/proper/net/trunk/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java?rev=1082341&r1=1082340&r2=1082341&view=diff ============================================================================== --- commons/proper/net/trunk/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java (original) +++ commons/proper/net/trunk/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java Wed Mar 16 23:12:34 2011 @@ -27,36 +27,36 @@ public class DotTerminatedMessageReaderT private DotTerminatedMessageReader reader; private StringBuilder str = new StringBuilder(); private char[] buf = new char[64]; - final static String SEP = System.getProperty("line.separator"); + private static final String CRLF = "\r\n"; + private static final String DOT = "."; + private static final String EOM = CRLF+DOT+CRLF; public void testReadSimpleStringCrLfLineEnding() throws IOException { - final String test = "Hello World!\r\n.\r\n"; + final String test = "Hello World!"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); - reader.LS_CHARS = new char[]{'\r','\n'}; int read = 0; while ((read = reader.read(buf)) != -1) { str.append(buf, 0, read); } - assertEquals("Hello World!" + String.valueOf(reader.LS_CHARS), str.toString()); + assertEquals("Hello World!"+CRLF, str.toString()); } public void testReadSimpleStringLfLineEnding() throws IOException { - final String test = "Hello World!\r\n.\r\n"; + final String test = "Hello World!"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); - reader.LS_CHARS = new char[]{'\n'}; int read = 0; while ((read = reader.read(buf)) != -1) { str.append(buf, 0, read); } - assertEquals("Hello World!" + String.valueOf(reader.LS_CHARS), str.toString()); + assertEquals("Hello World!"+CRLF, str.toString()); } public void testEmbeddedNewlines() throws IOException { - final String test = "Hello\r\nWorld\nA\rB\r\n.\r\n"; + final String test = "Hello"+CRLF+"World\nA\rB"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -64,11 +64,11 @@ public class DotTerminatedMessageReaderT str.append(buf, 0, read); } - assertEquals(str.toString(), "Hello" + SEP +"World\nA\rB" + SEP); + assertEquals(str.toString(), "Hello" + CRLF +"World\nA\rB" + CRLF); } public void testDoubleCrBeforeDot() throws IOException { - final String test = "Hello World!\r\r\n.\r\n"; + final String test = "Hello World!\r"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -76,11 +76,11 @@ public class DotTerminatedMessageReaderT str.append(buf, 0, read); } - assertEquals("Hello World!\r" + SEP,str.toString()); + assertEquals("Hello World!\r" + CRLF,str.toString()); } public void testLeadingDot() throws IOException { - final String test = "Hello World!\r\n..text\r\n.\r\n"; + final String test = "Hello World!"+CRLF+"..text"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -88,11 +88,11 @@ public class DotTerminatedMessageReaderT str.append(buf, 0, read); } - assertEquals("Hello World!" + SEP+".text"+SEP,str.toString()); + assertEquals("Hello World!" + CRLF+".text"+CRLF,str.toString()); } public void testEmbeddedDot1() throws IOException { - final String test = "Hello . World!\r\n.\r\n"; + final String test = "Hello . World!"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -100,11 +100,11 @@ public class DotTerminatedMessageReaderT str.append(buf, 0, read); } - assertEquals("Hello . World!" + SEP,str.toString()); + assertEquals("Hello . World!" + CRLF,str.toString()); } public void testEmbeddedDot2() throws IOException { - final String test = "Hello .. World!\r\n.\r\n"; + final String test = "Hello .. World!"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -112,11 +112,11 @@ public class DotTerminatedMessageReaderT str.append(buf, 0, read); } - assertEquals("Hello .. World!" + SEP,str.toString()); + assertEquals("Hello .. World!" + CRLF,str.toString()); } public void testEmbeddedDot3() throws IOException { - final String test = "Hello World.\r\nmore\r\n.\r\n"; + final String test = "Hello World."+CRLF+"more"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -124,11 +124,11 @@ public class DotTerminatedMessageReaderT str.append(buf, 0, read); } - assertEquals("Hello World." + SEP+"more"+SEP,str.toString()); + assertEquals("Hello World." + CRLF+"more"+CRLF,str.toString()); } public void testEmbeddedDot4() throws IOException { - final String test = "Hello World\r.\nmore\r\n.\r\n"; + final String test = "Hello World\r.\nmore"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -136,13 +136,39 @@ public class DotTerminatedMessageReaderT str.append(buf, 0, read); } - assertEquals("Hello World\r.\nmore" + SEP,str.toString()); + assertEquals("Hello World\r.\nmore" + CRLF,str.toString()); + } + + public void testReadLine1() throws Exception { + final String test = "Hello World"+CRLF+"more"+EOM; + reader = new DotTerminatedMessageReader(new StringReader(test)); + + String line; + while ((line = reader.readLine()) != null) { + str.append(line); + str.append("#"); + } + + assertEquals("Hello World#more#",str.toString()); + + } + + public void testReadLine2() throws Exception { + final String test = "Hello World\r.\nmore"+EOM; + reader = new DotTerminatedMessageReader(new StringReader(test)); + + String line; + while ((line = reader.readLine()) != null) { + str.append(line); + str.append("#"); + } + + assertEquals("Hello World\r.\nmore#",str.toString()); + } - // This test agrees with the Javadoc. - // However the sequence should not happen for well-behaved NNTP and POP3 servers public void testSingleDotWithTrailingText() throws IOException { - final String test = "Hello World!\r\n.text\r\n"; + final String test = "Hello World!"+CRLF+".text"+EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -150,10 +176,7 @@ public class DotTerminatedMessageReaderT str.append(buf, 0, read); } - assertEquals("Hello World!" + SEP,str.toString()); - - // Note: the StringReader input will still contain "xt\r\n" - // because DTMR treats the "te" as CRLF + assertEquals("Hello World!"+CRLF+".text"+CRLF,str.toString()); } }