All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jspringbot.keyword.db.SQLFormatter Maven / Gradle / Ivy

There is a newer version: 1.9
Show newest version
package org.jspringbot.keyword.db;

import org.apache.commons.lang.StringUtils;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.StringTokenizer;

public class SQLFormatter {

    public static String prettyPrint(String unformattedSQL) {
        if(StringUtils.trim(unformattedSQL).length() <= 50) {
            return StringUtils.trim(unformattedSQL);
        }

        return new SQLFormatter().format(unformattedSQL);
    }

    private static final Set BEGIN_CLAUSES = new HashSet();
    private static final Set END_CLAUSES = new HashSet();
    private static final Set LOGICAL = new HashSet();
    private static final Set QUANTIFIERS = new HashSet();
    private static final Set DML = new HashSet();
    private static final Set MISC = new HashSet();

    static {
        BEGIN_CLAUSES.add( "left" );
        BEGIN_CLAUSES.add( "right" );
        BEGIN_CLAUSES.add( "inner" );
        BEGIN_CLAUSES.add( "outer" );
        BEGIN_CLAUSES.add( "group" );
        BEGIN_CLAUSES.add( "order" );

        END_CLAUSES.add( "where" );
        END_CLAUSES.add( "set" );
        END_CLAUSES.add( "having" );
        END_CLAUSES.add( "join" );
        END_CLAUSES.add( "from" );
        END_CLAUSES.add( "by" );
        END_CLAUSES.add( "join" );
        END_CLAUSES.add( "into" );
        END_CLAUSES.add( "union" );
        END_CLAUSES.add( "limit" );

        LOGICAL.add( "and" );
        LOGICAL.add( "or" );
        LOGICAL.add( "when" );
        LOGICAL.add( "else" );
        LOGICAL.add( "end" );

        QUANTIFIERS.add( "in" );
        QUANTIFIERS.add( "all" );
        QUANTIFIERS.add( "exists" );
        QUANTIFIERS.add( "some" );
        QUANTIFIERS.add( "any" );

        DML.add( "insert" );
        DML.add( "update" );
        DML.add( "delete" );

        MISC.add( "select" );
        MISC.add( "on" );
    }

    static final String indentString = "    ";
    static final String initial = "\n";

    public String format(String source) {
        return new FormatProcess( source ).perform();
    }

    private static class FormatProcess {

        private static final String WHITESPACE = " \n\r\f\t";

        boolean beginLine = true;
        boolean afterBeginBeforeEnd = false;
        boolean afterByOrSetOrFromOrSelect = false;
        boolean afterValues = false;
        boolean afterOn = false;
        boolean afterBetween = false;
        boolean afterInsert = false;
        int inFunction = 0;
        int parensSinceSelect = 0;
        private LinkedList parenCounts = new LinkedList();
        private LinkedList afterByOrFromOrSelects = new LinkedList();

        int indent = 1;

        StringBuffer result = new StringBuffer();
        StringTokenizer tokens;
        String lastToken;
        String token;
        String lcToken;

        public FormatProcess(String sql) {
            tokens = new StringTokenizer(
                    sql,
                    "()+*/-=<>'`\"[]," + WHITESPACE,
                    true
            );
        }

        public String perform() {

            result.append( initial );

            while ( tokens.hasMoreTokens() ) {
                token = tokens.nextToken();
                lcToken = token.toLowerCase();

                if ( "'".equals( token ) ) {
                    String t;
                    do {
                        t = tokens.nextToken();
                        token += t;
                    }
                    while ( !"'".equals( t ) && tokens.hasMoreTokens() ); // cannot handle single quotes
                }
                else if ( "\"".equals( token ) ) {
                    String t;
                    do {
                        t = tokens.nextToken();
                        token += t;
                    }
                    while ( !"\"".equals( t ) );
                }

                if ( afterByOrSetOrFromOrSelect && ",".equals( token ) ) {
                    commaAfterByOrFromOrSelect();
                }
                else if ( afterOn && ",".equals( token ) ) {
                    commaAfterOn();
                }

                else if ( "(".equals( token ) ) {
                    openParen();
                }
                else if ( ")".equals( token ) ) {
                    closeParen();
                }

                else if ( BEGIN_CLAUSES.contains( lcToken ) ) {
                    beginNewClause();
                }

                else if ( END_CLAUSES.contains( lcToken ) ) {
                    endNewClause();
                }

                else if ( "select".equals( lcToken ) ) {
                    select();
                }

                else if ( DML.contains( lcToken ) ) {
                    updateOrInsertOrDelete();
                }

                else if ( "values".equals( lcToken ) ) {
                    values();
                }

                else if ( "on".equals( lcToken ) ) {
                    on();
                }

                else if ( afterBetween && lcToken.equals( "and" ) ) {
                    misc();
                    afterBetween = false;
                }

                else if ( LOGICAL.contains( lcToken ) ) {
                    logical();
                }

                else if ( isWhitespace( token ) ) {
                    white();
                }

                else {
                    misc();
                }

                if ( !isWhitespace( token ) ) {
                    lastToken = lcToken;
                }

            }
            return result.toString();
        }

