MessageFormatter.java

package com.github.mygreen.messageformatter;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

/**
 * メッセージを組み立てフォーマットするクラス。
 *
 *
 * @author T.TSUCHIE
 *
 */
public class MessageFormatter {

    /**
     * メッセージソース
     */
    @Getter
    private final MessageSource messageSource;

    /**
     * 名前付き変数のメッセージをフォーマットする
     */
    @Getter
    private final MessageInterpolator messageInterpolator;

    /**
     * メッセージパラメータのクラス型や列挙型のフォーマッター
     */
    @Getter
    private final ParameterFormatter parameterFormatter;

    /**
     * インスタンスを作成します。
     * @param messageSource メッセージソース
     * @param messageInterpolator 名前付き変数のメッセージのフォーマッタです。
     */
    public MessageFormatter(@NonNull MessageSource messageSource, @NonNull MessageInterpolator messageInterpolator) {
        this(messageSource, messageInterpolator, new ParameterFormatter());
    }

    /**
     * インスタンスを作成します。
     * @param messageSource メッセージソース
     * @param messageInterpolator 名前付き変数のメッセージのフォーマッタです。
     * @param parameterFormatter メッセージ変数中のクラス型や列挙型のフォーマッターです
     */
    public MessageFormatter(@NonNull MessageSource messageSource, @NonNull MessageInterpolator messageInterpolator,
            @NonNull ParameterFormatter parameterFormatter) {
        this.messageSource = messageSource;
        this.messageInterpolator = messageInterpolator;
        this.parameterFormatter = parameterFormatter;
    }

    /**
     * メッセージコード(キー)を指定し、メッセージの組み立ての開始します。
     * @param code メッセージコード(メッセージキー)
     * @return 変数を組み立てるためのビルダー。
     */
    public Builder create(final String code) {
        return create(code, null);
    }

    /**
     * メッセージコード(キー)を指定し、メッセージの組み立ての開始します。
     * @param code メッセージコード(メッセージキー)
     * @param locale ロケールを指定します。
     * @return 変数を組み立てるためのビルダー。
     */
    public Builder create(final String code, final Locale locale) {
        Assert.hasLength(code, "code should not be empty.");

        return new Builder(messageSource, messageInterpolator, parameterFormatter, code, locale);
    }

    /**
     * メッセージ中に埋め込むパラメータを組み立てるビルダークラス。
     *
     * @author T.TSUCHIE
     *
     */
    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    public static class Builder {

        private final MessageSource messageSource;

        private final MessageInterpolator messageInterpolator;

        private final ParameterFormatter parameterFormatter;

        private final String code;

        private final Locale locale;

        /**
         * 組み立てた変数のマップ
         */
        private Map<String, Object> vars = new HashMap<>();

        /**
         * メッセージパラメータを追加する。
         * @param key 変数名
         * @param value 値
         * @return 自身のインスタンス
         */
        public Builder param(final String key, final Object value) {
            vars.put(key, value);
            return this;
        }

        /**
         * メッセージパラメータとして配列を追加する。
         * @param key パラメータ名
         * @param values 値
         * @return 自身のインスタンス
         */
        public Builder param(final String key, final Object... values) {
            vars.put(key, values);
            return this;
        }

        /**
         * メッセージパラメータとしてアノテーション名を追加する。
         * @param key パラメータ名
         * @param annoClass アノテーションのクラス名
         * @return 自身のインスタンス
         */
        public Builder paramWithAnno(final String key, final Class<? extends Annotation> annoClass) {
            return param(key, parameterFormatter.formatWithAnno(annoClass));
        }

        /**
         * メッセージパラメータとしてクラス名を追加する。
         * <p>クラス名は、FQCNの形式</p>
         * @param key パラメータ名
         * @param clazz クラスタイプ
         * @return 自身のインスタンス
         */
        public Builder paramWithClass(final String key, final Class<?> clazz) {
            return param(key, parameterFormatter.formatWithClass(clazz));
        }

        /**
         * メッセージパラメータとしてクラス名を追加する。
         * <p>クラス名は、FQCNの形式</p>
         * @param key パラメータ名
         * @param classes クラスタイプ
         * @return 自身のインスタンス
         */
        public Builder paramWithClass(final String key, final Class<?>... classes) {
            return param(key, parameterFormatter.formatWithClasses(classes));
        }

        /**
         * メッセージパラメータとして列挙型を追加する。
         * @param key パラメータ名
         * @param enums 列挙型の要素
         * @return 自身のインスタンス
         */
        public Builder paramWithEnum(final String key, final Enum<?> enums) {
            return param(key, parameterFormatter.formatWithEnum(enums));

        }

        /**
         * メッセージをフォーマットして値を取得します。
         * <p>変換したメッセージに対しても再帰的に処理しません。</p>
         * @return フォーマットしたメッセージ
         * @throws IllegalArgumentException 指定したメッセージコードが見つからない場合
         */
        public String format() {

            final MessageSourceAccessor msa = new MessageSourceAccessor(messageSource, locale);
            final String message = msa.getMessage(code);
            return messageInterpolator.interpolate(message, vars, msa);
        }

        /**
         * メッセージをフォーマットして値を取得します。
         * <p>変換したメッセージに対しても再帰的に処理します</p>
         * @return フォーマットしたメッセージ
         * @throws NoSuchMessageException 指定したメッセージコードが見つからない場合
         */
        public String formatRecursively() {

            final MessageSourceAccessor msa = new MessageSourceAccessor(messageSource, locale);
            final String message = msa.getMessage(code);
            return messageInterpolator.interpolate(message, vars, 0, msa);
        }

        /**
         * メッセージをフォーマットして値を取得します。
         * <p>変換したメッセージに対しても再帰的に処理します</p>
         * @param maxRecursion メッセージを再帰的に処理する最大回数。0以下を指定すると再帰回数の制限はありません。
         * @return フォーマットしたメッセージ
         * @throws NoSuchMessageException 指定したメッセージコードが見つからない場合
         */
        public String formatRecursively(final int maxRecursion) {

            final MessageSourceAccessor msa = new MessageSourceAccessor(messageSource, locale);
            final String message = msa.getMessage(code);
            return messageInterpolator.interpolate(message, vars, maxRecursion, msa);
        }

    }

}