view src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java @ 937:d416e9b6b3d0

Update Copyright year.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 02 Jan 2025 13:27:58 +0100 (3 months ago)
parents 5a59910e8f87
children
line wrap: on
line source
/*
 * SPDX-License-Identifier: MPL-2.0
 *
 * 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 2024, 2025 MonetDB Foundation;
 * Copyright August 2008 - 2023 MonetDB B.V.;
 * Copyright 1997 - July 2008 CWI.
 */

package org.monetdb.mcl.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;

/**
 * Helper class to read and classify the lines of a query response.
 *
 * This class wraps and buffers the Reader on which the responses come in.
 * Use {@link #getLine()} to get the current line, {@link #getLineType()}
 * to get its {@link LineType} and {@link #advance()} to proceed to the
 * next line.
 *
 * Initially, the line type is set to LineType.UNKNOWN and the line
 * is set to null. When the end of the result set has been reached the line type
 * will remain LineType.PROMPT and the line will again be null.
 *
 * To start reading the next response, call {@link #resetLineType()}. This
 * is usually done automatically by the accompanying {@link BufferedMCLWriter}
 * whenever a new request is sent to the server.
 *
 * @author Fabian Groffen
 * @see org.monetdb.mcl.net.MapiSocket
 * @see org.monetdb.mcl.io.BufferedMCLWriter
 */
public final class BufferedMCLReader {

	private final BufferedReader inner;
	private String current = null;
	private LineType lineType = LineType.UNKNOWN;

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

	/**
	 * Create a buffering character-input stream that uses a
	 * default-sized input buffer, from an InputStream.
	 *
	 * @param in An InputStream
	 * @param cs A Charset
	 */
	public BufferedMCLReader(final InputStream in, final Charset cs) {
		this(new java.io.InputStreamReader(in, cs));
	}

	/**
	 * Proceed to the next line of the response.
	 * 
	 * Set line type to PROMPT and line to null if the end of the response
	 * has been reached.
	 * @throws IOException if exception occurred during reading
	 */
	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);
		}
	}

	/**
	 * Reset the linetype to UNKNOWN.
	 */
	public void resetLineType() {
		lineType = LineType.UNKNOWN;
	}

	/**
	 * Return the current line, or null if we're at the end or before the beginning.
	 * @return the current line or null
	 */
	public String getLine() {
		return current;
	}

	/**
	 * Return a substring of the current line, or null if we're at the end or before the beginning.
	 *
	 * @param start beginIndex
	 * @return the current line or null
	 */
	public String getLine(int start) {
		String line = getLine();
		if (line != null)
			line = line.substring(start);
		return line;
	}

	/**
	 * 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,
	 *         PROMPT, MORE, FILETRANSFER, SOHEADER, REDIRECT, INFO
	 */
	public LineType getLineType() {
		return lineType;
	}

	/**
	 * 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
	 */
	final public String discardRemainder() throws IOException {
		return discard(null);
	}

	/**
	 * Discard the remainder of the response but collect any further error messages.
	 *
	 * @param error A string containing earlier error messages to include in the error report.
	 * @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
	 */
	final public String discardRemainder(String error) throws IOException {
		final StringBuilder sb;

		if (error != null) {
			sb = makeErrorBuffer();
			sb.append(error);
		} else {
			sb = null;
		}
		return discard(sb);
	}

	/**
	 * Discard the remainder of the response but collect any further error messages.
	 *
	 * @param errmsgs An optional StringBuilder object containing earlier error messages to include in the error report.
	 * @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 synchronized String discard(StringBuilder errmsgs) throws IOException {
		while (lineType != LineType.PROMPT) {
			advance();
			if (getLine() == null)
				throw new IOException("Connection to server lost!");
			if (getLineType() == LineType.ERROR) {
				if (errmsgs == null)
					errmsgs = new StringBuilder(128);
				errmsgs.append('\n').append(getLine().substring(1));
			}
		}
		if (errmsgs == null)
			return null;
		return errmsgs.toString().trim();
	}

	private final StringBuilder makeErrorBuffer() {
		return new StringBuilder(128);
	}

	/**
	 * Close the wrapped Reader.
	 * @throws IOException if an IO exception occurs while talking to the server
	 */
	public void close() throws IOException {
		inner.close();
	}
}