org.dbflute.solr.cbean.SolrQueryBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.dbflute.solr.cbean.SolrQueryBuilder.java

Source

/*
 * Copyright 2015-2016 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.solr.cbean;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.dbflute.helper.HandyDate;
import org.dbflute.solr.entity.dbmeta.SolrDBMeta;
import org.dbflute.util.DfStringUtil;

/**
 * Solr?<br />
 * q??????
 * @author FreeGen
 */
public class SolrQueryBuilder {

    private static final String VALUE_SEPARATOR = "_";

    private static final Pattern BOOLEAN_PHRASE_PART = Pattern.compile("\"(.+?)\"");

    /**  */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter
            .ofPattern("yyyy-MM-dd'T'HH:mm:ss.000'Z'");

    private static final int PAD_LIMIT = 8192;

    /**
     * ?
     *
     */
    private static final String SET_RANGE_SEARCH_RIGHT_WILDCARD = "?????";

    /**
     * ?
     *
     */
    private static final String SET_RANGE_SEARCH_LEFT_WILDCARD = "\\*";

    /**
     * ??
     * @param query 
     * @return ??
     */
    public static String wrapGroupQuery(String query) {
        return "(" + query + ")";
    }

    /**
     * ???
     * @param query 
     * @param asterisk ????<code>true</code>
     * @return ??
     */
    public static String wrapNotGroupQuery(String query, boolean asterisk) {
        if (asterisk) {
            return "(NOT " + query + " AND *:*)";
        }
        return "(NOT " + query + ")";
    }

    /**
     * AND????
     * @param queryList 
     * @return AND?????
     */
    public static String concatEachCondition(List<String> queryList) {
        return concatEachCondition(queryList, SolrQueryLogicalOperator.AND);
    }

    /**
     * ?????
     * @param queryList 
     * @param operator ?
     * @return ??????
     */
    public static String concatEachCondition(List<String> queryList, SolrQueryLogicalOperator operator) {
        StringBuilder queryBuilder = new StringBuilder();
        if (isNotEmptyStrict(queryList)) {
            for (int i = 0; i < queryList.size(); i++) {
                if (i != 0) {
                    queryBuilder.append(" ");
                    queryBuilder.append(operator.name());
                    queryBuilder.append(" ");
                }
                queryBuilder.append(queryList.get(i));
            }
        } else {
            // ????
            queryBuilder.append("*:*");
        }
        return queryBuilder.toString();
    }

    public static String dismax(String query, Map<SolrDBMeta, Integer> qf) {
        StringBuilder queryBuilder = new StringBuilder();
        queryBuilder.append("{!dismax qf='");
        qf.forEach((field, weight) -> {
            queryBuilder.append(field.fieldName());
            if (weight != null) {
                queryBuilder.append("^");
                queryBuilder.append(weight);
            }
            queryBuilder.append(" ");
        });
        if (queryBuilder.length() != 0) {
            queryBuilder.deleteCharAt(queryBuilder.length() - 1);
        }
        queryBuilder.append("'}");
        queryBuilder.append(query);

        return queryBuilder.toString();
    }

    /**
     * ????
     * @param freewordSet 
     * @return ????
     */
    public static String[] splitFreeWords(String freewordSet) {
        if (DfStringUtil.is_Null_or_TrimmedEmpty(freewordSet)) {
            throw new IllegalArgumentException();
        }
        String strFreewords = normalizeFreeword(freewordSet);
        return strFreewords.split(" ");
    }

    /**
     * ???
     * @param freewordSet 
     * @return ???
     */
    public static String normalizeFreeword(String freewordSet) {
        if (freewordSet == null) {
            throw new IllegalArgumentException();
        }
        return freewordSet.replaceAll("", " ").replaceAll(" +", " ").replaceAll("^ +", "").replaceAll(" +$", "")
                .trim();
    }

