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

org.dbflute.cbean.sqlclause.subquery.SubQueryIndentProcessor Maven / Gradle / Ivy

/*
 * Copyright 2014-2020 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.cbean.sqlclause.subquery;

import java.io.Serializable;

import org.dbflute.helper.message.ExceptionMessageBuilder;
import org.dbflute.system.DBFluteSystem;
import org.dbflute.util.Srl;
import org.dbflute.util.Srl.ScopeInfo;

/**
 * @author jflute
 * @since 0.9.7.2 (2010/06/20 Sunday)
 */
public class SubQueryIndentProcessor implements Serializable {

    // ===================================================================================
    //                                                                          Definition
    //                                                                          ==========
    /** The serial version UID for object serialization. (Default) */
    private static final long serialVersionUID = 1L;

    public static final String BEGIN_MARK_PREFIX = "--#df:sqbegin#";
    public static final String END_MARK_PREFIX = "--#df:sqend#";
    public static final String IDENTITY_TERMINAL = "#df:idterm#";

    // ===================================================================================
    //                                                                    Resolve Identity
    //                                                                    ================
    public String resolveSubQueryBeginMark(String subQueryIdentity) {
        return BEGIN_MARK_PREFIX + subQueryIdentity + IDENTITY_TERMINAL;
    }

    public String resolveSubQueryEndMark(String subQueryIdentity) {
        return END_MARK_PREFIX + subQueryIdentity + IDENTITY_TERMINAL;
    }

    // ===================================================================================
    //                                                                      Process Indent
    //                                                                      ==============
    public String processSubQueryIndent(final String sql, final String preIndent, final String originalSql) {
        // /= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
        // it's a super core logic for formatting SQL generated by ConditionBean
        // = = = = = = = = = =/
        final String beginMarkPrefix = BEGIN_MARK_PREFIX;
        if (!sql.contains(beginMarkPrefix)) {
            return sql;
        }
        final String[] lines = sql.split(ln());
        final String endMarkPrefix = END_MARK_PREFIX;
        final String identityTerminal = IDENTITY_TERMINAL;
        final int terminalLength = identityTerminal.length();
        final StringBuilder mainSb = new StringBuilder();
        StringBuilder subSb = null;
        boolean throughBegin = false;
        boolean throughBeginFirst = false;
        String subQueryIdentity = null;
        String indent = null;
        for (String line : lines) {
            if (!throughBegin) {
                if (line.contains(beginMarkPrefix)) { // begin line
                    throughBegin = true;
                    subSb = new StringBuilder();
                    final int markIndex = line.indexOf(beginMarkPrefix);
                    final int terminalIndex = line.indexOf(identityTerminal);
                    if (terminalIndex < 0) {
                        String msg = "Identity terminal was not found at the begin line: [" + line + "]";
                        throw new SubQueryIndentFailureException(msg);
                    }
                    final String clause = line.substring(0, markIndex) + line.substring(terminalIndex + terminalLength);
                    subQueryIdentity = line.substring(markIndex + beginMarkPrefix.length(), terminalIndex);
                    subSb.append(clause);
                    indent = buildSpaceBar(markIndex - preIndent.length());
                } else { // normal line
                    if (needsLineConnection(mainSb)) {
                        mainSb.append(ln());
                    }
                    mainSb.append(line).append(ln());
                }
            } else {
                // - - - - - - - -
                // In begin to end
                // - - - - - - - -
                if (line.contains(endMarkPrefix + subQueryIdentity)) { // end line
                    final int markIndex = line.indexOf(endMarkPrefix);
                    final int terminalIndex = line.indexOf(identityTerminal);
                    if (terminalIndex < 0) {
                        String msg = "Identity terminal was not found at the begin line: [" + line + "]";
                        throw new SubQueryIndentFailureException(msg);
                    }
                    final String clause = line.substring(0, markIndex);
                    // e.g. " + 1" of ColumnQuery calculation for right column
                    final String preRemainder = line.substring(terminalIndex + terminalLength);
                    subSb.append(clause);
                    final String subQuerySql = subSb.toString();
                    final String nestedPreIndent = preIndent + indent;
                    final String currentSql = processSubQueryIndent(subQuerySql, nestedPreIndent, originalSql);
                    if (needsLineConnection(mainSb)) {
                        mainSb.append(ln());
                    }
                    mainSb.append(currentSql);
                    if (Srl.is_NotNull_and_NotTrimmedEmpty(preRemainder)) {
                        mainSb.append(preRemainder);
                    }
                    throughBegin = false;
                    throughBeginFirst = false;
                } else { // scope line
                    if (!throughBeginFirst) {
                        subSb.append(line.trim()).append(ln());
                        throughBeginFirst = true;
                    } else {
                        subSb.append(indent).append(line).append(ln());
                    }
                }
            }
        }
        final String filteredSql = Srl.rtrim(mainSb.toString()); // removed latest line separator
        if (throughBegin) {
            throwSubQueryNotFoundEndMarkException(subQueryIdentity, sql, filteredSql, originalSql);
        }
        if (filteredSql.contains(beginMarkPrefix)) {
            throwSubQueryAnyBeginMarkNotHandledException(subQueryIdentity, sql, filteredSql, originalSql);
        }
        return filteredSql;
    }

    protected boolean needsLineConnection(StringBuilder sb) {
        final int length = sb.length();
        if (length == 0) {
            return false;
        }
        final String lastStr = sb.substring(length - 1, length);
        if (lastStr.equals(ln())) {
            return false;
        }
        // if a previous line is sub-query,
        // it may not end with a line separator
        return true;
    }

    protected void throwSubQueryNotFoundEndMarkException(String subQueryIdentity, String sql, String filteredSql, String originalSql) {
        final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
        br.addNotice("Not found the end mark for sub-query.");
        br.addItem("SubQueryIdentity");
        br.addElement(subQueryIdentity);
        br.addItem("Before Filter");
        br.addElement(sql);
        br.addItem("After Filter");
        br.addElement(filteredSql);
        br.addItem("Original SQL");
        br.addElement(originalSql);
        final String msg = br.buildExceptionMessage();
        throw new SubQueryIndentFailureException(msg);
    }

    protected void throwSubQueryAnyBeginMarkNotHandledException(String subQueryIdentity, String sql, String filteredSql,
            String originalSql) {
        final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
        br.addNotice("Any begin marks are not handled.");
        br.addItem("SubQueryIdentity");
        br.addElement(subQueryIdentity);
        br.addItem("Before Filter");
        br.addElement(sql);
        br.addItem("After Filter");
        br.addElement(filteredSql);
        br.addItem("Original SQL");
        br.addElement(originalSql);
        final String msg = br.buildExceptionMessage();
        throw new SubQueryIndentFailureException(msg);
    }

    public static class SubQueryIndentFailureException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        public SubQueryIndentFailureException(String msg) {
            super(msg);
        }
    }

    // ===================================================================================
    //                                                                        Space Helper
    //                                                                        ============
    protected String buildSpaceBar(int size) {
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(" ");
        }
        return sb.toString();
    }

    // ===================================================================================
    //                                                                       Determination
    //                                                                       =============
    public static boolean hasSubQueryBeginOnFirstLine(String exp) {
        final String sqbegin = BEGIN_MARK_PREFIX;
        if (exp.contains(ln())) {
            final String firstLine = Srl.substringFirstFront(exp, ln());
            if (firstLine.contains(sqbegin)) {
                return true; // a first line has sub-query end mark
            }
        }
        return false;
    }

    public static boolean hasSubQueryEndOnLastLine(String exp) {
        final String sqend = END_MARK_PREFIX;
        if (exp.contains(ln())) {
            final String lastLine = Srl.substringLastRear(exp, ln());
            if (lastLine.contains(sqend)) {
                return true; // a last line has sub-query end mark
            }
        }
        return false;
    }

    // should be checked before calling if on last line
    public static String moveSubQueryEndToRear(String exp) {
        final String sqend = END_MARK_PREFIX;
        final String idterm = IDENTITY_TERMINAL;
        final ScopeInfo lastScope = Srl.extractScopeLast(exp, sqend, idterm);
        final String scopeStr = lastScope.getScope();
        final String front = exp.substring(0, lastScope.getBeginIndex());
        final String rear = exp.substring(lastScope.getEndIndex());
        return front + rear + scopeStr;
    }

    // ===================================================================================
    //                                                                      General Helper
    //                                                                      ==============
    protected String replaceString(String text, String fromText, String toText) {
        return Srl.replace(text, fromText, toText);
    }

    protected static String ln() {
        return DBFluteSystem.ln();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy