package sgc.portal.base.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;

import sgc.portal.base.Domain;
import sgc.portal.base.exception.BusinessException;

import lombok.Getter;

/**
 * The type String util.
 *
 * @author beomgeun.lee
 * @version 2019.11.11
 * @since JDK1.8
 */
public class StringUtil {
    private final static Logger logger = LoggerFactory.getLogger(StringUtil.class);

    private static final String HTML_TAG_FORMAT_PATTERN = "<(\"[^\"]*\"|'[^']*'|[^'\">])*>";

    /**
     * The constant EMPTY_STRING.
     */
    private static final String EMPTY_STRING = "";
    /**
     * The constant BLANK_STRING.
     */
    private static final String BLANK_STRING = " ";

    /**
     * Nvl string.
     *
     * @param str the str
     * @return the string
     */
    public static String nvl(Object str) {
        return str == null ? "" : str.toString();
    }

    /**
     * Nvl string.
     *
     * @param str the str
     * @param def the def
     * @return the string
     */
    public static String nvl(Object str, String def) {
        return str == null ? def : str.toString();
    }

    /**
     * Blank string.
     *
     * @param str the str
     * @param def the def
     * @return the string
     */
    public static String blank(Object str, String def) {
        return str == null || str.toString().length() == 0 ? def : str.toString();
    }

    /**
     * Join string.
     *
     * @param line  the line
     * @param delim the delim
     * @return the string
     */
    public static String join(String[] line, String delim) {
        String temp = "";
        for (String s : line) {
            temp += s + delim;
        }

        return temp.length() == 0 ? "" : temp.substring(0, temp.length() - delim.length());
    }

    /**
     * Split string [ ].
     *
     * @param str   구분자를 포함한 전체문자열
     * @param delim 전체 문자열을 분리하기 위한 구분자
     * @return 문자열을 구분자에 의해 분리하여 배열을 반환한다.
     * @Method Name : split
     * @Method description : 지정문자열을 구분자로 분리하여 배열로 저장된 값을 반환한다. ex) ",A,B,,C," -> [A,         B, C]
     * @Author : SWA
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static String[] split(String str, String delim) {
        ArrayList list = new ArrayList();
        StringTokenizer token = new StringTokenizer(str, delim);

        while (token.hasMoreTokens()) {
            list.add(token.nextToken());
        }

        return (String[]) list.toArray(new String[0]);
    }

    /**
     * Split all string [ ].
     *
     * @param str   the str
     * @param delim the delim
     * @return string [ ]
     * @Method Name : splitAll
     * @Method description : 지정문자열을 구분자로 분리하여 배열로 저장된 값을 반환한다.(null 포함)         <pre>                       ",A,B,,C," -> [null, A, B, null, C, null]         </pre>
     * @Author : SWA
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static String[] splitAll(String str, String delim) {
        ArrayList list = new ArrayList();
        String source = str;
        int find = 0;

        while (true) {
            source = source.substring(find);
            find = source.indexOf(delim);

            if (find == -1) {
                list.add(source);
                break;
            } else {
                if (find == 0) {
                    list.add(null);
                } else {
                    list.add(source.substring(0, find));
                }

                find++;
                if (find == source.length()) {
                    list.add(null);
                    break;
                }
            }
        }

        return (String[]) list.toArray(new String[0]);
    }

    /**
     * Rpad string.
     *
     * @param str  원본문자열
     * @param size 채울 문자열의 길이
     * @return 공백으로 채운 문자열, 원본문자열이 size보다 크거나 음수이면 그대로 반환한다.
     * @Method Name : rpad
     * @Method description : 주어진 원본문자열의 길이가 size보다 작은 경우 부족한 길이만큼 문자열의 오른쪽을 공백으로         채운다.         <pre>                          StringUtils.rpad(null, *)   = null                          StringUtils.rpad("", 3)     = "   "                          StringUtils.rpad("bat", 3)  = "bat"                          StringUtils.rpad("bat", 5)  = "bat  "                          StringUtils.rpad("bat", 1)  = "bat"                          StringUtils.rpad("bat", -1) = "bat"         </pre>
     * @Author : SWA
     */
    public static String rpad(String str, int size) {
        return rpad(str, size, " ");
    }

    /**
     * Rpad string.
     *
     * @param str    원본문자열
     * @param size   채울 문자열의 길이
     * @param padStr 채울때 쓰일 문자열
     * @return padding 문자열로 채운 문자열, 원본문자열이 size보다 크거나 음수이면 그대로 반환한다. 또한 padding 문자열이         null or empty이면 공백으로 취급된다.
     * @Method Name : rpad
     * @Method description : 주어진 원본문자열의 길이가 size보다 작은 경우 부족한 길이만큼 문자열의 오른쪽을 특정문자열로         채운다.
     * @Author : SWA
     */
    public static String rpad(String str, int size, String padStr) {
        if (str == null) {
            return null;
        }

        if (padStr == null || padStr.length() == 0) {
            padStr = BLANK_STRING;
        }

        int padLen = padStr.length();
        int strLen = str.length();
        int pads = size - strLen;
        if (pads <= 0) {
            return str; // returns original String when possible
        }

        if (pads == padLen) {
            return str.concat(padStr);
        } else if (pads < padLen) {
            return str.concat(padStr.substring(0, pads));
        } else {
            return str.concat(padding(pads, padStr));
        }
    }

