java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java
author František Kučera <franta-hg@frantovo.cz>
Fri, 20 Dec 2013 23:50:21 +0100
branchv_0
changeset 25 4c118af3e855
parent 24 65e3fffae091
child 29 d66858b4b563
permissions -rw-r--r--
formatter: currentRowCount

/**
 * 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 java.util.EmptyStackException;
import java.util.EnumSet;
import java.util.List;
import java.util.Stack;

/**
 *
 * @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;

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

	/*
	 * root
	 * .database
	 * ..resultSet
	 * ...@query
	 * ...@parameters
	 * ...@columnsHeader
	 * ...row
	 * ....@columnValue
	 * ..updatesResult
	 * ...@query
	 * ...@parameters
	 * ...@updatedRowsCount
	 * ...generatedKeys
	 * ....resultSet (see above)
	 */
	protected enum State {

		ROOT,
		DATABASE,
		RESULT_SET,
		ROW,
		UPDATES_RESULT,
		GENERATED_KEYS
	}

	/**
	 * 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 superior = state.pop();
			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 writeStartDatabase() {
		pushState(State.DATABASE, EnumSet.of(State.ROOT));
	}

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

	@Override
	public void writeStartResultSet() {
		pushState(State.RESULT_SET, EnumSet.of(State.DATABASE, State.GENERATED_KEYS));
		currentRowCount = 0;
	}

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

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

		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<Parameter> parameters) {
		peekState(EnumSet.of(State.RESULT_SET, State.UPDATES_RESULT));

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

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

	@Override
	public void writeColumnsHeader(ColumnsHeader header) {
		peekState(EnumSet.of(State.RESULT_SET, State.UPDATES_RESULT));

		if (currentColumnsHeader == null) {
			currentColumnsHeader = header;
		} else {
			throw new IllegalStateException("Columns header can be set only once per result set – was already set: " + currentColumnsHeader);
		}
	}

	@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 writeStartUpdatesResult() {
		pushState(State.RESULT_SET, EnumSet.of(State.DATABASE));
	}

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

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

	@Override
	public void writeStartGeneratedKeys() {
		pushState(State.GENERATED_KEYS, EnumSet.of(State.UPDATES_RESULT));
	}

	@Override
	public void writeEndGeneratedKeys() {
		popState(EnumSet.of(State.UPDATES_RESULT));
	}

	public FormatterContext getFormatterContext() {
		return formatterContext;
	}

	protected ColumnsHeader getCurrentColumnsHeader() {
		return currentColumnsHeader;
	}

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

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