    /**
     * ?????Equal
     * @param solrFieldName
     * @param query
     * @return 
     */
    public static String queryBuilderForEqual(String solrFieldName, String query) {
        StringBuilder queryBuilder = new StringBuilder();
        if (DfStringUtil.is_NotNull_and_NotEmpty(query)) {
            queryBuilder.append(solrFieldName);
            queryBuilder.append(":");
            queryBuilder.append(query);
        }
        return queryBuilder.toString();
    }

    public static String queryBuilderForPrefixSearch(String solrFieldName, String query) {
        if (solrFieldName == null) {
            return "";
        }
        StringBuilder queryBuilder = new StringBuilder();
        queryBuilder.append(solrFieldName);
        queryBuilder.append(":");
        queryBuilder.append(query).append("*");

        return queryBuilder.toString();
    }

    public static String queryBuilderForSuffixSearch(String solrFieldName, String query) {
        if (solrFieldName == null) {
            return "";
        }
        StringBuilder queryBuilder = new StringBuilder();
        queryBuilder.append(solrFieldName);
        queryBuilder.append(":");
        queryBuilder.append("*").append(query);

        return queryBuilder.toString();
    }

    public static String queryBuilderForContainsSearch(String solrFieldName, String query) {
        if (solrFieldName == null) {
            return "";
        }
        StringBuilder queryBuilder = new StringBuilder();
        queryBuilder.append(solrFieldName);
        queryBuilder.append(":");
        queryBuilder.append("*").append(query).append("*");

        return queryBuilder.toString();
    }

    /**
     * ????????????
     * @param solrFieldName
     * @return 
     */
    public static String queryBuilderForExists(String solrFieldName) {
        if (solrFieldName == null) {
            return "";
        }
        StringBuilder queryBuilder = new StringBuilder();
        queryBuilder.append(solrFieldName);
        queryBuilder.append(":");
        queryBuilder.append("*");

        return queryBuilder.toString();
    }

    /**
     * 
     *
     * @param solrFieldName
     * @param from
     * @param to
     * @return 
     */
    public static String queryBuilderForRangeSearch(String solrFieldName, Date from, Date to) {
        String toStr = null;
        String fromStr = null;
        if (from == null) {
            fromStr = "*";
        } else {
            fromStr = formatYMDHmsForSolrByJST(from);
        }

        if (to == null) {
            toStr = "*";
        } else {
            toStr = formatYMDHmsForSolrByJST(to);
        }
        return queryBuilderForRangeSearch(solrFieldName, fromStr, toStr);
    }

    public static String queryBuilderForRangeSearch(String solrFieldName, LocalDate from, LocalDate to) {
        Date fromDate = from == null ? null
                : Date.from(ZonedDateTime.of(from, LocalTime.MIDNIGHT, ZoneId.systemDefault()).toInstant());
        Date toDate = to == null ? null
                : Date.from(ZonedDateTime.of(to, LocalTime.MIDNIGHT, ZoneId.systemDefault()).toInstant());
        return queryBuilderForRangeSearch(solrFieldName, fromDate, toDate);
    }

    public static String queryBuilderForRangeSearch(String solrFieldName, LocalDateTime from, LocalDateTime to) {
        Date fromDate = from == null ? null : Date.from(ZonedDateTime.of(from, ZoneId.systemDefault()).toInstant());
        Date toDate = to == null ? null : Date.from(ZonedDateTime.of(to, ZoneId.systemDefault()).toInstant());
        return queryBuilderForRangeSearch(solrFieldName, fromDate, toDate);
    }

    /**
     * 
     * @param solrFieldName
     * @param from
     * @param to
     * @return 
     */
    public static String queryBuilderForRangeSearch(String solrFieldName, BigDecimal from, BigDecimal to) {
        String toStr = null;
        String fromStr = null;
        if (from == null) {
            fromStr = "*";
        } else {
            fromStr = from.toString();
        }

        if (to == null) {
            toStr = "*";
        } else {
            toStr = to.toString();
        }
        return queryBuilderForRangeSearch(solrFieldName, fromStr, toStr);
    }

