java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java
author František Kučera <franta-hg@frantovo.cz>
Thu, 24 Oct 2019 21:43:08 +0200
branchv_0
changeset 250 aae5009bd0af
parent 248 7f81cfa150d0
permissions -rw-r--r--
fix license version: GNU GPLv3

/**
 * SQL-DK
 * Copyright © 2013 František Kučera (frantovo.cz)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package info.globalcode.sql.dk.formatting;

import info.globalcode.sql.dk.Parameter;
import info.globalcode.sql.dk.configuration.DatabaseDefinition;
import java.util.EmptyStackException;
import java.util.EnumSet;
import java.util.List;
import java.util.Stack;

/**
 * <ol>
 * <li>ensures integrity – if methods are called in correct order and context</li>
 * <li>provides default implmentations of methods that does not produce any output for given
 * events</li>
 * </ol>
 *
 * @author Ing. František Kučera (frantovo.cz)
 */
public abstract class AbstractFormatter implements Formatter {

	private Stack<State> state = new Stack<>();
	private FormatterContext formatterContext;
	private ColumnsHeader currentColumnsHeader;
	private String currentQuery;
	private int currentColumnsCount;
	private int currentRowCount;
	private int resultSetCount;

	public AbstractFormatter(FormatterContext formatterContext) {
		this.formatterContext = formatterContext;
		state.push(State.ROOT);
	}

	protected String getCurrentRelationName() {
		if (getFormatterContext().getRelationNames() == null || getFormatterContext().getRelationNames().size() < resultSetCount) {
			return "r" + resultSetCount;
		} else {
			return getFormatterContext().getRelationNames().get(resultSetCount - 1);
		}
	}

	/*
	 * root
	 * .batch
	 * ..database
	 * ...statement
	 * ....@query
	 * ....@parameters
	 * ....resultSet
	 * .....row
	 * ......@columnValue
	 * ....@updatesResult
	 */
	protected enum State {

		ROOT,
		BATCH,
		DATABASE,
		STATEMENT,
		RESULT_SET,
		ROW
	}

	/**
	 * Go down in hierarchy.
	 * Pushes new state and verifies the old one.
	 *
	 * @param current the new state – currently entering
	 * @param expected expected previous states (any of them is valid)
	 * @return previous state
	 * @throws IllegalStateException if previous state was not one from expected
	 */
	private State pushState(State current, EnumSet expected) {
		State previous = state.peek();

		if (expected.contains(previous)) {
			state.push(current);
			return previous;
		} else {
			throw new IllegalStateException("Formatter was in wrong state: " + previous + " when it should be in one of: " + expected);
		}
	}

	protected State peekState(EnumSet expected) {
		State current = state.peek();

		if (expected.contains(current)) {
			return current;
		} else {
			throw new IllegalStateException("Formatter is in wrong state: " + current + " when it should be in one of: " + expected);
		}

	}

	/**
	 * Go up in hierarchy.
	 * Pops the superior state/branch.
	 *
	 * @param expected expected superior state
	 * @return the superior state
	 * @throws IllegalStateException if superior state was not one from expected or if there is no
	 * more superior state (we are at root level)
	 */
	private State popState(EnumSet expected) {
		try {
			state.pop();
			State superior = state.peek();
			if (expected.contains(superior)) {
				return superior;
			} else {
				throw new IllegalStateException("Formatter had wrong superior state: " + superior + " when it should be in one of: " + expected);
			}
		} catch (EmptyStackException e) {
			throw new IllegalStateException("Formatter was already at root level – there is nothing above that.", e);
		}
	}

	@Override
	public void writeStartBatch() {
		pushState(State.BATCH, EnumSet.of(State.ROOT));
		resultSetCount = 0;
	}

	@Override
	public void writeEndBatch() {
		popState(EnumSet.of(State.ROOT));
	}

	@Override
	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
		pushState(State.DATABASE, EnumSet.of(State.BATCH));
	}

	@Override
	public void writeEndDatabase() {
		popState(EnumSet.of(State.BATCH));
	}

	@Override
	public void writeStartStatement() {
		pushState(State.STATEMENT, EnumSet.of(State.DATABASE));
	}

	@Override
	public void writeEndStatement() {
		popState(EnumSet.of(State.DATABASE));
	}

	@Override
	public void writeStartResultSet(ColumnsHeader header) {
		pushState(State.RESULT_SET, EnumSet.of(State.STATEMENT));
		resultSetCount++;
		currentRowCount = 0;
		currentColumnsHeader = header;
	}

	@Override
	public void writeEndResultSet() {
		popState(EnumSet.of(State.STATEMENT));
		currentColumnsHeader = null;
	}

	@Override
	public void writeQuery(String sql) {
		peekState(EnumSet.of(State.STATEMENT));

		if (currentColumnsHeader == null) {
			currentQuery = sql;
		} else {
			throw new IllegalStateException("Query string '" + sql + "' must be set before columns header – was already set: " + currentColumnsHeader);
		}
	}

	@Override
	public void writeParameters(List<? extends Parameter> parameters) {
		peekState(EnumSet.of(State.STATEMENT));

		if (currentColumnsHeader != null) {
			throw new IllegalStateException("Parameters '" + parameters + "' must be set before columns header – was already set: " + currentColumnsHeader);
		}

		if (currentQuery == null && parameters != null) {
			throw new IllegalStateException("Parameters '" + parameters + "' must be set after query – was not yet set.");
		}
	}

	@Override
	public void writeStartRow() {
		pushState(State.ROW, EnumSet.of(State.RESULT_SET));
		currentColumnsCount = 0;
		currentRowCount++;
	}

	@Override
	public void writeEndRow() {
		popState(EnumSet.of(State.RESULT_SET));
	}

	@Override
	public void writeColumnValue(Object value) {
		peekState(EnumSet.of(State.ROW));
		currentColumnsCount++;

		int declaredCount = currentColumnsHeader.getColumnCount();
		if (currentColumnsCount > declaredCount) {
			throw new IllegalStateException("Current columns count is " + currentColumnsCount + " which is more than declared " + declaredCount + " in header.");
		}
	}

	@Override
	public void writeUpdatesResult(int updatedRowsCount) {
		peekState(EnumSet.of(State.STATEMENT));
	}

	@Override
	public void close() throws FormatterException {
	}

	public FormatterContext getFormatterContext() {
		return formatterContext;
	}

	protected ColumnsHeader getCurrentColumnsHeader() {
		return currentColumnsHeader;
	}

	/**
	 * @return column number, 1 = first
	 */
	protected int getCurrentColumnsCount() {
		return currentColumnsCount;
	}

	protected boolean isCurrentColumnFirst() {
		return currentColumnsCount == 1;
	}

	protected boolean isCurrentColumnLast() {
		return currentColumnsCount == currentColumnsHeader.getColumnCount();
	}

	/**
	 * @return row number, 1 = first
	 */
	protected int getCurrentRowCount() {
		return currentRowCount;
	}
}