    /**
     * Lpad string.
     *
     * @param str  원본문자열
     * @param size 채울 문자열의 길이
     * @return 공백으로 채운 문자열, 원본문자열이 size보다 크거나 음수이면 그대로 반환한다.
     * @Method Name : lpad
     * @Method description : 주어진 원본문자열의 길이가 size보다 작은 경우 부족한 길이만큼 문자열의 왼쪽을 공백으로 채운다.         <pre>                          StringUtils.lpad(null, *)   = null                          StringUtils.lpad("", 3)     = "   "                          StringUtils.lpad("bat", 3)  = "bat"                          StringUtils.lpad("bat", 5)  = "  bat"                          StringUtils.lpad("bat", 1)  = "bat"                          StringUtils.lpad("bat", -1) = "bat"         </pre>
     * @Author : SWA
     */
    public static String lpad(String str, int size) {
        return lpad(str, size, BLANK_STRING);
    }

    /**
     * Lpad string.
     *
     * @param str    원본문자열
     * @param size   채울 문자열의 길이
     * @param padStr 채울때 쓰일 문자열
     * @return padding 문자열로 채운 문자열, 원본문자열이 size보다 크거나 음수이면 그대로 반환한다. 또한 padding 문자열이         null or empty이면 공백으로 취급된다.
     * @Method Name : lpad
     * @Method description : 주어진 원본문자열의 길이가 size보다 작은 경우 부족한 길이만큼 문자열의 왼쪽을 특정문자열로         채운다.
     * @Author : SWA
     */
    public static String lpad(String str, int size, String padStr) {
        if (str == null) {
            return null;
        }

        if (padStr == null || padStr.length() == 0) {
            padStr = BLANK_STRING;
        }

        int padLen = padStr.length();
        int strLen = str.length();
        int pads = size - strLen;
        if (pads <= 0) {
            return str;
        }

        if (pads == padLen) {
            return padStr.concat(str);
        } else if (pads < padLen) {
            return padStr.substring(0, pads).concat(str);
        } else {
            return padding(pads, padStr).concat(str);
        }
    }

    /**
     * Padding string.
     *
     * @param size    반복하여 채울 문자열길이
     * @param padChar 채울때 쓰일 문자열
     * @return 주어진 길이만큼 padding 문자열로 채워진 문자열
     * @Method Name : padding
     * @Method description : 특정문자열을 일정 길이만큼 반복하여 채운다.
     * @Author : SWA
     */
    private static String padding(int size, String padChar) {
        if (padChar == null || padChar.length() == 0) {
            return EMPTY_STRING;
        }

        char[] padding = new char[size];
        char[] padChars = padChar.toCharArray();
        for (int i = 0; i < size; i++) {
            padding[i] = padChars[i % padChars.length];
        }

        return new String(padding);
    }

    /**
     * Is numeric boolean.
     *
     * @param str 검사하고자하는 원본문자열
     * @return 문자열이 숫자만을 포함하고 있다면 true를 반환한다.
     * @Method Name : isNumeric
     * @Method description : 주어진 문자열이 숫자만을 포함하고 있는지 검사한다.
     * @Author : SWA
     */
    public static boolean isNumeric(String str) {
        boolean result = true;
        if (str == null) {
            result = false;
            return result;
        }

        int sz = str.length();
        for (int i = 0; i < sz; i++) {
            if (!Character.isDigit(str.charAt(i))) {
                result = false;
                return result;
            }
        }
        return result;
    }

    /**
     * Is e mail boolean.
     *
     * @param strEmail the str email
     * @return boolean
     * @Method Name : isEMail
     * @Method description :
     * @Author : SWA
     */
    public static boolean isEMail(String strEmail) {
        boolean err = false;
        String emailFormatIn = "^([0-9a-zA-Z]|[0-9a-zA-Z.]|[0-9a-zA-Z])+@(joins.com|joins.co.kr|phoenix.com|phoenix.co.kr)$";
        String emailFormatOut = "^([0-9a-zA-Z]|[0-9a-zA-Z.]|[0-9a-zA-Z])+@([0-9a-zA-Z]+)(\\.([0-9a-zA-Z]+))+$";

        if (strEmail == null || strEmail.length() == 0) {
            logger.debug("e-mail 주소가 없습니다");
            return false;
        }

        Pattern patternIN = Pattern.compile(emailFormatIn);
        Matcher matcherIN = patternIN.matcher(strEmail);
        Pattern patternOUT = Pattern.compile(emailFormatOut);
        Matcher matcherOUT = patternOUT.matcher(strEmail);

        if (matcherIN.matches()) {
            logger.debug("유효한 사내 e-mail : " + strEmail);
            return true;
        } else if (matcherOUT.matches()) {
            logger.debug("유효한 외부 e-mail : " + strEmail);
            return true;
        } else {
            logger.debug("e-mail 주소가 유효하지 않습니다 : " + strEmail);
            return false;
        }
    }

    /**
     * Today string.
     *
     * @return string
     * @Method Name : today
     * @Method description : 오늘 날자 반환 (년,월,일)
     * @Author : SWA
     */
    public static String today() {
        SimpleDateFormat dt = new SimpleDateFormat("yyyyMMdd", Locale.KOREA);

        return dt.format(new Date());
    }

    /**
     * Is empty boolean.
     *
     * @param str the str
     * @return true, false
     * @Method Name : isEmpty
     * @Method description : 빈 문자열 여부 확인
     * @Date :  2020. 1. 8.
     * @Author : user         <pre> ------------------------------ Change History ------------------------------- Date				Programmer				Description  2020. 1. 8.		user					CREATE -----------------------------------------------------------------------------         </pre>
     */
    public static boolean isEmpty(String str) {

        // 전달받은 문자의 값이 null 이거나 빈 값인 경우
        return str == null || str.isEmpty();
    }

    /**
     * Nvl string.
     *
     * @param str    : null 여부를 확인할 대상 문자
     * @param conStr : null 인 경우 대체할 문자
     * @return rtnStr : 반환할 문자
     * @Method Name : nvl
     * @Method description : 전달받은 문자의(str) Null값 여부 확인하여 Null인 경우 변환문자로 변환하여 반환하고         Null이 아닌 경우 str 그대로 반환
     * @Author : SWA
     */
    public static String nvl(String str, String conStr) {

        String rtnStr;

        // 전달받은 문자의 값이 null 이거나 빈 값인 경우
        if (str == null || str.isEmpty()) {

            // 전달받은 변환문자의 값이 null 이거나 빈 값인 경우
            if (conStr == null || conStr.isEmpty()) {

                rtnStr = "";
            } else {

                rtnStr = conStr;
            }
        } else {

            rtnStr = str;
        }

        return rtnStr;
    }

    /**
     * Change char string.
     *
     * @param jStr the j str
     * @return string
     * @Method Name : changeChar
     * @Method description : 전달받은 문자열 중 특정 문자를 변경
     * @Author : SWA
     */
    public static String changeChar(String jStr) {

        if (jStr != null) {

            jStr = jStr.replaceAll("<", "&lt;");
            jStr = jStr.replaceAll(">", "&gt;");
            // jStr = jStr.replaceAll("&" , "&amp;");
            // jStr = jStr.replaceAll("\"", "&quot;");
            // jStr = jStr.replaceAll("\'", "&#x27;");
            // jStr = jStr.replaceAll("/" , "&#x2F;");
        }

        return jStr;
    }

    /**
     * Change string string.
     *
     * @param jStr the j str
     * @return string
     * @Method Name : changeString
     * @Method description : JSON 데이터 전달시 데이터 깨짐방지 처리
     * @Author : SWA
     */
    public static String changeString(String jStr) {

        if (jStr != null) {
            jStr = jStr.replaceAll("\"", "&quot;");
            jStr = jStr.replaceAll("\'", "&#x27;");
        }

        return jStr;
    }

    /**
     * To int int.
     *
     * @param str    : null 여부를 확인할 대상 문자
     * @param conInt : null인 경우 대체할 숫자
     * @return result : 반환할 숫자
     * @Method Name : toInt
     * @Method description : 전달받은 문자의(str) Null값 여부 확인하여 Null인 경우 변환 숫자로 반환하고 Null이         아닌 경우 int형으로 변환 반환
     * @Author : SWA
     */
    public static int toInt(String str, int conInt) {

        int result;

        // 전달받은 문자의 값이 null 이거나 빈 값인 경우
        if (str == null || str.isEmpty()) {
            // 전달받은 변환 숫자로 치환
            result = conInt;
        } else {
            result = Integer.parseInt(str);
        }

        return result;
    }

    /**
     * To array from object string [ ].
     *
     * @param object : 변환할 Object
     * @return result : 반환할 String[]
     * @Method Name : toArrayFromObject
     * @Method description : 전달받은 Object를 loop시켜 문자형 1차원 배열로 변환한다
     * @Author : SWA
     */
    public static String[] toArrayFromObject(Object object) {
        String[] result = null;
        int i;

        if (object != null) {
            List<?> listTemp = (List<?>) object;
            result = new String[listTemp.size()];
            for (i = 0; i < listTemp.size(); i++) {
                if (listTemp.get(i) != null) {
                    result[i] = listTemp.get(i).toString();
                } else {
                    result[i] = "";
                }
            }
        }

        return result;
    }

    /**
     * To num format string.
     *
     * @param num the num
     * @return string
     * @Method Name : toNumFormat
     * @Method description : 천단위마다 콤마를 찍어주는 유틸
     * @Author : SWA
     */
    public static String toNumFormat(int num) {

        DecimalFormat df = new DecimalFormat("#,###");
        return df.format(num);
    }

    /**
     * Replace asta string.
     *
     * @param str 치환대상 문자열
     * @return 치환된 문자열
     * @Method Name : replaceAsta
     * @Method description : 아스타(*) 문자열 치환
     * @Author : SWA
     */
    public static String replaceAsta(String str) {
        // 리턴용 스트링버퍼
        StringBuilder result = new StringBuilder();
        // 치환대상 글자가 존재하는 경우 치환
        if (str != null) {
            int len = str.length();
            for (int i = 0; i < len; i++) {
                result.append("*");
            }
        }
        // 치환된 문자 리턴
        return result.toString();
    }

    /**
     * Replace asta string.
     *
     * @param str    치환대상 문자열
     * @param length 치환시작할 범위숫자
     * @return 치환된 문자열
     * @Method Name : replaceAsta
     * @Method description : 아스타(*) 문자열 치환 ( 치환 범위 설정 )
     * @Author : SWA
     */
    public static String replaceAsta(String str, int length) {
        // 리턴용 스트링버퍼
        StringBuilder result = new StringBuilder();

        // 치환대상 글자가 없는경우
        if (str.isEmpty()) {
            result.append("");
            // 치환대상 글자보다 치환시작할 범위숫자가 큰경우
        } else if (str.trim().length() < length) {
            result.append("");
            // 치환시작
        } else {
            // 치환 시작할 범위숫자까지의 글자
            result.append(str.substring(0, length));
            // 치환 시작 범위숫자부터 글자 마지막까지 치환
            int len = str.length();
            for (int i = length; i < len; i++) {
                result.append("*");
            }
        }
        // 치환된 문자 리턴
        return result.toString();

    }

    /**
     * Validation check string.
     *
     * @param str  the str
     * @param name the name
     */
    public static void validationCheckString(String str, String name) {

        if (StringUtils.isEmpty(str)) {
            throw new BusinessException(name + " 값이 없습니다.");
        }
    }

    /**
     * Validation check html tag.
     *
     * @param tag the tag
     */
    public static void validationCheckHtmlTag(final String tag) {
        Pattern pattern = Pattern.compile(HTML_TAG_FORMAT_PATTERN);

        Matcher matcher = pattern.matcher(tag);

        if (!matcher.matches()) {
            throw new BusinessException("시스템 오류입니다. 담당자에게 문의하시기 바랍니다.");
        }
    }

    /**
     * Random pw string.
     *
     * @return the string
     */
    public static String randomPw() {
        char[] pwCollection = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
                'v', 'w', 'x', 'y', 'z', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')' };// 배열에 선언

        StringBuilder ranPw = new StringBuilder();
        for (int i = 0; i < 8; i++) {
            int selectRandomPw = (int) (Math.random() * (pwCollection.length));// Math.rondom()은 0.0이상 1.0미만의 난수를 생성해
                                                                               // 준다.
            ranPw.append(pwCollection[selectRandomPw]);
        }
        return ranPw.toString();
    }

    /**
     * Null to zero string.
     *
     * @param str the str
     * @return string
     * @Method Name : nullToZero
     * @Method description : 널 값을 0으로 반환한다.
     * @Author : SWA
     */
    public static String nullToZero(String str) {
        String sRtnStr;
        if (str == null || "".equals(str)) {
            sRtnStr = "0";
        } else {
            sRtnStr = str;
        }
        return sRtnStr;
    }

    /**
     * Null to string string.
     *
     * @param str the str
     * @return string
     * @Method Name : nullToString
     * @Method description : str이 null이면 "", 아니면 str 반환.
     * @Author : SWA
     */
    public static String nullToString(String str) {
        return StringUtils.defaultString(str);
    }

    /**
     * Str blank remove string.
     *
     * @param str the str
     * @return string
     * @Method Name : strBlankRemove
     * @Method description : 문자열 중 공백 문자가 있으면 모두 제거
     * @Author : SWA
     */
    public static String strBlankRemove(String str) {
        return StringUtils.deleteWhitespace(str);
    }

    /**
     * Is not empty boolean.
     *
     * @param str the str
     * @return boolean
     * @Method Name : isNotEmpty
     * @Method description : str이 null이 아니거나 길이가 0이 아니면 true 반환.
     * @Author : SWA
     */
    public static boolean isNotEmpty(String str) {
        return StringUtils.isNotEmpty(str);
    }

    /**
     * Ltrim string.
     *
     * @param str the str
     * @return string
     * @Method Name : ltrim
     * @Method description : 문자열 좌측의 공백을 제거하는 메소드
     * @Author : SWA
     */
    public static String ltrim(String str) {
        int len = str.length();
        int idx = 0;

        while ((idx < len) && (str.charAt(idx) <= ' ')) {
            idx++;
        }

        return str.substring(idx, len);
    }

    /**
     * Rtrim string.
     *
     * @param str the str
     * @return string
     * @Method Name : rtrim
     * @Method description : 문자열 우측의 공백을 제거하는 메소드
     * @Author : SWA
     */
    public static String rtrim(String str) {
        int len = str.length();

        while ((0 < len) && (str.charAt(len - 1) <= ' ')) {
            len--;
        }

        return str.substring(0, len);
    }

    /**
     * The entry point of application.
     *
     * @param args the input arguments
     * @Method Name : main
     * @Method description :
     * @Author : SWA
     */
    public static void main(String[] args) {


        String certNumber = StringUtil.numberGen(6,2);
        logger.debug("certNumber >>>>> " + certNumber);

    }

    /**
     * Convert object to map map.
     *
     * @param obj              the obj
     * @return the map
     */
//PEK. 20200207 Map 추가
    public static Map<String, Object> convertObjectToMap(Object obj, @Nullable String... ignoreProperties) {
        return StringUtil.convertObjectToMap(obj, null, ignoreProperties);
    }
    public static Map<String, Object> convertObjectToMap(Object obj, Map<String, Object> map, @Nullable String... ignoreProperties) {
        return StringUtil.convertObjectToMap(obj, map, false, ignoreProperties);
    }
    public static Map<String, Object> convertObjectToMap(Object obj, boolean listOnly, @Nullable String... ignoreProperties) {
        return StringUtil.convertObjectToMap(obj, null, listOnly, ignoreProperties);
    }

    /**
     * Convert object to map map.
     *
     * @param obj              the obj
     * @param map              the map
     * @param ignoreProperties the ignore properties
     * @return the map
     */