    /**
     * 
     * @param solrFieldName
     * @param from
     * @param to
     * @return 
     */
    public static String queryBuilderForRangeSearch(String solrFieldName, Long from, Long to) {
        String toStr = null;
        String fromStr = null;
        if (from == null) {
            fromStr = "*";
        } else {
            fromStr = from.toString();
        }

        if (to == null) {
            toStr = "*";
        } else {
            toStr = to.toString();
        }
        return queryBuilderForRangeSearch(solrFieldName, fromStr, toStr);
    }

    /**
     * 
     *
     * @param solrFieldName
     * @param from
     * @param to
     * @return 
     */
    public static String queryBuilderForRangeSearch(String solrFieldName, Integer from, Integer to) {
        String toStr = null;
        String fromStr = null;
        if (from == null) {
            fromStr = "*";
        } else {
            fromStr = from.toString();
        }

        if (to == null) {
            toStr = "*";
        } else {
            toStr = to.toString();
        }
        return queryBuilderForRangeSearch(solrFieldName, fromStr, toStr);
    }

    /**
     * 
     *
     * @param solrFieldName
     * @param from
     * @param to
     * @return 
     */
    public static String queryBuilderForRangeSearch(String solrFieldName, String from, String to) {
        if (DfStringUtil.is_Null_or_Empty(from)) {
            from = "*";
        }
        if (DfStringUtil.is_Null_or_Empty(to)) {
            to = "*";
        }
        return solrFieldName + ":[" + from + " TO " + to + "]";
    }

    /**
     * 
     * @param solrFieldName
     * @param cd
     * @param from
     * @param to
     * @param paddingLength
     * @param paddingStr
     * @return 
     */
    public static String queryBuilderForSetRangeSearch(String solrFieldName, String cd, String from, String to,
            int paddingLength, String paddingStr) {
        if (DfStringUtil.is_NotNull_and_NotEmpty(cd)) {
            if (DfStringUtil.is_Null_or_Empty(from)) {
                from = SET_RANGE_SEARCH_LEFT_WILDCARD;
            }
            if (DfStringUtil.is_Null_or_Empty(to)) {
                to = SET_RANGE_SEARCH_RIGHT_WILDCARD;
            }

            if (!from.contains("*")) {
                from = leftPad(from, paddingLength, paddingStr);
            }

            if (!to.contains("?")) {
                to = leftPad(to, paddingLength, paddingStr);
            }
            return solrFieldName + ":[" + cd + VALUE_SEPARATOR + from + " TO " + cd + VALUE_SEPARATOR + to + "]";
        }
        return "";
    }

    /**
     *  InScope
     * @param solrFieldName
     * @param beanList
     * @param paddingLength
     * @param paddingStr
     * @return 
     */
    public static String queryBuilderForSetRangeSearchInScope(String solrFieldName,
            Collection<SolrSetRangeSearchBean> beanList, int paddingLength, String paddingStr) {
        StringBuilder queryBuilder = new StringBuilder();
        if (beanList != null) {
            for (SolrSetRangeSearchBean bean : beanList) {
                if (queryBuilder.length() > 0) {
                    queryBuilder.append(" ");
                    queryBuilder.append(SolrQueryLogicalOperator.OR.name());
                    queryBuilder.append(" ");
                }
                queryBuilder.append(queryBuilderForSetRangeSearch(solrFieldName, bean.getCd(), bean.getFrom(),
                        bean.getTo(), paddingLength, paddingStr));
            }

            if (queryBuilder.length() > 0) {
                queryBuilder.insert(0, "(");
                queryBuilder.append(")");
            }
        }
        return queryBuilder.toString();

    }

    public static String queryBuilderForSetRangeSearchAndScope(String solrFieldName,
            Collection<SolrSetRangeSearchBean> beanList, int paddingLength, String paddingStr) {
        StringBuilder queryBuilder = new StringBuilder();
        if (beanList != null) {
            for (SolrSetRangeSearchBean bean : beanList) {
                if (queryBuilder.length() > 0) {
                    queryBuilder.append(" ");
                    queryBuilder.append(SolrQueryLogicalOperator.AND.name());
                    queryBuilder.append(" ");
                }
                queryBuilder.append(queryBuilderForSetRangeSearch(solrFieldName, bean.getCd(), bean.getFrom(),
                        bean.getTo(), paddingLength, paddingStr));
            }

            if (queryBuilder.length() > 0) {
                queryBuilder.insert(0, "(");
                queryBuilder.append(")");
            }
        }
        return queryBuilder.toString();

    }

    /**
     * 
     * ?
     * @param solrFieldName
     * @param queryStrs
     * @param operator
     * @return 
     */
    public static String queryBuilderForFreewordSearch(String solrFieldName, String queryStrs,
            SolrQueryLogicalOperator operator) {
        if (DfStringUtil.is_NotNull_and_NotTrimmedEmpty(queryStrs)) {
            List<String> queryList = createSearchWordList(queryStrs);
            return queryBuilderForSearchWordList(solrFieldName, queryList, operator);
        }
        return "";
    }

    public static String queryBuilderForFreewordSearchSupportIgnore(String solrFieldName, String queryStrs,
            SolrQueryLogicalOperator operator) {
        if (DfStringUtil.is_NotNull_and_NotTrimmedEmpty(queryStrs)) {
            List<String> queryList = createSearchWordList(queryStrs);
            if (operator == SolrQueryLogicalOperator.AND) {
                List<String> plusQuerys = new ArrayList<String>();
                List<String> minusQuerys = new ArrayList<String>();
                for (String query : queryList) {
                    if (query.startsWith("\"-") && query.endsWith("\"") && query.length() >= 4) {
                        minusQuerys.add("\"" + query.substring(2, query.length() - 1) + "\"");
                    } else {
                        plusQuerys.add(query);
                    }
                }

                if (minusQuerys.size() > 0 && plusQuerys.size() > 0) {
                    // ???????NOT??
                    return queryBuilderForSearchWordListAndNot(solrFieldName, plusQuerys, minusQuerys);
                }
            }

            return queryBuilderForSearchWordList(solrFieldName, queryList, operator);
        }
        return "";
    }

    /**
     * 
     * @param solrFieldName
     * @param queryList
     * @param operator
     * @return 
     */
    public static String queryBuilderForSearchBigDecimalList(String solrFieldName, Collection<BigDecimal> queryList,
            SolrQueryLogicalOperator operator) {
        List<String> queryStrList = new ArrayList<String>();
        if (isNotEmptyStrict(queryList)) {
            for (BigDecimal num : queryList) {
                queryStrList.add(num.toString());
            }
        }
        return queryBuilderForSearchWordList(solrFieldName, queryStrList, operator);
    }

    /**
     * 
     * @param solrFieldName
     * @param queryList
     * @param operator
     * @return 
     */
    public static String queryBuilderForSearchLongList(String solrFieldName, Collection<Long> queryList,
            SolrQueryLogicalOperator operator) {
        List<String> queryStrList = new ArrayList<String>();
        if (isNotEmptyStrict(queryList)) {
            for (Long num : queryList) {
                queryStrList.add(num.toString());
            }
        }
        return queryBuilderForSearchWordList(solrFieldName, queryStrList, operator);
    }

    /**
     * 
     * @param solrFieldName
     * @param queryList
     * @param operator
     * @return 
     */
    public static String queryBuilderForSearchIntegerList(String solrFieldName, Collection<Integer> queryList,
            SolrQueryLogicalOperator operator) {
        List<String> queryStrList = new ArrayList<String>();
        if (isNotEmptyStrict(queryList)) {
            for (Integer integer : queryList) {
                queryStrList.add(integer.toString());
            }
        }
        return queryBuilderForSearchWordList(solrFieldName, queryStrList, operator);
    }

