org.dbflute.twowaysql.SqlTokenizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dbflute-runtime Show documentation
Show all versions of dbflute-runtime Show documentation
The runtime library of DBFlute
/*
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.dbflute.twowaysql;
import org.dbflute.exception.CommentTerminatorNotFoundException;
import org.dbflute.helper.message.ExceptionMessageBuilder;
/**
* @author modified by jflute (originated in S2Dao)
*/
public class SqlTokenizer {
// ===================================================================================
// Definition
// ==========
public static final int SQL = 1;
public static final int COMMENT = 2;
public static final int ELSE = 3;
public static final int BIND_VARIABLE = 4;
public static final int EOF = 99;
// ===================================================================================
// Attribute
// =========
protected final String _sql;
protected int _position = 0;
protected String _token;
protected int _tokenType = SQL;
protected int _nextTokenType = SQL;
protected int _bindVariableNum = 0;
protected boolean _overlookNativeBinding; // treats native binding as plain question mark on SQL
// ===================================================================================
// Constructor
// ===========
public SqlTokenizer(String sql) {
this._sql = sql;
}
public SqlTokenizer overlookNativeBinding() { // unused on DBFlute, for general purpose
_overlookNativeBinding = true;
return this;
}
// ===================================================================================
// Next
// ====
public int next() {
if (_position >= _sql.length()) {
_token = null;
_tokenType = EOF;
_nextTokenType = EOF;
return _tokenType;
}
switch (_nextTokenType) {
case SQL:
parseSql();
break;
case COMMENT:
parseComment();
break;
case ELSE:
parseElse();
break;
case BIND_VARIABLE:
parseBindVariable();
break;
default:
parseEof();
break;
}
return _tokenType;
}
// ===================================================================================
// Parse SQL
// =========
protected void parseSql() {
int commentStartPos = _sql.indexOf("/*", _position);
int commentStartPos2 = _sql.indexOf("#*", _position);
if (0 < commentStartPos2 && commentStartPos2 < commentStartPos) {
commentStartPos = commentStartPos2;
}
// native binding is unused on DBFlute, check it later or overlook
final int bindVariableStartPos = !_overlookNativeBinding ? _sql.indexOf("?", _position) : -1;
int elseCommentStartPos = -1;
int elseCommentLength = -1;
int elseCommentSearchCurrentPosition = _position;
while (true) { // searching nearest next ELSE comment
final int lineCommentStartPos = _sql.indexOf("--", elseCommentSearchCurrentPosition);
if (lineCommentStartPos < 0) {
break;
}
if (calculateNextStartPos(commentStartPos, bindVariableStartPos, -1) < lineCommentStartPos) {
break;
}
int skipPos = skipWhitespace(lineCommentStartPos + 2);
if (skipPos + 4 < _sql.length() && "ELSE".equals(_sql.substring(skipPos, skipPos + 4))) {
elseCommentStartPos = lineCommentStartPos;
elseCommentLength = skipPos + 4 - lineCommentStartPos;
break;
}
elseCommentSearchCurrentPosition = skipPos;
}
int nextStartPos = calculateNextStartPos(commentStartPos, bindVariableStartPos, elseCommentStartPos);
if (nextStartPos < 0) {
_token = _sql.substring(_position);
_nextTokenType = EOF;
_position = _sql.length();
_tokenType = SQL;
} else { // e.g. next is /**/ or native binding or else comment
_token = _sql.substring(_position, nextStartPos);
_tokenType = SQL;
boolean needNext = nextStartPos == _position;
if (nextStartPos == commentStartPos) {
_nextTokenType = COMMENT;
_position = commentStartPos + 2;
} else if (nextStartPos == bindVariableStartPos) {
_nextTokenType = BIND_VARIABLE;
_position = bindVariableStartPos;
} else if (nextStartPos == elseCommentStartPos) {
_nextTokenType = ELSE;
_position = elseCommentStartPos + elseCommentLength;
}
if (needNext) {
next();
}
}
}
protected int calculateNextStartPos(int commentStartPos, int bindVariableStartPos, int elseCommentStartPos) {
int nextStartPos = -1;
if (commentStartPos >= 0) {
nextStartPos = commentStartPos;
}
if (bindVariableStartPos >= 0 && (nextStartPos < 0 || bindVariableStartPos < nextStartPos)) {
nextStartPos = bindVariableStartPos;
}
if (elseCommentStartPos >= 0 && (nextStartPos < 0 || elseCommentStartPos < nextStartPos)) {
nextStartPos = elseCommentStartPos;
}
return nextStartPos;
}
// ===================================================================================
// Parse Comment
// =============
protected void parseComment() {
int commentEndPos = _sql.indexOf("*/", _position);
int commentEndPos2 = _sql.indexOf("*#", _position);
if (0 < commentEndPos2 && commentEndPos2 < commentEndPos) {
commentEndPos = commentEndPos2;
}
if (commentEndPos < 0) {
throwCommentTerminatorNotFoundException(_sql.substring(_position));
}
_token = _sql.substring(_position, commentEndPos);
_nextTokenType = SQL;
_position = commentEndPos + 2;
_tokenType = COMMENT;
}
protected void throwCommentTerminatorNotFoundException(String expression) {
final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
br.addNotice("The comment end was NOT found!");
br.addItem("Advice");
br.addElement("Please confirm the SQL comment writing.");
br.addElement("Any comments DOESN'T have a comment end.");
br.addElement("For example:");
br.addElement(" (x) -- /*pmb.xxxId3");
br.addElement(" (o) -- /*pmb.xxxId*/3");
br.addItem("Specified SQL");
br.addElement(expression);
br.addItem("Comment Expression");
br.addElement(_sql);
final String msg = br.buildExceptionMessage();
throw new CommentTerminatorNotFoundException(msg);
}
// ===================================================================================
// Parse Parameter
// ===============
protected void parseBindVariable() {
_token = nextBindVariableName();
_nextTokenType = SQL;
_position += 1;
_tokenType = BIND_VARIABLE;
}
protected String nextBindVariableName() {
return "$" + ++_bindVariableNum;
}
protected void parseElse() {
_token = null;
_nextTokenType = SQL;
_tokenType = ELSE;
}
protected void parseEof() {
_token = null;
_tokenType = EOF;
_nextTokenType = EOF;
}
// ===================================================================================
// Skip Character
// ==============
public String skipToken() {
return skipToken(false);
}
public String skipToken(boolean testValue) {
int index = _sql.length(); // last index as default
final String dateLiteralPrefix = extractDateLiteralPrefix(testValue, _sql, _position);
if (dateLiteralPrefix != null) {
_position = _position + dateLiteralPrefix.length();
}
final char quote;
{
final char firstChar = (_position < _sql.length() ? _sql.charAt(_position) : '\0');
quote = (firstChar == '(' ? ')' : firstChar);
}
final boolean quoting = quote == '\'' || quote == ')';
for (int i = quoting ? _position + 1 : _position; i < _sql.length(); ++i) {
final char c = _sql.charAt(i);
if (isNotQuoteEndPoint(quoting, c)) {
index = i;
break;
} else if (isBlockCommentBeginPoint(_sql, c, i)) {
index = i;
break;
} else if (isLineCommentBeginPoint(_sql, c, i)) {
index = i;
break;
} else if (quoting && isSingleQuoteEndPoint(_sql, quote, c, i)) {
index = i + 1;
break;
} else if (quoting && isQuoteEndPoint(_sql, quote, c, i)) {
index = i + 1;
break;
}
}
_token = _sql.substring(_position, index);
if (dateLiteralPrefix != null) {
_token = dateLiteralPrefix + _token;
}
_tokenType = SQL;
_nextTokenType = SQL;
_position = index;
return _token;
}
protected String extractDateLiteralPrefix(boolean testValue, String currentSql, int position) {
if (!testValue) {
return null;
}
if (position >= currentSql.length()) {
return null;
}
final char firstChar = currentSql.charAt(position);
if (firstChar != 'd' && firstChar != 'D' && firstChar != 't' && firstChar != 'T') {
return null;
}
final String rear;
{
final String tmpRear = currentSql.substring(position);
final int maxlength = "timestamp '".length();
if (tmpRear.length() > maxlength) {
// get only the quantity needed for performance
rear = tmpRear.substring(0, maxlength);
} else {
rear = tmpRear;
}
}
final String lowerRear = rear.toLowerCase();
String literalPrefix = null;
if (lowerRear.startsWith("date '")) {
literalPrefix = rear.substring(0, "date ".length());
} else if (lowerRear.startsWith("date'")) {
literalPrefix = rear.substring(0, "date".length());
} else if (lowerRear.startsWith("timestamp '")) { // has max length
literalPrefix = rear.substring(0, "timestamp ".length());
} else if (lowerRear.startsWith("timestamp'")) {
literalPrefix = rear.substring(0, "timestamp".length());
}
return literalPrefix;
}
protected boolean isNotQuoteEndPoint(boolean quoting, char c) {
return !quoting && (Character.isWhitespace(c) || c == ',' || c == ')' || c == '(');
}
protected boolean isBlockCommentBeginPoint(String currentSql, char c, int i) {
return c == '/' && isNextCharacter(currentSql, i, '*');
}
protected boolean isLineCommentBeginPoint(String currentSql, char c, int i) {
return c == '-' && isNextCharacter(currentSql, i, '-');
}
protected boolean isSingleQuoteEndPoint(String currentSql, char quote, char c, int i) {
final int sqlLen = currentSql.length();
final boolean endSqlOrNotEscapeQuote = (i + 1 >= sqlLen || currentSql.charAt(i + 1) != '\'');
return quote == '\'' && c == '\'' && endSqlOrNotEscapeQuote;
}
protected boolean isQuoteEndPoint(String currentSql, char quote, char c, int i) {
return c == quote;
}
protected boolean isNextCharacter(String currentSql, int i, char targetChar) {
return i + 1 < currentSql.length() && currentSql.charAt(i + 1) == targetChar;
}
public String skipWhitespace() {
int index = skipWhitespace(_position);
_token = _sql.substring(_position, index);
_position = index;
return _token;
}
protected int skipWhitespace(int position) {
int index = _sql.length();
for (int i = position; i < _sql.length(); ++i) {
char c = _sql.charAt(i);
if (!Character.isWhitespace(c)) {
index = i;
break;
}
}
return index;
}
// ===================================================================================
// Accessor
// ========
public int getPosition() {
return _position;
}
public String getToken() {
return _token;
}
public String getBefore() {
return _sql.substring(0, _position);
}
public String getAfter() {
return _sql.substring(_position);
}
public int getTokenType() {
return _tokenType;
}
public int getNextTokenType() {
return _nextTokenType;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy