java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java Mon Mar 04 20:15:24 2019 +0100
1.3 @@ -0,0 +1,254 @@
1.4 +/**
1.5 + * SQL-DK
1.6 + * Copyright © 2013 František Kučera (frantovo.cz)
1.7 + *
1.8 + * This program is free software: you can redistribute it and/or modify
1.9 + * it under the terms of the GNU General Public License as published by
1.10 + * the Free Software Foundation, either version 3 of the License, or
1.11 + * (at your option) any later version.
1.12 + *
1.13 + * This program is distributed in the hope that it will be useful,
1.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.16 + * GNU General Public License for more details.
1.17 + *
1.18 + * You should have received a copy of the GNU General Public License
1.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
1.20 + */
1.21 +package info.globalcode.sql.dk.formatting;
1.22 +
1.23 +import info.globalcode.sql.dk.Parameter;
1.24 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
1.25 +import java.util.EmptyStackException;
1.26 +import java.util.EnumSet;
1.27 +import java.util.List;
1.28 +import java.util.Stack;
1.29 +
1.30 +/**
1.31 + * <ol>
1.32 + * <li>ensures integrity – if methods are called in correct order and context</li>
1.33 + * <li>provides default implmentations of methods that does not produce any output for given
1.34 + * events</li>
1.35 + * </ol>
1.36 + *
1.37 + * @author Ing. František Kučera (frantovo.cz)
1.38 + */
1.39 +public abstract class AbstractFormatter implements Formatter {
1.40 +
1.41 + private Stack<State> state = new Stack<>();
1.42 + private FormatterContext formatterContext;
1.43 + private ColumnsHeader currentColumnsHeader;
1.44 + private String currentQuery;
1.45 + private int currentColumnsCount;
1.46 + private int currentRowCount;
1.47 +
1.48 + public AbstractFormatter(FormatterContext formatterContext) {
1.49 + this.formatterContext = formatterContext;
1.50 + state.push(State.ROOT);
1.51 + }
1.52 +
1.53 + /*
1.54 + * root
1.55 + * .batch
1.56 + * ..database
1.57 + * ...statement
1.58 + * ....@query
1.59 + * ....@parameters
1.60 + * ....resultSet
1.61 + * .....row
1.62 + * ......@columnValue
1.63 + * ....@updatesResult
1.64 + */
1.65 + protected enum State {
1.66 +
1.67 + ROOT,
1.68 + BATCH,
1.69 + DATABASE,
1.70 + STATEMENT,
1.71 + RESULT_SET,
1.72 + ROW
1.73 + }
1.74 +
1.75 + /**
1.76 + * Go down in hierarchy.
1.77 + * Pushes new state and verifies the old one.
1.78 + *
1.79 + * @param current the new state – currently entering
1.80 + * @param expected expected previous states (any of them is valid)
1.81 + * @return previous state
1.82 + * @throws IllegalStateException if previous state was not one from expected
1.83 + */
1.84 + private State pushState(State current, EnumSet expected) {
1.85 + State previous = state.peek();
1.86 +
1.87 + if (expected.contains(previous)) {
1.88 + state.push(current);
1.89 + return previous;
1.90 + } else {
1.91 + throw new IllegalStateException("Formatter was in wrong state: " + previous + " when it should be in one of: " + expected);
1.92 + }
1.93 + }
1.94 +
1.95 + protected State peekState(EnumSet expected) {
1.96 + State current = state.peek();
1.97 +
1.98 + if (expected.contains(current)) {
1.99 + return current;
1.100 + } else {
1.101 + throw new IllegalStateException("Formatter is in wrong state: " + current + " when it should be in one of: " + expected);
1.102 + }
1.103 +
1.104 + }
1.105 +
1.106 + /**
1.107 + * Go up in hierarchy.
1.108 + * Pops the superior state/branch.
1.109 + *
1.110 + * @param expected expected superior state
1.111 + * @return the superior state
1.112 + * @throws IllegalStateException if superior state was not one from expected or if there is no
1.113 + * more superior state (we are at root level)
1.114 + */
1.115 + private State popState(EnumSet expected) {
1.116 + try {
1.117 + state.pop();
1.118 + State superior = state.peek();
1.119 + if (expected.contains(superior)) {
1.120 + return superior;
1.121 + } else {
1.122 + throw new IllegalStateException("Formatter had wrong superior state: " + superior + " when it should be in one of: " + expected);
1.123 + }
1.124 + } catch (EmptyStackException e) {
1.125 + throw new IllegalStateException("Formatter was already at root level – there is nothing above that.", e);
1.126 + }
1.127 + }
1.128 +
1.129 + @Override
1.130 + public void writeStartBatch() {
1.131 + pushState(State.BATCH, EnumSet.of(State.ROOT));
1.132 + }
1.133 +
1.134 + @Override
1.135 + public void writeEndBatch() {
1.136 + popState(EnumSet.of(State.ROOT));
1.137 + }
1.138 +
1.139 + @Override
1.140 + public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
1.141 + pushState(State.DATABASE, EnumSet.of(State.BATCH));
1.142 + }
1.143 +
1.144 + @Override
1.145 + public void writeEndDatabase() {
1.146 + popState(EnumSet.of(State.BATCH));
1.147 + }
1.148 +
1.149 + @Override
1.150 + public void writeStartStatement() {
1.151 + pushState(State.STATEMENT, EnumSet.of(State.DATABASE));
1.152 + }
1.153 +
1.154 + @Override
1.155 + public void writeEndStatement() {
1.156 + popState(EnumSet.of(State.DATABASE));
1.157 + }
1.158 +
1.159 + @Override
1.160 + public void writeStartResultSet(ColumnsHeader header) {
1.161 + pushState(State.RESULT_SET, EnumSet.of(State.STATEMENT));
1.162 + currentRowCount = 0;
1.163 + currentColumnsHeader = header;
1.164 + }
1.165 +
1.166 + @Override
1.167 + public void writeEndResultSet() {
1.168 + popState(EnumSet.of(State.STATEMENT));
1.169 + currentColumnsHeader = null;
1.170 + }
1.171 +
1.172 + @Override
1.173 + public void writeQuery(String sql) {
1.174 + peekState(EnumSet.of(State.STATEMENT));
1.175 +
1.176 + if (currentColumnsHeader == null) {
1.177 + currentQuery = sql;
1.178 + } else {
1.179 + throw new IllegalStateException("Query string '" + sql + "' must be set before columns header – was already set: " + currentColumnsHeader);
1.180 + }
1.181 + }
1.182 +
1.183 + @Override
1.184 + public void writeParameters(List<? extends Parameter> parameters) {
1.185 + peekState(EnumSet.of(State.STATEMENT));
1.186 +
1.187 + if (currentColumnsHeader != null) {
1.188 + throw new IllegalStateException("Parameters '" + parameters + "' must be set before columns header – was already set: " + currentColumnsHeader);
1.189 + }
1.190 +
1.191 + if (currentQuery == null && parameters != null) {
1.192 + throw new IllegalStateException("Parameters '" + parameters + "' must be set after query – was not yet set.");
1.193 + }
1.194 + }
1.195 +
1.196 + @Override
1.197 + public void writeStartRow() {
1.198 + pushState(State.ROW, EnumSet.of(State.RESULT_SET));
1.199 + currentColumnsCount = 0;
1.200 + currentRowCount++;
1.201 + }
1.202 +
1.203 + @Override
1.204 + public void writeEndRow() {
1.205 + popState(EnumSet.of(State.RESULT_SET));
1.206 + }
1.207 +
1.208 + @Override
1.209 + public void writeColumnValue(Object value) {
1.210 + peekState(EnumSet.of(State.ROW));
1.211 + currentColumnsCount++;
1.212 +
1.213 + int declaredCount = currentColumnsHeader.getColumnCount();
1.214 + if (currentColumnsCount > declaredCount) {
1.215 + throw new IllegalStateException("Current columns count is " + currentColumnsCount + " which is more than declared " + declaredCount + " in header.");
1.216 + }
1.217 + }
1.218 +
1.219 + @Override
1.220 + public void writeUpdatesResult(int updatedRowsCount) {
1.221 + peekState(EnumSet.of(State.STATEMENT));
1.222 + }
1.223 +
1.224 + @Override
1.225 + public void close() throws FormatterException {
1.226 + }
1.227 +
1.228 + public FormatterContext getFormatterContext() {
1.229 + return formatterContext;
1.230 + }
1.231 +
1.232 + protected ColumnsHeader getCurrentColumnsHeader() {
1.233 + return currentColumnsHeader;
1.234 + }
1.235 +
1.236 + /**
1.237 + * @return column number, 1 = first
1.238 + */
1.239 + protected int getCurrentColumnsCount() {
1.240 + return currentColumnsCount;
1.241 + }
1.242 +
1.243 + protected boolean isCurrentColumnFirst() {
1.244 + return currentColumnsCount == 1;
1.245 + }
1.246 +
1.247 + protected boolean isCurrentColumnLast() {
1.248 + return currentColumnsCount == currentColumnsHeader.getColumnCount();
1.249 + }
1.250 +
1.251 + /**
1.252 + * @return row number, 1 = first
1.253 + */
1.254 + protected int getCurrentRowCount() {
1.255 + return currentRowCount;
1.256 + }
1.257 +}