    /**
     * 
     *  NOT candidate_id: Not (1000 OR 10002 OR 10004)
     *  OR candidate_id: (1000 OR 10002 OR 10004)
     *  AND candidate_id: (1000 AND 10002 AND 10004)
     * @param solrFieldName
     * @param queryList
     * @param operator
     * @return 
     */
    public static String queryBuilderForSearchWordList(String solrFieldName, Collection<String> queryList,
            SolrQueryLogicalOperator operator) {
        StringBuilder queryBuilder = new StringBuilder();
        SolrQueryLogicalOperator partOperator = operator;
        if (isNotEmptyStrict(queryList)) {
            if (SolrQueryLogicalOperator.NOT.equals(operator)) {
                queryBuilder.append(SolrQueryLogicalOperator.NOT.name());
                queryBuilder.append(" ");
                partOperator = SolrQueryLogicalOperator.OR;
            }
            queryBuilder.append(solrFieldName);
            queryBuilder.append(": ");
            queryBuilder.append(queryBuilderForSearchWordListPart(queryList, partOperator));
        }
        return queryBuilder.toString();
    }

    public static String queryBuilderForSearchWordListAndNot(String solrFieldName, Collection<String> andQueryList,
            Collection<String> notQueryList) {
        StringBuilder queryBuilder = new StringBuilder();
        if (isNotEmptyStrict(andQueryList) && isNotEmptyStrict(notQueryList)) {
            queryBuilder.append(solrFieldName);
            queryBuilder.append(": ");
            queryBuilder.append(queryBuilderForSearchWordListPartRaw(andQueryList, SolrQueryLogicalOperator.AND));
            queryBuilder.append(" NOT");
            queryBuilder.append(queryBuilderForSearchWordListPartRaw(notQueryList, SolrQueryLogicalOperator.NOT));
        }
        return queryBuilder.toString();
    }

    /**
     * Word List?
     * (1000 OR 10002 OR 10004)
     * @param queryList
     * @param operator
     * @return 
     */
    private static String queryBuilderForSearchWordListPart(Collection<String> queryList,
            SolrQueryLogicalOperator operator) {
        StringBuilder queryBuilder = new StringBuilder();
        queryBuilder.append("(");
        boolean isNotfirst = false;
        for (String query : queryList) {
            if (DfStringUtil.is_NotNull_and_NotEmpty(query)) {
                if (isNotfirst) {
                    queryBuilder.append(operator.name());
                }
                queryBuilder.append(" ");
                queryBuilder.append(query);
                queryBuilder.append(" ");
                isNotfirst = true;
            }
        }
        queryBuilder.append(")");
        return queryBuilder.toString();
    }

    private static String queryBuilderForSearchWordListPartRaw(Collection<String> queryList,
            SolrQueryLogicalOperator operator) {
        StringBuilder queryBuilder = new StringBuilder();
        boolean isNotfirst = false;
        for (String query : queryList) {
            if (DfStringUtil.is_NotNull_and_NotEmpty(query)) {
                if (isNotfirst) {
                    queryBuilder.append(operator.name());
                }
                queryBuilder.append(" ");
                queryBuilder.append(query);
                queryBuilder.append(" ");
                isNotfirst = true;
            }
        }
        return queryBuilder.toString();
    }

    /**
     * ?
     * ???????????
     * ""??????
     * "fund manager"   "fund manager", ""
     * @param str
     * @return 
     */
    public static List<String> createSearchWordList(final String str) {
        if (DfStringUtil.is_Null_or_TrimmedEmpty(str)) {
            throw new IllegalArgumentException("???null?????");
        }
        String normalizedStr = normalizeFreeword(str);
        List<String> excludedPhraseList = getExcludedPhraseList(normalizedStr);
        List<String> phraseList = getPhraseList(normalizedStr);
        List<String> arrayList = new ArrayList<String>();
        if (isNotEmptyStrict(excludedPhraseList)) {
            arrayList.addAll(excludedPhraseList);
        }
        if (isNotEmptyStrict(phraseList)) {
            arrayList.addAll(phraseList);
        }
        return arrayList;
    }

    /**
     * ????????<br />
     * <pre>
     * ????1????
     *    BLK_CMP_1
     * </pre>
     * @param args
     * @return 
     */
    public static String createCombinationQueryValue(Object... args) {
        if (args == null || args.length == 0) {
            throw new IllegalArgumentException("null???????????");
        }
        return Arrays.stream(args).map(arg -> arg.toString()).collect(Collectors.joining(VALUE_SEPARATOR));
    }

    /**
     * 2???lpad???????<br />
     * <pre>
     * ????
     *  RG_01000
     * </pre>
     * @param value1 ?
     * @param value2 
     * @param lpadLength lpad??
     * @param lpadChar lpad??
     * @return 
     */
    public static String createDoubleQueryValue(String value1, String value2, int lpadLength, Character lpadChar) {
        if (value1 == null || value2 == null) {
            throw new IllegalArgumentException("value?null?????");
        }
        Character c = lpadChar == null ? '0' : lpadChar;

        return value1 + VALUE_SEPARATOR + leftPad(value2, lpadLength, c.toString());
    }

    /**
     * ???????
     * @param str
     * @return 
     */
    public static List<String> getExcludedPhraseList(String str) {
        Matcher phrasePartMatcher = BOOLEAN_PHRASE_PART.matcher(str);
        String exceptPhraseWord = phrasePartMatcher.replaceAll("").trim();
        exceptPhraseWord = exceptPhraseWord.replaceAll("\"", "");
        exceptPhraseWord = normalizeFreeword(exceptPhraseWord);
        String[] splitedArray = exceptPhraseWord.split(" ");
        List<String> excludedPhraseList = new ArrayList<String>();
        for (int i = 0; i < splitedArray.length; i++) {
            excludedPhraseList.add("\"" + splitedArray[i] + "\"");
        }
        return excludedPhraseList;
    }

    /**
     * ??????
     * @param str
     * @return 
     */
    public static List<String> getPhraseList(String str) {
        Matcher phrasePartMatcher = BOOLEAN_PHRASE_PART.matcher(str);
        List<String> phraseList = new ArrayList<String>();
        while (phrasePartMatcher.find()) {
            String phraseStr = phrasePartMatcher.group();
            phraseList.add("\"" + phraseStr.replaceAll("\"", "").trim() + "\"");
        }
        return phraseList;
    }

    /**
     * ??List??
     * @param collection
     * @return ?????{@code true}
     */
    private static <E> boolean isNotEmptyStrict(Collection<E> collection) {
        return !isEmptyStrict(collection);
    }

    /**
     * ??List??
     * @param collection
     * @return ???{@code true}
     */
    private static <E> boolean isEmptyStrict(Collection<E> collection) {
        if (collection == null || collection.size() == 0) {
            return true;
        }

        // String?List???????????null????
        if (collection instanceof List<?>) {
            if (((List<?>) collection).get(0) instanceof String) {
                for (E s : collection) {
                    if (DfStringUtil.is_NotNull_and_NotEmpty(String.class.cast(s))) {
                        return false;
                    }
                }
                return true;
            } else {
                for (E s : collection) {
                    if (s != null) {
                        return false;
                    }
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Solr??Date??????Date?JST??????
     * @param date
     * @returnSolr???UTC??Date????
     */
    public static String formatYMDHmsForSolrByJST(Date date) {
        // TODO -9?????
        return formatYMDHmsForSolr(date, -9);
    }

    /**
     * Solr???UTC??Date??????
     * @param date ????
     * @param timeZoneDiff ???UTC???(JST?-9?UTC????-9?)
     * @returnSolr???UTC??Date????
     */
    public static String formatYMDHmsForSolr(Date date, int timeZoneDiff) {
        return DATE_TIME_FORMATTER.format(new HandyDate(date).addHour(timeZoneDiff).getLocalDateTime());
    }

    public static void assertNullQuery(String fieldName, String query) {
        if (fieldName == null) {
            String msg = "fieldName should not be null: fieldName=null query=" + query;
            throw new IllegalArgumentException(msg);
        }
        if (query != null) {
            String msg = "Query for this field is already registered: fieldName:query=" + query;
            throw new IllegalArgumentException(msg);
        }
    }

    public static void assertNotNullQuery(String fieldName, String query) {
        if (fieldName == null) {
            String msg = "fieldName should not be null: fieldName=null query=" + query;
            throw new IllegalArgumentException(msg);
        }
        if (query == null) {
            String msg = "The query should not be null: fieldName=" + fieldName;
            throw new IllegalArgumentException(msg);
        }
    }

    // TODO org.apache.commons.lang3.StringUtils????????
    /**
     * <p>Left pad a String with a specified String.</p>
     *
     * <p>Pad to a size of {@code size}.</p>
     *
     * <pre>
     * StringUtils.leftPad(null, *, *)      = null
     * StringUtils.leftPad("", 3, "z")      = "zzz"
     * StringUtils.leftPad("bat", 3, "yz")  = "bat"
     * StringUtils.leftPad("bat", 5, "yz")  = "yzbat"
     * StringUtils.leftPad("bat", 8, "yz")  = "yzyzybat"
     * StringUtils.leftPad("bat", 1, "yz")  = "bat"
     * StringUtils.leftPad("bat", -1, "yz") = "bat"
     * StringUtils.leftPad("bat", 5, null)  = "  bat"
     * StringUtils.leftPad("bat", 5, "")    = "  bat"
     * </pre>
     *
     * @param str  the String to pad out, may be null
     * @param size  the size to pad to
     * @param padStr  the String to pad with, null or empty treated as single space
     * @return left padded String or original String if no padding is necessary,
     *  {@code null} if null String input
     */
    public static String leftPad(final String str, final int size, String padStr) {
        if (str == null) {
            return null;
        }
        if (DfStringUtil.is_Null_or_Empty(padStr)) {
            padStr = " ";
        }
        final int padLen = padStr.length();
        final int strLen = str.length();
        final int pads = size - strLen;
        if (pads <= 0) {
            return str; // returns original String when possible
        }
        if (padLen == 1 && pads <= PAD_LIMIT) {
            return leftPad(str, size, padStr.charAt(0));
        }

        if (pads == padLen) {
            return padStr.concat(str);
        } else if (pads < padLen) {
            return padStr.substring(0, pads).concat(str);
        } else {
            final char[] padding = new char[pads];
            final char[] padChars = padStr.toCharArray();
            for (int i = 0; i < pads; i++) {
                padding[i] = padChars[i % padLen];
            }
            return new String(padding).concat(str);
        }
    }

    /**
     * <p>Left pad a String with a specified character.</p>
     *
     * <p>Pad to a size of {@code size}.</p>
     *
     * <pre>
     * StringUtils.leftPad(null, *, *)     = null
     * StringUtils.leftPad("", 3, 'z')     = "zzz"
     * StringUtils.leftPad("bat", 3, 'z')  = "bat"
     * StringUtils.leftPad("bat", 5, 'z')  = "zzbat"
     * StringUtils.leftPad("bat", 1, 'z')  = "bat"
     * StringUtils.leftPad("bat", -1, 'z') = "bat"
     * </pre>
     *
     * @param str  the String to pad out, may be null
     * @param size  the size to pad to
     * @param padChar  the character to pad with
     * @return left padded String or original String if no padding is necessary,
     *  {@code null} if null String input
     * @since 2.0
     */
    public static String leftPad(final String str, final int size, final char padChar) {
        if (str == null) {
            return null;
        }
        final int pads = size - str.length();
        if (pads <= 0) {
            return str; // returns original String when possible
        }
        if (pads > PAD_LIMIT) {
            return leftPad(str, size, String.valueOf(padChar));
        }
        return repeat(padChar, pads).concat(str);
    }

    public static String repeat(final char ch, final int repeat) {
        final char[] buf = new char[repeat];
        for (int i = repeat - 1; i >= 0; i--) {
            buf[i] = ch;
        }
        return new String(buf);
    }
}