/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * 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.seasar.extension.sql.parser; import java.util.Stack; import org.seasar.extension.sql.EndCommentNotFoundRuntimeException; import org.seasar.extension.sql.IfConditionNotFoundRuntimeException; import org.seasar.extension.sql.Node; import org.seasar.extension.sql.SqlParser; import org.seasar.extension.sql.SqlTokenizer; import org.seasar.extension.sql.VariableSqlNotAllowedRuntimeException; import org.seasar.extension.sql.node.BeginNode; import org.seasar.extension.sql.node.BindVariableNode; import org.seasar.extension.sql.node.ContainerNode; import org.seasar.extension.sql.node.ElseNode; import org.seasar.extension.sql.node.EmbeddedValueNode; import org.seasar.extension.sql.node.IfNode; import org.seasar.extension.sql.node.ParenBindVariableNode; import org.seasar.extension.sql.node.PrefixSqlNode; import org.seasar.extension.sql.node.SqlNode; import org.seasar.framework.util.StringUtil; /** * {@link SqlParser}のための実装クラスです。 * * @author higa * */ public class SqlParserImpl implements SqlParser { private SqlTokenizer tokenizer; private Stack nodeStack = new Stack(); private boolean allowVariableSql = true; /** * {@link SqlParserImpl}を作成します。 * * @param sql */ public SqlParserImpl(String sql) { this(sql, true); } /** * {@link SqlParserImpl}を作成します。 * * @param sql * SQL * @param allowVariableSql * 可変なSQLを許可する場合は<code>true</code> */ public SqlParserImpl(String sql, boolean allowVariableSql) { sql = sql.trim(); if (sql.endsWith(";")) { sql = sql.substring(0, sql.length() - 1); } tokenizer = new SqlTokenizerImpl(sql); this.allowVariableSql = allowVariableSql; } public Node parse() { push(new ContainerNode()); while (SqlTokenizer.EOF != tokenizer.next()) { parseToken(); } return pop(); } /** * トークンを解析します。 */ protected void parseToken() { switch (tokenizer.getTokenType()) { case SqlTokenizer.SQL: parseSql(); break; case SqlTokenizer.COMMENT: parseComment(); break; case SqlTokenizer.ELSE: parseElse(); break; case SqlTokenizer.BIND_VARIABLE: parseBindVariable(); break; } } /** * SQLを解析します。 */ protected void parseSql() { String sql = tokenizer.getToken(); if (isElseMode()) { sql = StringUtil.replace(sql, "--", ""); } Node node = peek(); if ((node instanceof IfNode || node instanceof ElseNode) && node.getChildSize() == 0) { SqlTokenizer st = new SqlTokenizerImpl(sql); st.skipWhitespace(); String token = st.skipToken(); st.skipWhitespace(); if (sql.startsWith(",")) { if (sql.startsWith(", ")) { node.addChild(new PrefixSqlNode(", ", sql.substring(2))); } else { node.addChild(new PrefixSqlNode(",", sql.substring(1))); } } else if ("AND".equalsIgnoreCase(token) || "OR".equalsIgnoreCase(token)) { node.addChild(new PrefixSqlNode(st.getBefore(), st.getAfter())); } else { node.addChild(new SqlNode(sql)); } } else { node.addChild(new SqlNode(sql)); } } /** * コメントを解析します。 */ protected void parseComment() { String comment = tokenizer.getToken(); if (isTargetComment(comment)) { if (isIfComment(comment)) { if (!allowVariableSql) { throw new VariableSqlNotAllowedRuntimeException(tokenizer .getSql()); } parseIf(); } else if (isBeginComment(comment)) { parseBegin(); } else if (isEndComment(comment)) { return; } else { parseCommentBindVariable(); } } } /** * IFを解析します。 */ protected void parseIf() { String condition = tokenizer.getToken().substring(2).trim(); if (StringUtil.isEmpty(condition)) { throw new IfConditionNotFoundRuntimeException(); } IfNode ifNode = new IfNode(condition); peek().addChild(ifNode); push(ifNode); parseEnd(); } /** * BEGINを解析します。 */ protected void parseBegin() { BeginNode beginNode = new BeginNode(); peek().addChild(beginNode); push(beginNode); parseEnd(); } /** * ENDを解析します。 */ protected void parseEnd() { while (SqlTokenizer.EOF != tokenizer.next()) { if (tokenizer.getTokenType() == SqlTokenizer.COMMENT && isEndComment(tokenizer.getToken())) { pop(); return; } parseToken(); } throw new EndCommentNotFoundRuntimeException(tokenizer.getSql()); } /** * ELSEを解析します。 */ protected void parseElse() { Node parent = peek(); if (!(parent instanceof IfNode)) { return; } IfNode ifNode = (IfNode) pop(); ElseNode elseNode = new ElseNode(); ifNode.setElseNode(elseNode); push(elseNode); tokenizer.skipWhitespace(); } /** * バインド変数コメントを解析します。 */ protected void parseCommentBindVariable() { String expr = tokenizer.getToken(); String s = tokenizer.skipToken(); if (s.startsWith("(") && s.endsWith(")")) { peek().addChild(new ParenBindVariableNode(expr)); } else if (expr.startsWith("$")) { if (!allowVariableSql) { throw new VariableSqlNotAllowedRuntimeException(tokenizer .getSql()); } peek().addChild(new EmbeddedValueNode(expr.substring(1))); } else if (expr.equals("orderBy")) { peek().addChild(new EmbeddedValueNode(expr)); } else { peek().addChild(new BindVariableNode(expr)); } } /** * バインド変数を解析します。 */ protected void parseBindVariable() { String expr = tokenizer.getToken(); peek().addChild(new BindVariableNode(expr)); } /** * 一番上のノードを取り出します。 * * @return 一番上のノード */ protected Node pop() { return (Node) nodeStack.pop(); } /** * 一番上のノードを返します。 * * @return 一番上のノード */ protected Node peek() { return (Node) nodeStack.peek(); } /** * ノードを一番上に追加します。 * * @param node * ノード */ protected void push(Node node) { nodeStack.push(node); } /** * 可変なSQLを許可する場合は<code>true</code>を設定します。 * * @param allowVariableSql * 可変なSQLを許可する場合は<code>true</code> */ public void setAllowVariableSql(boolean allowVariableSql) { this.allowVariableSql = allowVariableSql; } /** * ELSEモードかどうかを返します。 * * @return ELSEモードかどうか */ protected boolean isElseMode() { for (int i = 0; i < nodeStack.size(); ++i) { if (nodeStack.get(i) instanceof ElseNode) { return true; } } return false; } /** * 対象とするコメントかどうかを返します。 * * @param comment * コメント * @return 対象とするコメントかどうか */ protected static boolean isTargetComment(String comment) { return comment != null && comment.length() > 0 && Character.isJavaIdentifierStart(comment.charAt(0)); } /** * IFコメントかどうかを返します。 * * @param comment * コメント * @return IFコメントかどうか */ protected static boolean isIfComment(String comment) { return comment.startsWith("IF"); } /** * BEGINコメントかどうかを返します。 * * @param content * コメント * @return BEGINコメントかどうか */ protected static boolean isBeginComment(String content) { return content != null && "BEGIN".equals(content); } /** * ENDコメントかどうかを返します。 * * @param content * コメント * @return ENDコメントかどうか */ protected static boolean isEndComment(String content) { return content != null && "END".equals(content); } }