/*
 * StringUtil.java
 * 
 * Author: http://www.hrtdotnet.jp/
 * 
 */
package jp.hrtdotnet.java.util;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * 文字列のチェックをするクラスです。
 * 
 * @version 2.0
 * @author hrtdotnet.jp
 * <br>http://www.hrtdotnet.jp/
 * @since 2.0
 */
public class StringUtil {

    private static final int UNICODE_MULTIBYTE = 1;
    private static final int UNICODE_ALL = 2;

    /**
     * char文字が英字かどうかの判定をします。
     * 英字は半角英字[a-zA-Z]と全角英字[ａ-ｚＡ-Ｚ]とします。
     * 
     * @param c 判定する文字
     * @return 英字であればtrue、そうでなければfalse
     * @since 2.0
     */
    public static boolean isAlphabet(char c) {
        if ((c >= '\u0041' && c <= '\u005A') || (c >= '\u0061' && c <= '\u007A')
                        || (c >= '\uFF21' && c <= '\uFF3A') || (c >= '\uFF41' && c <= '\uFF5A')) {
                return true;
        }
        return false;
    }


    /**
     * 文字列が英字かどうか判定します。
     * 英字は半角英字[a-zA-Z]と全角英字[ａ-ｚＡ-Ｚ]とします。
     * 
     * @param str 判定する文字列
     * @return 英字であればtrue、そうでなければfalse
     * @since 2.0
     */
    public static boolean isAlphabet(String str) {
        if (str == null || str.length() < 1) {
            return false;
        }
        for (int i = 0; i < str.length(); i++) {
            if(!isAlphabet(str.charAt(i))){
                return false;
            }
        }
        return true;
    }


    /**
     * 文字列の中に指定した１文字が含まれているかを文字の大小の区別して判断します。
     * １文字は大小を区別されます。
     * 文字列がnullや空文字の場合はfalseを返します。
     * 
     * @param str 対象の文字列
     * @param c 含まれるかを判定する文字
     * @return 含まれていればtrue、含まれていなければfalse
     * @since 2.0
     */
    public static boolean includeChar(String str, char c){
        return includeChar(str, c, true);
    }


    /**
     * 文字列の中に指定した１文字が含まれているかを判断します。
     * 大小を区別をするかしないかを指定することができます。
     * 文字列がnullや空文字の場合はfalseを返します。
     * 
     * @param str 対象の文字列
     * @param c 含まれるかを判定する文字
     * @param em 大小区別をする場合はtrue、大小区別しないのであればfalse
     * @return 含まれていればtrue、含まれていなければfalse
     * @since 2.0
     */
    public static boolean includeChar(String str, char c, boolean em){
        if (str == null || str.length() < 1) {
            return false;
        }
        
        char c1;
        char c2;        
        for (int i = 0; i < str.length(); i++) {
            
            // 大小を区別しないで比較
            if (em == false) {
                c1 = Character.toLowerCase(str.charAt(i));
                c2 = Character.toLowerCase(c);
                if (c1 == c2) {
                    return true;
                }
            // 大小を区別して比較
            } else {
                if (str.charAt(i) == c) {
                    return true;
                }
            }            
        }
        return false;
    }
    

    /**
     * 文字列の前方が半角スペースかどうかを判定します。
     * 
     * @param str 対象の文字列
     * @return 前方が半角スペースならばtrue、そうでなければfalse
     * @since 2.0
     */
    public static boolean checkFrontSpace(String str){
        return checkFrontSpace(str, false);
    }

    
    /**
     * 文字列の前方が全半角空白スペースかどうかを判定します。
     * 全角スペースの判定をするかどうかを指定することができます。
     * 
     * @param str 対象の文字列
     * @param em 全半角はtrue、半角のみはfalse
     * @return 前方がスペースならばtrue、そうでなければfalse
     * @since 2.0
     */
    public static boolean checkFrontSpace(String str, boolean em){
        
        // 文字列がnullや空文字であれば、チェックはしないでfalseを返す
        if (str == null || str.equals("")) {
            return false;
        }

        // 半角スペースの判定
        if (str.substring(0, 1).equals(" ")) {
            return true;
        }

        // 全角スペース判定
        if (em == true) {
            if (str.substring(0, 1).equals("　")) {
                return true;
            }
        }
        
        return false;
    }

    
    /**
     * 文字列の後方が半角スペースかどうかを判定します。
     * 
     * @param str 対象の文字列
     * @return 後方が半角スペースならばtrue、そうでなければfalse
     * @since 2.0
     */
    public static boolean checkRearSpace(String str){
        return checkRearSpace(str, false);
    }


    /**
     * 文字列の後方が全半角空白スペースかどうかを判定します。
     * 全角スペースの判定をするかどうかを指定することができます。
     * 
     * @param str 対象の文字列
     * @param em 全半角はtrue、半角のみはfalse
     * @return 後方がスペースならばtrue、そうでなければfalse
     * @since 2.0
     */
    public static boolean checkRearSpace(String str, boolean em){        
        // 文字列がnullや空文字であれば、チェックはしないでfalseを返す
        if (str == null || str.equals("")) {
            return false;
        }
        // 半角スペースの判定
        if (str.substring(str.length() - 1).equals(" ")) {
            return true;
        }
        // 全角スペース判定
        if (em == true) {
            if (str.substring(str.length() - 1).equals("　")) {
                return true;
            }
        }
        return false;
    }

    /**
     * 全角文字を半角文字に変換します。
     * 半角文字の場合はそのまま返されます。
     * 半角に変換できない文字を指定することはできません。
     * <br><br>
     * 現バージョンでは数値と「−」のみ対応しています。
     * 
     * @param c 半角に変換する文字
     * @return 半角に変換した文字
     * @throws IllegalArgumentException
     *          半角に変換できない文字が指定された場合
     * @since 2.0
     */
    public static char toSingleByteChar(char c) {
        // 半角文字の場合はそのまま返す
        if (Character.toString(c).getBytes().length == 1){
            return c;
        }        
        // 全角数値を半角数値に変換
        if ((c >= '\uFF10' && c <= '\uFF19') || (c >= '\uFF21' && c <= '\uFF3A') || (c >= '\uFF41' && c <= '\uFF5A')) {
            return (char) (c - '\uFF10' + '\u0030');
        } else if(c == '\u2212' || c == '\uFF0D') {
            return '-';
        }else {
            throw new IllegalArgumentException("Char can't convert single-byte.");
        }
    }

    /**
     * 文字列中のマルチバイト文字をシングルバイト文字に変換します。
     * 半角に変換できる文字はtoSingleByteChar(char)に依存します。
     * シングルバイト文字に変換できない文字列を指定することはできません。
     * 
     * @param str 半角に変換する文字列
     * @return 半角に変換された文字列
     * @throws NullPointerException
     *          strがnullの場合
     * @throws IllegalArgumentException
     *          strが半角に変換できない文字列の場合
     * @since 2.0
     */
    public static String toSingleByteString(String str) {
        if (str == null) {
            throw new NullPointerException("Argument is null.");
        } else if (str.length() == 0) {
            throw new IllegalArgumentException("Argument is empty.");
        }
        char[] c = str.toCharArray();
        for (int i = 0; i < c.length; i++) {
            c[i] = toSingleByteChar(c[i]);
        }
        return new String(c);
    }

    /**
     * 文字列中のマルチバイト文字をUnicode 16進コードに変換します。
     * 変換された文字列は新規に作成されたオブジェクトです。
     * 
     * @param str 変換する文字列
     * @return Unicode 16進コードに変換された文字列
     * @since 2.0
     */
    public static String toUnicodeHexMultiByte(String str) {
        return StringUtil.toUnicodeHex(str, UNICODE_MULTIBYTE);
    }

    /**
     * 文字列をUnicode 16進コードに変換します。
     * 変換された文字列は新規に作成されたオブジェクトです。
     * 
     * @param str 変換する文字列
     * @return Unicode 16進コードに変換された文字列
     * @since 2.0
     */
    public static String toUnicodeHex(String str) {
        return StringUtil.toUnicodeHex(str, UNICODE_ALL);
    }

    /**
     * Unicode 16進コードを文字列に変換します。
     * 変換された文字列は新規に作成されたオブジェクトです。
     * 
     * @param str 変換するUnicode 16進コード
     * @return 変換された文字列
     * @since 1.0
     */
    public static String toUnicodeString(String str) {
        
        if (str == null) {
            return null;
        }
        if (str.length() < 1) {
            return "";
        }
        
        StringBuffer sb = new StringBuffer();
        int length = str.length();
        for (int i = 0; i < length; i++) {
            if ('\\' == str.charAt(i)) {
                int i2 = i + 1;
                if ('u' == str.charAt(i2) || 'U' == str.charAt(i2)) {
                    try {
                        i2++;
                        while ('u' == str.charAt(i2) || 'U' == str.charAt(i2)) {
                            i2++;
                        }
                        sb.append((char) Integer.parseInt(str.substring(i2, i2 + 4), 16));
                        i = i2 + 3;
                    } catch (Exception e) {
                        sb.append(str.charAt(i));
                    }
                } else {
                    sb.append(str.charAt(i));                    
                }
            } else {
                sb.append(str.charAt(i));
            }
        }        
        return sb.toString();
    }

    /**
     * CSV形式の文字列を分割します。
     * ダブルクォーテーション（"）で囲まれた文字列の区切り文字は無視されます。
     * なおダブルクォーテーション（"）は区切り文字に指定することはできません。
     * 
     * @param line CSV形式の文字列
     * @param delim 区切り文字
     * @return 区切られた文字列の配列
     * @since 1.0
     */
    public static String[] csvSplit(String line, String delim) {
        return StringUtil.csvSplit(line, delim, '"');
    }

    /**
     * CSV形式の文字列を分割します。
     * 囲い文字で囲まれた文字列の区切り文字は無視されます。
     * なお区切り文字と囲い文字を等価の文字を指定することはできません。
     * 
     * @param line CSV形式の文字列
     * @param delim 区切り文字
     * @param quot 囲い文字
     * @return 区切られた文字列の配列
     * @since 2.0
     */
    public static String[] csvSplit(String line, String delim, char quot) {
        
        if (line == null) {
            return null;
        }
        
        if (delim == null || delim.length() < 1) {
            throw new IllegalArgumentException();
        }
        
        if (line.indexOf(quot) == -1) {
            return line.split(delim);
        }
        
        int index = 0;        
        int delimX = line.indexOf(delim);
        int quotX = line.indexOf(quot);
        final int delimLength = delim.length();
        final int quotLength = 1;
        ArrayList list = new ArrayList();
        
        while(true) {
            
            if (delimX == -1) {
                list.add(StringUtil.csvConvert(line.substring(index), quot));
                break;
            }
            
            if (delimX < quotX || quotX == -1) {
                list.add(StringUtil.csvConvert(line.substring(index, delimX), quot));
                index = delimX + delimLength;
                delimX = line.indexOf(delim, index);
                quotX = line.indexOf(quot, index);
                continue;
            }
            
            quotX = line.indexOf(quot, quotX + quotLength);
            if (quotX == -1) {
                list.add(StringUtil.csvConvert(line.substring(index), quot));
                break;
            }
            delimX = line.indexOf(delim, quotX + quotLength);
            quotX = line.indexOf(quot, quotX + quotLength);
            
        }
        
        return (String[]) list.toArray(new String[]{});
    }

    /**
     * 正規表現を使わないで文字列を区切り文字で区切り、文字列配列として取得します。
     * 対象の文字列に空文字が指定された場合は長さ０の文字列配列を取得します。
     * 
     * @param str 文字列
     * @param delim 区切り文字列
     * @return 区切った文字列配列
     * @throws NullPointerException
     *          文字列、もしくは対象の文字列が null の場合
     * @since 2.0
     */
    public static String[] split(String str, String delim) {
        if (str == null || delim == null) {
            throw new NullPointerException("The values of argument are null.");
        }
        List l = new ArrayList();
        StringTokenizer st = new StringTokenizer(str, delim);
        while(st.hasMoreTokens()) {
            l.add(st.nextToken());
        }
        return (String[])l.toArray(new String[]{});
    }

    // Unicode 16 進数変換
    private static String toUnicodeHex(String str, int stat) {        
        if (str == null) {
            return null;
        }
        
        if (str.length() < 1) {
            return "";
        }
        
        char[] c = str.toCharArray();
        StringBuffer ret = new StringBuffer();
        for (int i = 0; i < c.length; i++) {
            if(c[i] <= 127){
                if(stat == UNICODE_ALL){
                    ret.append("\\u");
                    ret.append(StringUtil.toHexFromBytes(c[i]));
                } else if(stat == UNICODE_MULTIBYTE){
                    ret.append(c[i]);
                }
            } else {
                ret.append("\\u");
                ret.append(StringUtil.toHexFromBytes(c[i]));
            }
        }
        
        return ret.toString();
    }

    private static StringBuffer toHexFromBytes(char c) {
        StringBuffer sb = new StringBuffer(Integer.toHexString(c));
        for (int i = 0; sb.length() < 4; i++) {
            sb.insert(0, 0);
        }
        return sb;
    }

    private static String csvConvert(String str, char quot) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c == quot) {
                if (i == str.length() - 1) {
                } else {
                    buf.append(str.charAt(++i));
                }
            } else {
                buf.append(c);
            }
        }
        
        return buf.toString();
    }


    /**
     * int型整数を32ビットの2進数のビット文字列に変換します。
     * 
     * @param number ビット文字列に変換する整数
     * @return 変換後の文字列
     * @since 2.0
     */
    public static String toBinaryString(int number) {
        StringBuffer sb = new StringBuffer(Integer.toBinaryString(number));
        for (int j = sb.length(); j < 32; j++) {
            sb.insert(0, 0);
        }
        return sb.toString();
    }


    /**
     * 0以上の整数の桁が指定桁数まで達していない場合に先頭に0をつけて補完します。
     * 整数の桁数が指定した桁数よりも大きい場合は何もしません。
     * 
     * @param number 0を自動補完する整数
     * @param digit 0を補完する桁数
     * @return 0の補完後の文字列
     * @throws IllegalArgumentException
     *          numberもしくはdigitが負の場合
     * @since 2.0
     */
    public static String completeZero(int number, int digit) {
        if (number < 0 || digit < 0) {
            throw new IllegalArgumentException("The argument can not specify a negative value.");
        }
        StringBuffer sb = new StringBuffer(Integer.toString(number));
        for (int j = sb.length(); j < digit; j++) {
            sb.insert(0, 0);
        }
        return sb.toString();
    }

    

}