        private void commaAfterOn() {
            out();
            indent--;
            newline();
            afterOn = false;
            afterByOrSetOrFromOrSelect = true;
        }

        private void commaAfterByOrFromOrSelect() {
            out();
            newline();
        }

        private void logical() {
            if ( "end".equals( lcToken ) ) {
                indent--;
            }
            newline();
            out();
            beginLine = false;
        }

        private void on() {
            indent++;
            afterOn = true;
            newline();
            out();
            beginLine = false;
        }

        private void misc() {
            out();
            if ( "between".equals( lcToken ) ) {
                afterBetween = true;
            }
            if ( afterInsert ) {
                newline();
                afterInsert = false;
            }
            else {
                beginLine = false;
                if ( "case".equals( lcToken ) ) {
                    indent++;
                }
            }
        }

        private void white() {
            if ( !beginLine ) {
                result.append( " " );
            }
        }

        private void updateOrInsertOrDelete() {
            out();
            indent++;
            beginLine = false;
            if ( "update".equals( lcToken ) ) {
                newline();
            }
            if ( "insert".equals( lcToken ) ) {
                afterInsert = true;
            }
        }

        private void select() {
            out();
            indent++;
            newline();
            parenCounts.addLast(parensSinceSelect);
            afterByOrFromOrSelects.addLast(afterByOrSetOrFromOrSelect);
            parensSinceSelect = 0;
            afterByOrSetOrFromOrSelect = true;
        }

        private void out() {
            result.append( token );
        }

        private void endNewClause() {
            if ( !afterBeginBeforeEnd ) {
                indent--;
                if ( afterOn ) {
                    indent--;
                    afterOn = false;
                }
                newline();
            }
            out();
            if ( !"union".equals( lcToken ) ) {
                indent++;
            }
            newline();
            afterBeginBeforeEnd = false;
            afterByOrSetOrFromOrSelect = "by".equals( lcToken )
                    || "set".equals( lcToken )
                    || "from".equals( lcToken );
        }

        private void beginNewClause() {
            if ( !afterBeginBeforeEnd ) {
                if ( afterOn ) {
                    indent--;
                    afterOn = false;
                }
                indent--;
                newline();
            }
            out();
            beginLine = false;
            afterBeginBeforeEnd = true;
        }

        private void values() {
            indent--;
            newline();
            out();
            indent++;
            newline();
            afterValues = true;
        }

        private void closeParen() {
            parensSinceSelect--;
            if ( parensSinceSelect < 0 ) {
                indent--;
                parensSinceSelect = parenCounts.removeLast();
                afterByOrSetOrFromOrSelect = afterByOrFromOrSelects.removeLast();
            }
            if ( inFunction > 0 ) {
                inFunction--;
                out();
            }
            else {
                if ( !afterByOrSetOrFromOrSelect ) {
                    indent--;
                    newline();
                }
                out();
            }
            beginLine = false;
        }

        private void openParen() {
            if ( isFunctionName( lastToken ) || inFunction > 0 ) {
                inFunction++;
            }
            beginLine = false;
            if ( inFunction > 0 ) {
                out();
            }
            else {
                out();
                if ( !afterByOrSetOrFromOrSelect ) {
                    indent++;
                    newline();
                    beginLine = true;
                }
            }
            parensSinceSelect++;
        }

        private static boolean isFunctionName(String tok) {
            final char begin = tok.charAt( 0 );
            final boolean isIdentifier = Character.isJavaIdentifierStart( begin ) || '"' == begin;
            return isIdentifier &&
                    !LOGICAL.contains( tok ) &&
                    !END_CLAUSES.contains( tok ) &&
                    !QUANTIFIERS.contains( tok ) &&
                    !DML.contains( tok ) &&
                    !MISC.contains( tok );
        }

        private static boolean isWhitespace(String token) {
            return WHITESPACE.contains(token);
        }

        private void newline() {
            result.append( "\n" );
            for ( int i = 0; i < indent; i++ ) {
                result.append( indentString );
            }
            beginLine = true;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy