changeset 700:940e266eeccd

Refactor BufferedMCLReader It used to inherit from BufferedReader but there is no reason for that. Also, it used to have a method readLine() which - returned the line read - stored the linetype In the new setup we have a method advance() which reads a line and stores both it and its type. This makes the code more regular and makes it possible to peek ahead without consuming.
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Thu, 08 Dec 2022 15:59:17 +0100 (2022-12-08)
parents 0ff364f569a1
children f89882b07614
files src/main/java/org/monetdb/jdbc/MonetConnection.java src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java src/main/java/org/monetdb/mcl/io/BufferedMCLWriter.java src/main/java/org/monetdb/mcl/net/MapiSocket.java src/main/java/org/monetdb/merovingian/Control.java src/main/java/org/monetdb/util/SQLRestore.java tests/JDBC_API_Tester.java tests/SQLcopyinto.java
diffstat 8 files changed, 110 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetConnection.java
+++ b/src/main/java/org/monetdb/jdbc/MonetConnection.java
@@ -343,7 +343,7 @@ public class MonetConnection
 			in = server.getReader();
 			out = server.getWriter();
 
-			final String error = in.waitForPrompt();
+			final String error = in.discardRemainder();
 			if (error != null)
 				throw new SQLNonTransientConnectionException((error.length() > 6) ? error.substring(6) : error, "08001");
 		} catch (java.net.UnknownHostException e) {
@@ -2138,7 +2138,7 @@ public class MonetConnection
 					out.writeLine(queryTempl[0] + command + queryTempl[1]);
 				else
 					out.writeLine(commandTempl[0] + command + commandTempl[1]);
-				final String error = in.waitForPrompt();
+				final String error = in.discardRemainder();
 				if (error != null)
 					throw new SQLException(error.substring(6), error.substring(0, 5));
 			} catch (SocketTimeoutException e) {
@@ -3131,7 +3131,7 @@ public class MonetConnection
 					// have the prompt it is possible (and most likely) that we
 					// already have the prompt and do not have to skip any
 					// lines.  Ignore errors from previous result sets.
-					in.waitForPrompt();
+					in.discardRemainder();
 
 					// {{{ set reply size
 					/**
@@ -3158,16 +3158,15 @@ public class MonetConnection
 					out.writeLine(templ[0] + query + templ[1]);
 
 					// go for new results
-					String tmpLine = in.readLine();
-					LineType linetype = in.getLineType();
+					in.advance();
 					Response res = null;
-					while (linetype != LineType.PROMPT) {
+					while (in.getLineType() != LineType.PROMPT) {
 						// each response should start with a start of header (or error)
-						switch (linetype) {
+						switch (in.getLineType()) {
 						case SOHEADER:
 							// make the response object, and fill it
 							try {
-								switch (sohp.parse(tmpLine)) {
+								switch (sohp.parse(in.getLine())) {
 								case StartOfHeaderParser.Q_PARSE:
 									throw new MCLParseException("Q_PARSE header not allowed here", 1);
 								case StartOfHeaderParser.Q_TABLE:
@@ -3228,31 +3227,29 @@ public class MonetConnection
 								final int offset = e.getErrorOffset();
 								error = "M0M10!error while parsing start of header:\n" +
 									e.getMessage() +
-									" found: '" + tmpLine.charAt(offset) +
-									"' in: \"" + tmpLine +
+									" found: '" + in.getLine().charAt(offset) +
+									"' in: \"" + in.getLine() +
 									"\" at pos: " + offset;
 								// flush all the rest
-								in.waitForPrompt();
-								linetype = in.getLineType();
+								in.discardRemainder();
 								break;
 							}
 
 							// immediately handle errors after parsing the header (res may be null)
 							if (error != null) {
-								in.waitForPrompt();
-								linetype = in.getLineType();
+								in.discardRemainder();
 								break;
 							}
 
 							// here we have a res object, which we can start filling
 							while (res.wantsMore()) {
-								error = res.addLine(in.readLine(), in.getLineType());
+								in.advance();
+								error = res.addLine(in.getLine(), in.getLineType());
 								if (error != null) {
 									// right, some protocol violation,
 									// skip the rest of the result
 									error = "M0M10!" + error;
-									in.waitForPrompt();
-									linetype = in.getLineType();
+									in.discardRemainder();
 									break;
 								}
 							}
@@ -3266,21 +3263,20 @@ public class MonetConnection
 
 							// read the next line (can be prompt, new result, error, etc.)
 							// before we start the loop over
-							tmpLine = in.readLine();
-							linetype = in.getLineType();
+							in.advance();
 							break;
 						case INFO:
-							addWarning(tmpLine.substring(1), "01000");
+							addWarning(in.getLine().substring(1), "01000");
 							// read the next line (can be prompt, new result, error, etc.)
 							// before we start the loop over
-							tmpLine = in.readLine();
-							linetype = in.getLineType();
+							in.advance();
 							break;
 						case FILETRANSFER:
 							// Consume the command
-							final String transferCommand = in.readLine();
+							in.advance();
+							final String transferCommand = in.getLine();
 							// Consume the fake prompt inserted by MapiSocket.
-							in.readLine();
+							in.advance();
 							// Handle the request
 							if (transferCommand != null)
 								error = handleTransfer(transferCommand);
@@ -3289,27 +3285,21 @@ public class MonetConnection
 							// Then prepare for the next iteration
 							if (error != null) {
 								out.writeLine(error + "\n");
-								error = in.waitForPrompt();
+								error = in.discardRemainder();
 							} else {
-								tmpLine = in.readLine();
+								in.advance();
 							}
-							linetype = in.getLineType();
 							break;
-						default:	// Yeah... in Java this is correct!
+						default:
 							// we have something we don't expect/understand, let's make it an error message
-							tmpLine = "!M0M10!protocol violation, unexpected " + linetype + " line: " + tmpLine;
-							// don't break; fall through...
+							String msg = "M0M10!protocol violation, unexpected " + in.getLineType() + " line: " + in.getLine();
+							error = in.discardRemainder(msg);
+							break;
 						case ERROR:
 							// read everything till the prompt (should be
 							// error) we don't know if we ignore some
 							// garbage here... but the log should reveal that
-							error = in.waitForPrompt();
-							linetype = in.getLineType();
-							if (error != null) {
-								error = tmpLine.substring(1) + "\n" + error;
-							} else {
-								error = tmpLine.substring(1);
-							}
+							error = in.discardRemainder(in.getLine().substring(1));
 							break;
 						} // end of switch (linetype)
 					} // end of while (linetype != LineType.PROMPT)
--- a/src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java
+++ b/src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java
@@ -40,9 +40,10 @@ import java.io.UnsupportedEncodingExcept
  * @see org.monetdb.mcl.net.MapiSocket
  * @see org.monetdb.mcl.io.BufferedMCLWriter
  */
-public final class BufferedMCLReader extends BufferedReader {
+public final class BufferedMCLReader /* extends BufferedReader */ {
 
-	/** The type of the last line read */
+	private final BufferedReader inner;
+	private String current = null;
 	private LineType lineType = LineType.UNKNOWN;
 
 	/**
@@ -52,7 +53,7 @@ public final class BufferedMCLReader ext
 	 * @param in A Reader
 	 */
 	public BufferedMCLReader(final Reader in) {
-		super(in);
+		inner = new BufferedReader(in);
 	}
 
 	/**
@@ -66,47 +67,37 @@ public final class BufferedMCLReader ext
 	public BufferedMCLReader(final InputStream in, final String enc)
 		throws UnsupportedEncodingException
 	{
-		super(new java.io.InputStreamReader(in, enc));
+		this(new java.io.InputStreamReader(in, enc));
+	}
+
+	public void advance() throws IOException {
+		if (lineType == LineType.PROMPT)
+			return;
+
+		current = inner.readLine();
+		lineType = LineType.classify(current);
+		if (lineType == LineType.ERROR && current != null && !current.matches("^![0-9A-Z]{5}!.+")) {
+			current = "!22000!" + current.substring(1);
+		}
 	}
 
 	/**
-	 * Read a line of text.  A line is considered to be terminated by
-	 * any one of a line feed ('\n'), a carriage return ('\r'), or a
-	 * carriage return followed immediately by a linefeed.  Before this
-	 * method returns, it sets the linetype to any of the in MCL
-	 * recognised line types.
-	 *
-	 * Warning: until the server properly prefixes all of its error
-	 * messages with SQLSTATE codes, this method prefixes all errors it
-	 * sees without sqlstate with the generic data exception code (22000).
-	 *
-	 * @return A String containing the contents of the line, not
-	 *         including any line-termination characters, or null if the
-	 *         end of the stream has been reached
-	 * @throws IOException If an I/O error occurs
+	 * Resets the linetype to UNKNOWN.
 	 */
-	@Override
-	public String readLine() throws IOException {
-		String r = super.readLine();
-		setLineType(r);
-		if (lineType == LineType.ERROR && r != null && !r.matches("^![0-9A-Z]{5}!.+")) {
-			r = "!22000!" + r.substring(1);
-		}
-		return r;
+	public void resetLineType() {
+		lineType = LineType.UNKNOWN;
 	}
 
 	/**
-	 * Sets the linetype to the type of the string given.  If the string
-	 * is null, lineType is set to UNKNOWN.
-	 *
-	 * @param line the string to examine
+	 * Return the current line, or null if we're at the end or before the beginning.
+	 * @return the current line or null
 	 */
-	public void setLineType(final String line) {
-		lineType = LineType.classify(line);
+	public String getLine() {
+		return current;
 	}
 
 	/**
-	 * getLineType returns the type of the last line read.
+	 * getLineType returns the type of the current line.
 	 *
 	 * @return Linetype representing the kind of line this is, one of the
 	 *         following enums: UNKNOWN, HEADER, ERROR, RESULT,
@@ -117,35 +108,52 @@ public final class BufferedMCLReader ext
 	}
 
 	/**
-	 * Reads up till the MonetDB prompt, indicating the server is ready
-	 * for a new command.  All read data is discarded.  If the last line
-	 * read by readLine() was a prompt, this method will immediately return.
-	 *
-	 * If there are errors present in the lines that are read, then they
-	 * are put in one string and returned <b>after</b> the prompt has
-	 * been found. If no errors are present, null will be returned.
+	 * Discard the remainder of the response but collect any further error messages.
 	 *
 	 * @return a string containing error messages, or null if there aren't any
 	 * @throws IOException if an IO exception occurs while talking to the server
 	 *
 	 * TODO(Wouter): should probably not have to be synchronized.
 	 */
-	final public synchronized String waitForPrompt() throws IOException {
-		StringBuilder errmsgs = null;
-		String tmp;
+
+
+	final public synchronized String discardRemainder() throws IOException {
+		return discard(null);
+	}
+
+	final public synchronized String discardRemainder(String error) throws IOException {
+		final StringBuilder sb;
 
+		if (error != null) {
+			sb = makeErrorBuffer();
+			sb.append(error);
+		} else {
+			sb = null;
+		}
+		return discard(sb);
+	}
+
+	final synchronized String discard(StringBuilder errmsgs) throws IOException {
 		while (lineType != LineType.PROMPT) {
-			tmp = readLine();
-			if (tmp == null)
+			advance();
+			if (getLine() == null)
 				throw new IOException("Connection to server lost!");
-			if (lineType == LineType.ERROR) {
+			if (getLineType() == LineType.ERROR) {
 				if (errmsgs == null)
 					errmsgs = new StringBuilder(128);
-				errmsgs.append('\n').append(tmp.substring(1));
+				errmsgs.append('\n').append(getLine().substring(1));
 			}
 		}
 		if (errmsgs == null)
 			return null;
 		return errmsgs.toString().trim();
 	}
+
+	private final StringBuilder makeErrorBuffer() {
+		return new StringBuilder(128);
+	}
+
+	public void close() throws IOException {
+		inner.close();
+	}
 }
--- a/src/main/java/org/monetdb/mcl/io/BufferedMCLWriter.java
+++ b/src/main/java/org/monetdb/mcl/io/BufferedMCLWriter.java
@@ -98,6 +98,6 @@ public final class BufferedMCLWriter ext
 
 		// reset reader state, last line isn't valid any more now
 		if (reader != null)
-			reader.setLineType(null);
+			reader.resetLineType();
 	}
 }
--- a/src/main/java/org/monetdb/mcl/net/MapiSocket.java
+++ b/src/main/java/org/monetdb/mcl/net/MapiSocket.java
@@ -287,30 +287,30 @@ public class MapiSocket {	/* cannot (yet
 			}
 		}
 
-		final String c = reader.readLine();
-		reader.waitForPrompt();
+		reader.advance();
+		final String c = reader.getLine();
+		reader.discardRemainder();
 		writer.writeLine(getChallengeResponse(c, user, pass, language, database, hash));
 
 		// read monetdb mserver response till prompt
 		final ArrayList<String> redirects = new ArrayList<String>();
 		final List<String> warns = new ArrayList<String>();
 		String err = "", tmp;
-		LineType lineType;
 		do {
-			tmp = reader.readLine();
+			reader.advance();
+			tmp = reader.getLine();
 			if (tmp == null)
 				throw new IOException("Read from " +
 						con.getInetAddress().getHostName() + ":" +
 						con.getPort() + ": End of stream reached");
-			lineType = reader.getLineType();
-			if (lineType == LineType.ERROR) {
+			if (reader.getLineType() == LineType.ERROR) {
 				err += "\n" + tmp.substring(7);
-			} else if (lineType == LineType.INFO) {
+			} else if (reader.getLineType() == LineType.INFO) {
 				warns.add(tmp.substring(1));
-			} else if (lineType == LineType.REDIRECT) {
+			} else if (reader.getLineType() == LineType.REDIRECT) {
 				redirects.add(tmp.substring(1));
 			}
-		} while (lineType != LineType.PROMPT);
+		} while (reader.getLineType() != LineType.PROMPT);
 
 		if (err.length() > 0) {
 			close();
--- a/src/main/java/org/monetdb/merovingian/Control.java
+++ b/src/main/java/org/monetdb/merovingian/Control.java
@@ -218,9 +218,9 @@ public class Control {
 
 		mout.writeLine(database + " " + command + "\n");
 		ArrayList<String> l = new ArrayList<String>();
-		String tmpLine = min.readLine();
-		LineType linetype = min.getLineType();
-		switch (linetype) {
+		min.advance();
+		String tmpLine = min.getLine();
+		switch (min.getLineType()) {
 		case ERROR:
 			throw new MerovingianException(tmpLine.substring(6));
 		case RESULT:
@@ -231,18 +231,16 @@ public class Control {
 			throw new MerovingianException("unexpected line: " + tmpLine);
 		}
 
-		boolean hasPrompt = false;
-		while (!hasPrompt) {
-			tmpLine = min.readLine();
-			linetype = min.getLineType();
 
-			switch (linetype) {
-			case PROMPT:
-				hasPrompt = true;
-				break;
+		lineloop:
+		while (true) {
+			min.advance();
+			switch (min.getLineType()) {
+				case PROMPT:
+				break lineloop;
 			case RESULT:
 				l.add(tmpLine.substring(1));
-				break;
+				continue lineloop;
 			default:
 				throw new MerovingianException("unexpected line: " + tmpLine);
 			}
--- a/src/main/java/org/monetdb/util/SQLRestore.java
+++ b/src/main/java/org/monetdb/util/SQLRestore.java
@@ -51,7 +51,8 @@ public final class SQLRestore {
 		public void run() {
 			try {
 				while (true) {
-					final String line = _is.readLine();
+					_is.advance();
+					final String line = _is.getLine();
 					if (line == null)
 						break;
 					final LineType result = _is.getLineType();
--- a/tests/JDBC_API_Tester.java
+++ b/tests/JDBC_API_Tester.java
@@ -6172,7 +6172,7 @@ final public class JDBC_API_Tester {
 			org.monetdb.mcl.io.BufferedMCLReader mclIn = server.getReader();
 			org.monetdb.mcl.io.BufferedMCLWriter mclOut = server.getWriter();
 
-			String error = mclIn.waitForPrompt();
+			String error = mclIn.discardRemainder();
 			if (error != null)
 				sb.append("Received start error: ").append(error).append("\n");
 
@@ -6190,13 +6190,13 @@ final public class JDBC_API_Tester {
 			}
 			mclOut.writeLine(""); // need this one for synchronisation over flush()
 
-			error = mclIn.waitForPrompt();
+			error = mclIn.discardRemainder();
 			if (error != null)
 				sb.append("Received error: ").append(error).append("\n");
 
 			mclOut.writeLine(""); // need this one for synchronisation over flush()
 
-			error = mclIn.waitForPrompt();
+			error = mclIn.discardRemainder();
 			if (error != null)
 				sb.append("Received finish error: ").append(error).append("\n");
 
--- a/tests/SQLcopyinto.java
+++ b/tests/SQLcopyinto.java
@@ -7,7 +7,6 @@
  */
 
 import java.sql.*;
-import java.io.*;
 import java.util.*;
 import org.monetdb.mcl.net.MapiSocket;
 import org.monetdb.mcl.io.BufferedMCLReader;
@@ -102,7 +101,7 @@ public class SQLcopyinto {
 			BufferedMCLReader mclIn = server.getReader();
 			BufferedMCLWriter mclOut = server.getWriter();
 
-			String error = mclIn.waitForPrompt();
+			String error = mclIn.discardRemainder();
 			if (error != null)
 				System.err.println("Received start error: " + error);
 
@@ -120,12 +119,12 @@ public class SQLcopyinto {
 			}
 
 			mclOut.writeLine(""); // need this one for synchronisation over flush()
-			error = mclIn.waitForPrompt();
+			error = mclIn.discardRemainder();
 			if (error != null)
 				System.err.println("Received error: " + error);
 
 			mclOut.writeLine(""); // need this one for synchronisation over flush()
-			error = mclIn.waitForPrompt();
+			error = mclIn.discardRemainder();
 			if (error != null)
 				System.err.println("Received finish error: " + error);