diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java Mon Mar 04 20:15:24 2019 +0100 @@ -0,0 +1,254 @@ +/** + * 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 . + */ +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; + +/** + *
    + *
  1. ensures integrity – if methods are called in correct order and context
  2. + *
  3. provides default implmentations of methods that does not produce any output for given + * events
  4. + *
+ * + * @author Ing. František Kučera (frantovo.cz) + */ +public abstract class AbstractFormatter implements Formatter { + + private Stack 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 + * ...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)); + } + + @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)); + 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 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; + } +}