KansujiConverter.java

package com.github.mygreen.cellformatter.callback;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.github.mygreen.cellformatter.lang.ArgUtils;

/**
 * 数字の表現を漢数字に変換する。
 * <p>数字が連続しないと、桁数を考慮した変換はできない。</p>
 * <p>途中に区切り文字などがあると、そこで途切れる。</p>
 * <p>大字の定義:<a href="http://www.benricho.org/kanji/kansuji.html">漢数字と大字〔だいじ〕の書き方</a></p>
 *
 * @since 2.0
 * @author T.TSUCHIE
 *
 */
public class KansujiConverter {

    /**
     * 整数部分の切り出し用正規表現
     */
    protected static final Pattern PATTERN_NUM = Pattern.compile("([\\D]*)([\\d]+)([\\.]{0,1}[.\\s\\w]*)");

    /**
     * 0~9の数字のマップ
     */
    protected String[] numMap = {
        "〇",
        "一",
        "二",
        "三",
        "四",
        "五",
        "六",
        "七",
        "八",
        "九"
    };

    /**
     * 10^4ごとの桁単位のマップ
     * <p><a href="http://www2s.biglobe.ne.jp/~hotori/kazu.html">桁数の名前</a>
     */
    protected String[] digits10Map = {
        "",      // 10^0
        "万",    // 10^4
        "億",    // 10^8
        "兆",    // 10^12
        "京",    // 10^16
    };

    /**
     * 4桁の桁単位のマップ
     */
    protected String[] digits4Map = {
        "",
        "十",
        "百",
        "千",
    };

    /**
     * 文字列を変換する
     * @param value 変換対象の値
     * @param is4YearTerm 4桁の年指定の項の場合
     * @return 変換後の値
     */
    public String convert(final String value, final boolean is4YearTerm) {

        final Matcher matcher = PATTERN_NUM.matcher(value);
        if(!matcher.matches()) {
            // 一致しない場合は、単純に数値の変換
            return replaceSimple(value);
        }

        final String before = matcher.group(1);
        final String num = matcher.group(2);
        final String after = matcher.group(3);

        if(is4YearTerm && num.length() == 4) {
            // 年の桁は、単純に数値変換する
            return replaceSimple(before) + replaceSimple(num) + replaceSimple(after);

        } else {
            return replaceSimple(before) + replaceDisits(num) + replaceSimple(after);

        }

    }

    /**
     * 数字を単純に変換する。
     * @param value 変換対象の文字列
     * @return
     */
    protected String replaceSimple(final String value) {
        String str = value;
        for(int i=0; i < numMap.length; i++) {
            str = str.replaceAll(String.valueOf(i), numMap[i]);
        }

        return str;
    }

    /**
     * 数字を桁数に合わせて変換する。
     * @param value
     * @return
     */
    protected String replaceDisits(final String value) {

        if(value.equals("0")) {
            return numMap[0];
        }

        // 4桁ごとに、分割する。
        final int length = value.length();
        final List<String> split4 = new ArrayList<>();
        for(int i=0; i < length; i=i+4) {

            // 下の桁から切り出す
            int end = length -i;
            int start;
            if(i + 4 < length) {
                start = end - 4;
            } else {
                start = 0;
            }
            String item = value.substring(start, end);
            split4.add(item);
        }

        // 4桁ごとに変換を行う。
        final List<String> digits = new ArrayList<>();
        for(int i=0; i < split4.size(); i++) {
            String item = split4.get(i);
            item = replace4Digits(item) + digits10Map[i];

            digits.add(item);
        }

        /*
         * 文字列に直す。
         * ・桁数が逆順になっているので、戻し結合する。
         */
        Collections.reverse(digits);
        StringBuilder sb = new StringBuilder();
        for(String item : digits) {
            sb.append(item);
        }

        return sb.toString();
    }

    /**
     * 4桁以下の数字を、漢数字に変換する。
     * @param value
     * @return
     */
    protected String replace4Digits(final String value) {

        // 桁ごとに変換を行う。
        final int length = value.length();
        final List<String> digits = new ArrayList<>();

        for(int i=0; i < length; i++) {
            // 下の桁から処理する
            final char c = value.charAt(length-i-1);
            if(c == '0') {
                continue;

            }

            String item;
            if(c == '1' && i > 0) {
                // 10の位以上で、かつ1の場合、数字部分は省略し、桁数のみにする。
                item = digits4Map[i];
            } else {
                item = replaceSimple(String.valueOf(c)) + digits4Map[i];
            }

            digits.add(item);

        }

        /*
         * 文字列に直す。
         * ・桁数が逆順になっているので、戻し結合する。
         */
        Collections.reverse(digits);
        StringBuilder sb = new StringBuilder();
        for(String item : digits) {
            sb.append(item);
        }

        return sb.toString();

    }

    /**
     * 0~9の数字のマップを設定する
     * @param numMap 配列のインデックスに対応する数字のマップ
     * @throws IllegalArgumentException {@literal numMap size != 10}
     */
    public void setNumMap(String[] numMap) {
        ArgUtils.notEmpty(numMap, "numMap");

        if(numMap.length != 10) {
            throw new IllegalArgumentException("numMap length should be 10.");
        }

        this.numMap = numMap;
    }

    /**
     * 10^4ごとの桁単位のマップ
     * @param digits10Map 万、億などの桁のマップ。
     * @throws IllegalArgumentException {@literal digits10Map size != 5}
     */
    public void setDigits10Map(String[] digits10Map) {
        ArgUtils.notEmpty(digits10Map, "digits10Map");

        if(digits10Map.length != 5) {
            throw new IllegalArgumentException("digits10Map length should be 5.");
        }

        this.digits10Map = digits10Map;
    }

    /**
     * 4桁の桁単位のマップ
     * @param digits4Map 十、百などの桁のマップ
     * @throws IllegalArgumentException {@literal digits4Map size != 4}
     */
    public void setDigits4Map(String[] digits4Map) {

        ArgUtils.notEmpty(digits4Map, "digits4Map");

        if(digits4Map.length != 4) {
            throw new IllegalArgumentException("digits4Map length should be 4.");
        }

        this.digits4Map = digits4Map;
    }

}