view src/main/java/nl/cwi/monetdb/mcl/io/BufferedMCLReader.java @ 350:54137aeb1f92

Update Copyright year.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 02 Jan 2020 14:42:27 +0100 (2020-01-02)
parents bb273e9c7e09
children
line wrap: on
line source
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0.  If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 1997 - July 2008 CWI, August 2008 - 2020 MonetDB B.V.
 */

package nl.cwi.monetdb.mcl.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;

/**
 * Read text from a character-input stream, buffering characters so as
 * to provide a means for efficient reading of characters, arrays and
 * lines.  This class is based on the BufferedReader class, and provides
 * extra functionality useful for MCL.
 *
 * The BufferedMCLReader is typically used as layer inbetween an
 * InputStream and a specific interpreter of the data.
 * <pre>
 *                         / Response
 * BufferedMCLReader ---o &lt;- Tuple
 *                         \ DataBlock
 * </pre>
 * Because the BufferedMCLReader provides an efficient way to access the
 * data from the stream in a linewise fashion, whereby each line is
 * identified as a certain type, consumers can easily decide how to
 * parse each retrieved line.  The line parsers from
 * nl.cwi.monetdb.mcl.parser are well suited to work with the lines
 * outputted by the BufferedMCLReader.
 * This class is client-oriented, as it doesn't take into account the
 * messages as the server receives them.
 *
 * @author Fabian Groffen
 * @see nl.cwi.monetdb.mcl.net.MapiSocket
 * @see nl.cwi.monetdb.mcl.io.BufferedMCLWriter
 */
public final class BufferedMCLReader extends BufferedReader {
	/** "there is currently no line", or the the type is unknown is represented by UNKNOWN */
	public final static int UNKNOWN  = 0;
	/** a line starting with ! indicates ERROR */
	public final static int ERROR    = '!';
	/** a line starting with % indicates HEADER */
	public final static int HEADER   = '%';
	/** a line starting with [ indicates RESULT */
	public final static int RESULT   = '[';
	/** a line which matches the pattern of prompt1 is a PROMPT */
	public final static int PROMPT   = '.';
	/** a line which matches the pattern of prompt2 is a MORE */
	public final static int MORE     = ',';
	/** a line starting with &amp; indicates the start of a header block */
	public final static int SOHEADER = '&';
	/** a line starting with ^ indicates REDIRECT */
	public final static int REDIRECT = '^';
	/** a line starting with # indicates INFO */
	public final static int INFO     = '#';

	/** The type of the last line read */
	private int lineType = UNKNOWN;

	/**
	 * Create a buffering character-input stream that uses a
	 * default-sized input buffer.
	 *
	 * @param in A Reader
	 */
	public BufferedMCLReader(final Reader in) {
		super(in);
	}

	/**
	 * Create a buffering character-input stream that uses a
	 * default-sized input buffer, from an InputStream.
	 *
	 * @param in An InputStream
	 * @param enc Encoding
	 * @throws UnsupportedEncodingException If encoding is not supported
	 */
	public BufferedMCLReader(final InputStream in, final String enc)
		throws UnsupportedEncodingException
	{
		super(new java.io.InputStreamReader(in, enc));
	}

	/**
	 * 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
	 */
	@Override
	public String readLine() throws IOException {
		String r = super.readLine();
		setLineType(r);
		if (lineType == ERROR && r != null && !r.matches("^![0-9A-Z]{5}!.+")) {
			r = "!22000!" + r.substring(1);
		}
		return r;
	}
	
	/**
	 * 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
	 */
	public void setLineType(final String line) {
		if (line == null || line.isEmpty()) {
			lineType = UNKNOWN;
			return;
		}
		switch (line.charAt(0)) {
			case '.':
				lineType = PROMPT;
				break;
			case ',':
				lineType = MORE;
				break;
			case '[':	/* multi field result */
			case '=':	/* single value result */
				lineType = RESULT;
				break;
			case '%':
				lineType = HEADER;
				break;
			case '&':
				lineType = SOHEADER;
				break;
			case '#':
				lineType = INFO;
				break;
			case '!':
				lineType = ERROR;
				break;
			case '^':
				lineType = REDIRECT;
				break;
			default:
				lineType = UNKNOWN;
		}
	}

	/**
	 * getLineType returns the type of the last line read.
	 *
	 * @return an integer representing the kind of line this is, one of the
	 *         following constants: UNKNOWN, HEADER, ERROR, PROMPT, MORE,
	 *         RESULT, SOHEADER, REDIRECT, INFO
	 */
	public int getLineType() {
		return lineType;
	}

	/**
	 * 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.
	 *
	 * @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 {
		final StringBuilder ret = new StringBuilder(128);
		String tmp;

		while (lineType != PROMPT) {
			tmp = readLine();
			if (tmp == null)
				throw new IOException("Connection to server lost!");
			if (lineType == ERROR)
				ret.append('\n').append(tmp.substring(1));
		}
		return ret.length() == 0 ? null : ret.toString().trim();
	}
}