jp.co.tis.gsp.tools.dba.util.SqlSplitter.java Source code

Java tutorial

Introduction

Here is the source code for jp.co.tis.gsp.tools.dba.util.SqlSplitter.java

Source

/*
 * Copyright (C) 2015 coastland
 *
 * 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 jp.co.tis.gsp.tools.dba.util;

import org.apache.commons.lang.StringUtils;

public final class SqlSplitter {
    private SqlSplitter() {
        // hide utility class constructor
    }

    /**
     * Value indicating the sql has no end-delimiter like i.e. the semicolon.
     */
    public static final int NO_END = -1;

    /**
     * parsed sql started a single quote static text which continues on the next line (did not end)
     */
    public static final int OVERFLOW_SINGLE_QUOTE = -2;

    /**
     * parsed sql started a double quote static text which continues on the next line (did not end)
     */
    public static final int OVERFLOW_DOUBLE_QUOTE = -4;

    /**
     * parsed sql started a comment with /_* which continues on the next line (did not end)
     */
    public static final int OVERFLOW_COMMENT = -8;

    /**
     * Check if the given sql line contains a delimiter representing the end of the command.
     * Please note that we do <em>not</em> fully parse the SQL,
     * so if we get a malformed statement, we cannot detect it.
     *
     * @param line to parse
     * @param delimiter which should be used to split SQL commands
     * @param overflowValue 0=none, {@link SqlSplitter#OVERFLOW_COMMENT},
     *        {@link SqlSplitter#OVERFLOW_SINGLE_QUOTE} or
     *        {@link SqlSplitter#OVERFLOW_DOUBLE_QUOTE}
     * @return position after the end character if the given line contains the end of a SQL script,
     *         {@link SqlSplitter#NO_END} if it doesn't contain an end char. {@link SqlSplitter#OVERFLOW_SINGLE_QUOTE}
     *         will be returned if a single quote didn't get closed, {@link SqlSplitter#OVERFLOW_DOUBLE_QUOTE} likewise
     *         for not closed double quotes.
     */
    public static int containsSqlEnd(String line, String delimiter, final int overflowValue) {
        int ret = overflowValue >= 0 ? NO_END : overflowValue;

        // / * * / comments
        boolean isComment = (overflowValue == OVERFLOW_COMMENT);

        String quoteChar = null;
        if (overflowValue == OVERFLOW_SINGLE_QUOTE) {
            quoteChar = "'";
        } else if (overflowValue == OVERFLOW_DOUBLE_QUOTE) {
            quoteChar = "\"";
        }

        boolean isAlphaDelimiter = StringUtils.isAlpha(delimiter);

        if (line == null || line.length() == 0) {
            return ret;
        }

        int pos = 0;
        int maxpos = line.length() - 1;

        char c1;
        char c2 = line.charAt(0);
        statement: do {
            if (isComment) {
                do {
                    // keep c2 in line
                    if (pos < maxpos) {
                        c2 = line.charAt(pos + 1);
                    }

                    if (startsWith(line, '*', pos) && startsWith(line, '/', pos + 1)) {
                        ret = NO_END;
                        isComment = false;

                        continue statement;
                    }
                } while (pos++ < maxpos);

                //reached EOL
                break statement;
            }

            // if in quote-mode, search for end quote, respecting escaped characters
            if (quoteChar != null) {
                String doubleQuote = quoteChar + quoteChar;
                do {
                    // keep c2 in line
                    if (pos < maxpos) {
                        c2 = line.charAt(pos + 1);
                    }

                    if (startsWith(line, "\\", pos) || startsWith(line, doubleQuote, pos)) {
                        //skip next character, but stay in quote-mode
                        pos++;
                    } else if (startsWith(line, quoteChar, pos)) {
                        ret = NO_END;
                        quoteChar = null;

                        continue statement;
                    }
                } while (pos++ < maxpos);

                // reach EOL
                break statement;
            }

            // use the nextchar from the previous iteration
            c1 = c2;
            if (pos < maxpos) {
                // and set the following char
                c2 = line.charAt(pos + 1);
            } else {
                // or reset to blank if the line has ended
                c2 = ' ';
            }

            // verify if current char indicates start  of new quoted block
            if (c1 == '\'' || c1 == '"') {
                quoteChar = String.valueOf(c1);
                ret = quoteChar.equals("'") ? OVERFLOW_SINGLE_QUOTE : OVERFLOW_DOUBLE_QUOTE;
                continue statement;
            }

            // parse for a / * start of comment
            if (c1 == '/' && c2 == '*') {
                isComment = true;
                pos++;
                ret = OVERFLOW_COMMENT;
                continue statement;
            }

            if (c1 == '-' && c2 == '-') {
                return ret;
            }

            if (startsWith(line, delimiter, pos)) {
                if (isAlphaDelimiter) {
                    // check if delimiter is at start or end of line, surrounded
                    // by non-alpha character
                    if ((pos == 0 || !isAlpha(line.charAt(pos - 1))) && (line.length() == pos + delimiter.length()
                            || !isAlpha(line.charAt(pos + delimiter.length())))) {
                        return pos + delimiter.length();
                    }
                } else {
                    return pos + delimiter.length();
                }
            }

        } while (maxpos > pos++);

        return ret;
    }

    /**
     * Small performance optimized replacement for {@link String#startsWith(String, int)}.
     *
     * @param toParse the String to parse
     * @param delimiter the delimiter to look for
     * @param position the initial position to start the scan with
     */
    private static boolean startsWith(String toParse, String delimiter, int position) {
        if (delimiter.length() == 1) {
            return toParse.length() > position && toParse.charAt(position) == delimiter.charAt(0);
        } else {
            return toParse.startsWith(delimiter, position);
        }
    }

    /**
     *
     * @param toParse the String to parse
     * @param delimiter the delimiter to look for
     * @param position the initial position to start the scan with
     * @return
     */
    private static boolean startsWith(String toParse, char delimiter, int position) {
        return toParse.length() > position && toParse.charAt(position) == delimiter;
    }

    /**
     * @param c the char to check
     * @return <code>true</code> if the given character is either a lower or an upperchase alphanumerical character
     */
    private static boolean isAlpha(char c) {
        return Character.isUpperCase(c) || Character.isLowerCase(c);
    }

}