//PEK. 20200207 Map 추가
//PEK. 20200323 제외 or 포함여부 추가
    public static Map<String, Object> convertObjectToMap(Object obj, @Nullable Map<String, Object> map, boolean listOnly, @Nullable String... ignoreProperties) {
        //PEK. 20200207 예외항목 추가
        if(map==null){map = new HashMap<>();}
        if(obj==null){return map;}
        Optional<List<String>> ignoreList = Optional.ofNullable(ignoreProperties!=null?Arrays.asList(ignoreProperties):null);
        //
        for (Field field: StringUtil.getAllFields(obj.getClass(), true)) {
            //PEK. 20200207 예외항목 추가
            //PEK. 20200323 제외 or 포함여부 추가
            if(ignoreList.filter(l->listOnly!=l.contains(field.getName())).isPresent()) {continue;}
            //
            field.setAccessible(true);
            try {
                //PEK. 20200323 easymap케이스 추가
                map.put(map instanceof EasyHashMap ? StringUtil.camelToUnderscore(field.getName()) : field.getName(), field.get(obj));
            } catch (Exception e) {
                logger.error(e.getMessage());
            }
        }
        return map;
    }

    private static List<Field> getAllFields(Class<?> clazz, boolean addSuper) {
        List<Field> fields = new ArrayList<>();
        fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        if(addSuper && clazz.getSuperclass()!=null) {
            fields.addAll(StringUtil.getAllFields(clazz.getSuperclass(), addSuper));
        }
        //
        return fields;
    }

    /**
     * Convert map to object object.
     *
     * @param map              the map
     * @param obj              the obj
     * @param ignoreProperties the ignore properties
     * @return the object
     */
    public static <T> T convertMapToObject(Map<String, Object> map, T obj, @Nullable String... ignoreProperties) {
        return StringUtil.convertMapToObject(map, obj, false, ignoreProperties);
    }
//PEK. 20200323 제외 or 포함여부 추가
    public static <T> T convertMapToObject(Map<String, Object> map, T obj, boolean listOnly, @Nullable String... ignoreProperties) {
        //PEK. 20200207 예외항목 추가
        if(obj==null || map==null) {
            return obj;
        }
        Optional<List<String>> ignoreList = Optional.ofNullable(ignoreProperties!=null?Arrays.asList(ignoreProperties):null);
        List<Field> fields = StringUtil.getAllFields(obj.getClass(), true);
        for (String key : map.keySet()) {
            //PEK. 20200207 예외항목 추가
            //PEK. 20200323 제외 or 포함여부 추가
            if(ignoreList.filter(l->listOnly!=l.contains(key)).isPresent()) {continue;}
            fields.stream().filter(f->f.getName().equals(key)).findFirst().ifPresent(f->{
                try {f.setAccessible(true); f.set(obj, map.get(key));} catch(IllegalAccessException e) {logger.error(e.getMessage());}
            });
        }
        return obj;
    }

    /**
     * Convert map to object t.
     *
     * @param <T>              the type parameter
     * @param map              the map
     * @param clazz            the clazz
     * @param ignoreProperties the ignore properties
     * @return the t
     */
//PEK. 20200207 Class 추가
    public static <T> T convertMapToObject(Map<String, Object> map, Class<T> clazz, @Nullable String... ignoreProperties) {
        return StringUtil.convertMapToObject(map, clazz, false, ignoreProperties);
    }
    public static <T> T convertMapToObject(Map<String, Object> map, Class<T> clazz, boolean listOnly, @Nullable String... ignoreProperties) {
        T obj = null;
        try {
            obj = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            logger.error(e.getMessage());
            //e.printStackTrace();
        }
//        return clazz.cast(StringUtil.convertMapToObject(map, obj, listOnly, ignoreProperties));
        return StringUtil.convertMapToObject(map, obj, listOnly, ignoreProperties);
    }

//PEK. 20200323 BeanUtils.copyProperties
    /**
     * Object를 Object로 복사한다.
     *
     * @return Object
     */
    public static <S, T> T copyProperties(S source, T target, boolean listOnly, @Nullable String... ignoreProperties) {
        //포함인 경우 전체 프로퍼티목록에서 예외항목을 제거하여 다시 재생성한다.
        if(listOnly && ignoreProperties!=null) {
            //포함되어야 할 항목이 아닌 경우만 예외리스트에 추가. BeanUtils.copyProperties는 부모속성까지 copy하므로 예외항목을 부모필드까지 조회하여 넘긴다.
            BeanUtils.copyProperties(source, target, StringUtil.getAllFields(source.getClass(), true).stream()
                    .map(Field::getName).filter(nm->!Arrays.asList(ignoreProperties).contains(nm)).collect(Collectors.toList()).toArray(new String[0]));
        }else {
            BeanUtils.copyProperties(source, target, ignoreProperties);
        }
        return target;
    }
    public static <S, T> T copyProperties(S source, T target, @Nullable String... ignoreProperties) {
        return StringUtil.copyProperties(source, target, false, ignoreProperties);
    }
    public static <S, T> T copyProperties(S source, Class<T> targetCls, boolean listOnly, @Nullable String... ignoreProperties) {
        T target = null;
        try {
            target = targetCls.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            logger.error(e.getMessage());
            //e.printStackTrace();
        }
        //
        return StringUtil.copyProperties(source, target, listOnly, ignoreProperties);
    }
    public static <S, T> T copyProperties(S source, Class<T> targetCls, @Nullable String... ignoreProperties) {
        return StringUtil.copyProperties(source, targetCls, false, ignoreProperties);
    }

