/**
* 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 info.globalcode.sql.dk.configuration.DatabaseDefinition;
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
* .batch
* ..database
* ...resultSet
* ....@query
* ....@parameters
* ....@columnsHeader
* ....row
* .....@columnValue
* ...updatesResult
* ....@query
* ....@parameters
* ....@updatedRowsCount
*/
protected enum State {
ROOT,
BATCH,
DATABASE,
RESULT_SET,
ROW,
UPDATES_RESULT
}
/**
* 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));
}
@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 writeStartResultSet() {
pushState(State.RESULT_SET, EnumSet.of(State.DATABASE));
currentRowCount = 0;
}
@Override
public void writeEndResultSet() {
popState(EnumSet.of(State.DATABASE));
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<? extends 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 && parameters != 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));
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.UPDATES_RESULT, 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 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;
}
}