//PEK. 20200323 Map to Map
    /**
     * Map을 Map으로 복사한다.
     *
     * @return HashMap
     */
    public static Map<String, Object> putAll(@NonNull Map<String, Object> source, @Nullable Map<String, Object> target, boolean listOnly, @Nullable String... ignoreProperties) {
        if(target==null){target = new HashMap<>();}
        if(source==null){return target;}
        //
        for(String key: source.keySet()) {
            if(ignoreProperties==null || listOnly==org.apache.commons.lang3.ArrayUtils.contains(ignoreProperties, key)) {
                target.put(target instanceof EasyHashMap?StringUtil.camelToUnderscore(key):key, source.get(key));
            }
        }
        return target;
    }
    public static Map<String, Object> putAll(Map<String, Object> source, Map<String, Object> target, @Nullable String... ignoreProperties) {
        return StringUtil.putAll(source, target, false, ignoreProperties);
    }
    public static Map<String, Object> putAll(Map<String, Object> source, boolean listOnly, @Nullable String... ignoreProperties) {
        return StringUtil.putAll(source, null, listOnly, ignoreProperties);
    }
    public static Map<String, Object> putAll(Map<String, Object> source, @Nullable String... ignoreProperties) {
        return StringUtil.putAll(source, null, ignoreProperties);
    }
    /**
     * Map을 Map으로 복사한다.
     * @param "key::val@key::val@..." string value만 가능
     * @return HashMap
     */
    public static Map<String, Object> putAll(String mapSrcStr, Map<String, Object> target) {
        //map을 생성할 데이터가 "key::val@key::val@..."의 구조로 되어있어야 하며 val은 string만 가능하다.
        Map<String, Object> newMap = Arrays.stream(mapSrcStr.split("@"))
                .collect(Collectors.toMap(s->s.trim().split("::")[0], s->{return s.trim().split("::").length>1?s.trim().split("::")[1]:"null";}, (oldVal,newVal)->newVal));
        newMap.replaceAll((k,v)->"null".equals(v)?null:v);
        //
        return StringUtil.putAll(newMap, target);
    }
    /**
     * Map을 Map으로 복사한다.
     * @return HashMap
     */
    public static Map<String, Object> putAll(String mapSrcStr) {
        return StringUtil.putAll(mapSrcStr, null);
    }
    private static String camelToUnderscore(String str) {
        if (StringUtil.isNull(str)){return str;}
        //
        StringBuilder buffer = new StringBuilder();
        for (char ch: str.toCharArray()) {
            if (Character.isUpperCase(ch)) {
                buffer.append('_');
                ch = Character.toLowerCase(ch);
            }
            buffer.append(ch);
        }
        return buffer.toString();
    }

  //PEK. 20200323 List sort
    /**
     * List를 정렬한다.
     *
     * @return List
     */
    public static <T> List<T> listSort(@NonNull List<T> source, @NonNull Comparator<? super T> c) {
        if(CollectionUtils.isEmpty(source)) {return source;}
        //
        source.sort(c);
        return source;
    }
    public static <T> List<T> listSort(@NonNull List<T> source, @NonNull String key, boolean asc) {
        if(CollectionUtils.isEmpty(source)) {return source;}
        //
        if(source.get(0) instanceof Map) {source.sort((a,b)->StringUtil.nvl(((Map<?,?>)a).get(key)).compareTo(StringUtil.nvl(((Map<?,?>)b).get(key)))*(asc?1:-1));}
        else {source.sort((a,b)->StringUtil.nvl(StringUtil.getObjField(a,key)).compareTo(StringUtil.nvl(StringUtil.getObjField(b,key)))*(asc?1:-1));}
        return source;
    }
    public static <T> List<T> listSort(@NonNull List<T> source, @NonNull String key) {
        return StringUtil.listSort(source, key, true);
    }
    private static <T> Object getObjField(T obj, String key) {
        Field field = ReflectionUtils.findField(obj.getClass(), key);
        field.setAccessible(true);
        return ReflectionUtils.getField(field, obj);
    }

  //PEK. 20200323 String to Object
    /**
     * String을 Object로 설정한다.
     *
     * @param "property::value@property::value@..." String속성만 가능
     * @return Object
     */
    public static <T> T setProperties(@NonNull String objPropStr, @NonNull T target) {
        if(target==null || objPropStr==null){return target;}
        //
        return StringUtil.convertMapToObject(StringUtil.putAll(objPropStr), target);
    }
    /**
     * String을 Object로 설정한다.
     *
     * @param "property::value@property::value@..." String속성만 가능
     * @return Object
     */
    public static <T> T setProperties(@NonNull String objPropStr, @NonNull Class<T> clazz) {
        if(clazz==null || objPropStr==null){return null;}
        //
        return StringUtil.convertMapToObject(StringUtil.putAll(objPropStr), clazz);
    }

    /**
     * Object를 String 형으로 변환 한다.
     *
     * @param obj the obj
     * @return string
     */
    public static String convertObjectToString(Object obj) {
        return convertObjectToString(obj, ",");
    }

    /**
     * Object를 String 형으로 변환 한다.
     *
     * @param obj       the obj
     * @param separator the separator
     * @return string
     */
    public static String convertObjectToString(Object obj, String separator) {
        StringBuilder sb = new StringBuilder();
        Field[] fields = obj.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            fields[i].setAccessible(true);
            try {
                sb.append(fields[i].getName()).append("=").append(fields[i].get(obj));
                if(",".equals(separator) && i < (fields.length -1 )){
                    sb.append(separator);
                }else{
                    sb.append(separator);
                }
            } catch (Exception e) {
                logger.error(e.getMessage());
                //e.printStackTrace();
            }
        }
        return sb.toString();
    }


    /**
     * Convert string to object object.
     *
     * @param str the str
     * @param obj the obj
     */
    public static void convertStringToObject(String str, Object obj) {
        HashMap<String, Object> map = new HashMap<>();
        String [] el = str.split(";");
        for (String s : el) {
            int n = s.indexOf("=");
            String k = s.substring(0, n);
            String v = s.substring(n + 1);
            map.put(k, v);
        }
        StringUtil.convertMapToObject(map, obj);
    }


    /**
     * Check mdtr value string.
     *
     * @param checkModel      the check model
     * @param checkProperties the check properties
     * @param propertiesName  the properties name
     * @return the string
     */
    /*
     * <pre> 필수 항목 체크 설명 : 필수값 존재 여부 체크 작성자 : PEK @version  : 2019-11-20 버전 : v.1.0 변경이력
     * 1. 최초 작성 </pre>
     *
     * @param Domain checkDomain: 필수 값 체크할 any model String[] checkProperties: 필수 값을
     * 체크할 프로퍼티 리스트 String[] propertiesName: 필수 값을 체크할 프로퍼티 명 리스트
     *
     * @return String: null Property 정보
     *
     * @author PEK
     *
     * @version 1.0.0
     *
     * @since 2019. 11. 20.
     *
     * @example String nullProperty = StringUtil.checkMdtrValue(samCponRsrv,
     * nullProperty, new
     * String[]{"sellChnlCd","eachCponNo","useDate","corpCd","outletCd","menuCd",
     * "rsrvQty","rsrvAmt","rsrvNo"}, new
     * String[]{"판매 채널 코드","개별 쿠폰 번호","사용 일자","사업장 코드","영업장 코드","메뉴 코드","예약 수량"
     * ,"예약 금액","예약 번호"}); if(!"".equals(nullProperty)){ throw new
     * BusinessException("필수값(" + nullProperty + ")이 없습니다."); }
     */
    public static String checkMdtrValue(Domain checkModel, String[] checkProperties,
            @Nullable String... propertiesName) {
        if (checkModel == null) {
            return "checkModel is null!";
        }
        if (checkProperties == null || checkProperties.length < 1) {
            return "checkProperties is empty!";
        }
        //
        List<String> nameList = (propertiesName != null ? Arrays.asList(propertiesName) : null);
        //
        for (int i = 0; i < checkProperties.length; i++) {
            if ("".equals(checkProperties[i])) {
                continue;
            }
            //
            String methodName = "get" + checkProperties[i].substring(0, 1).toUpperCase()
                    + checkProperties[i].substring(1);
            try {
                Method getMethod = checkModel.getClass().getMethod(methodName);
                if ("".equals(StringUtil.nvl(getMethod.invoke(checkModel)))) {
                    if (nameList != null && nameList.size() > i && !"".equals(nameList.get(i))) {
                        return "필수값(" + nameList.get(i) + ")이 없습니다.";
                    } else {
                        return "필수값(" + checkProperties[i] + ")이 없습니다.";
                    }
                }
            } catch (Exception e) {
                logger.error(e.getMessage());
                //e.printStackTrace();
                return checkProperties[i] + " is N/A!";
            }
        }

        //
        return "";
    }

    /**
     * Check mdtr map value string.
     *
     * @param checkData the check data
     * @param checkKeys the check keys
     * @return the string
     */
    public static String checkMdtrMapValue(Map<String, Object> checkData, String[] checkKeys,
            @Nullable String... keyNames) {
        if (checkData == null) {
            return "checkData is null!";
        }
        if (checkKeys == null || checkKeys.length < 1) {
            return "checkKeys is empty!";
        }
        //
        List<String> nameList = (keyNames != null ? Arrays.asList(keyNames) : null);
        //
        for (int i = 0; i < checkKeys.length; i++) {
            if ("".equals(checkKeys[i])) {
                continue;
            }
            //
            if ("".equals(StringUtil.nvl(checkData.get(checkKeys[i])))) {
                if (nameList != null && nameList.size() > i && !"".equals(nameList.get(i))) {
                    return "필수값(" + nameList.get(i) + ")이 없습니다.";
                } else {
                    return "필수값(" + checkKeys[i] + ")이 없습니다.";
                }
            }
        }

        //
        return "";
    }

    /**
     * The enum Valid data type.
     */
    /*
     * <pre> 유효성 체크 설명 : 전화번호 등 유효성 체크 작성자 : PEK @version  : 2019-11-20 버전 : v.1.0 변경이력
     * 1. 최초 작성 </pre>
     *
     * @param String checkData: 체크할 값 String validDataType: 체크 유형 01 숫자만, 02 영문자만, 03
     * 한글만, 04 영어 & 숫자만, 05 E-Mail, 06 휴대폰, 07 일반전화, 08 주민등록번호, 09 IP 주소, 10
     * 휴대폰(-포함), 11 일반전화(-포함), 12 주민등록번호(-포함)
     *
     * @return boolean
     *
     * @author PEK
     *
     * @version 1.0.0
     *
     * @since 2019. 11. 20.
     */
    @Getter
    public enum ValidDataType{
        /**
         * Numeric valid data type.
         *
         * @설명: 숫자
         */
        NUMERIC("^[0-9]*$"),
        /**
         * non-Numeric valid data type.
         */
        NON_NUMERIC("[^0-9]"),
        /**
         * Alpha valid data type.
         */
        ALPHA("^[a-zA-Z]*$"),
        /**
         * Korean valid data type.
         */
        KOREAN("^[가-힣]*$"),
        /**
         * Alphanum valid data type.
         */
        ALPHA_NUM("^[a-zA-Z0-9]*$"),
        /**
         * Email valid data type.
         */
        EMAIL("^[_a-z0-9-]+(.[_a-z0-9-]+)*@(?:\\w+\\.)+\\w+$"),
        /**
         * Mobno valid data type.
         */
        MOB_NO("^01(?:0|1|[6-9])(?:\\d{3}|\\d{4})\\d{4}$"),
        /**
         * Telno valid data type.
         */
        TEL_NO("^\\d{2.3}\\d{3,4}\\d{4}$"),
        /**
         * Juminno valid data type.
         */
        JUMIN_NO("\\d{6}[1-4]\\d{6}"),
        /**
         * Ipaddr valid data type.
         */
        IP_ADDR("([0-9]{1,3}) \\. ([0-9]{1,3}) \\. ([0-9]{1,3}) \\. ([0-9]{1,3})"),
        /**
         * Mobn ow dash valid data type.
         */
        MOB_NO_W_DASH("^01(?:0|1|[6-9]) - (?:\\d{3}|\\d{4}) - \\d{4}$"),
        /**
         * Teln ow dash valid data type.
         */
        TEL_NO_W_DASH("^\\d{2.3} - \\d{3,4} - \\d{4}$"),
        /**
         * Juminn ow dash valid data type.
         */
        JUMIN_NO_W_DASH("^\\d{6} \\- [1-4]\\d{6}")
        ;

        final private String attr1;

        ValidDataType(String attr1) {
            this.attr1 = attr1;
        }
    }

    /**
     * Is valid data boolean.
     *
     * @param checkData the check data
     * @param checkType the check type
     * @return the boolean
     */
    public static boolean isValidData(String checkData, ValidDataType checkType) {
        if (checkData == null || checkData.length() == 0) {
            logger.debug("checkData가 없습니다");
            return false;
        }
        //
        Pattern pattern = Pattern.compile(checkType.getAttr1());
        Matcher matcher = pattern.matcher(checkData);
        //
        return matcher.matches();
    }

    /*
     * <pre> byte단위 substring
     * 설명 : byte단위 substring
     * 작성자 : PEK
     * @version  : 2019-11-20 버전 : v.1.0 변경이력
     * 1. 최초 작성 </pre>
     * @return String
     * @author PEK
     * @version 1.0.0
     * @since 2019. 11. 20.
     */
    private static String substrB(String source, int size) {
        if("".equals(StringUtil.nvl(source))){
            return "";
        }
        //
        String result = source;
        byte[] bytes = source.getBytes();
        if(bytes.length > size) {
            result = new String(bytes, 0, size);
        }
        return result;
    }
    public static String substrB(String source) {
        return StringUtil.substrB(source, source.length());
    }

    /**
     * Is obj null boolean.
     *
     * @param arg0 the arg 0
     * @return the boolean
     */
    private static boolean isObjNull(Object arg0) {
        return (arg0 == null);
    }

    /**
     * Nvl 2 string.
     *
     * @param arg0 the arg 0
     * @param arg1 the arg 1
     * @return the string
     */
    public static String nvl2(Object arg0, String arg1) {
        if (isObjNull(arg0)) {
            return arg1;
        } else if ("".equals(StringUtil.nvl(arg0, ""))) {
            return arg1;
        } else {
            return String.valueOf(arg0);
        }
    }

    /**
     * Nvl big decimal.
     *
     * @param arg0 the arg 0
     * @param arg1 the arg 1
     * @return big decimal
     */
    public static BigDecimal nvl(Object arg0, BigDecimal arg1) {
        if (isObjNull(arg0)) {
            return arg1;
        } else {
            String strArg0 = String.valueOf(arg0);
            if(isNull(strArg0)) {
                return arg1;
            }

            return new BigDecimal(strArg0);
        }
    }

    /**
     * Is null boolean.
     *
     * @param arg0 the arg 0
     * @return boolean
     */
    public static boolean isNull(String arg0) {
        return (arg0 == null || "".equals(arg0.trim()) || "null".equals(arg0.trim()));
    }
//20200325 object파라메터 추가
    public static boolean isNull(Object arg0) {
        return arg0 == null || StringUtil.isNull(arg0.toString());
    }


    /**
     * 전달된 파라미터에 맞게 난수를 생성한다
     *
     * @param len   : 생성할 난수의 길이
     * @param dupCd : 중복 허용 여부 (1: 중복허용, 2:중복제거) Created by SWA
     * @return the string
     */
    public static String numberGen(int len, int dupCd ) {

        Random rand = new Random();
        StringBuilder numStr = new StringBuilder(); //난수가 저장될 변수

        for(int i=0;i<len;i++) {

            //0~9 까지 난수 생성
            String ran = Integer.toString(rand.nextInt(10));

            if(dupCd==1) {
                //중복 허용시 numStr에 append
                numStr.append(ran);
            }else if(dupCd==2) {
                //중복을 허용하지 않을시 중복된 값이 있는지 검사한다
                if(!numStr.toString().contains(ran)) {
                    //중복된 값이 없으면 numStr에 append
                    numStr.append(ran);
                }else {
                    //생성된 난수가 중복되면 루틴을 다시 실행한다
                    i-=1;
                }
            }
        }
        return numStr.toString();
    }

    /**
     * Get conn ip string.
     *
     * @param request the request
     * @return the string
     */
    public static String getConnIp(HttpServletRequest request){
        String ip = request.getHeader("X-Forwarded-For");

        if (ip == null) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    public static String getConnBrowser(HttpServletRequest request){
        return request.getHeader("User-Agent");
    }

}
