ResourceBundleMessageResolver.java

package com.gh.mygreen.xlsmapper.localization;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;

import com.gh.mygreen.xlsmapper.util.ArgUtils;

/**
 * {@link ResourceBundle}を元にメッセージを解決するクラス。
 * <p>クラスパスのルートにリソース名が{@literal SheetValidationMessages}のプロパティファイルを配置していると自動的に読み込みます。</p>
 * <p>デフォルトでは、{@link ResourceBundleMessageResolver#DEFAULT_MESSAGE}に配置されているリソースファイルを読み込みます。</p>
 * 
 * @version 2.0
 * @author T.TSUCHIE
 *
 */
public class ResourceBundleMessageResolver implements MessageResolver {
    
    /**
     * デフォルトのメッセージソースのパス
     */
    public static final String DEFAULT_MESSAGE = "com.gh.mygreen.xlsmapper.localization.SheetValidationMessages";
    
    private final Map<ResourceBundle, List<String>> messageBundleKeys = new HashMap<ResourceBundle, List<String>>(8);
    
    private final LinkedList<ResourceBundle> messageBundles = new LinkedList<ResourceBundle>();
    
    /**
     * メッセージリソースのパスを指定して、インスタンスを作成します。
     * @param baseName メッセージリソースのパス。
     * @param appendUserResource クラスパスのルートにあるユーザ定義のメッセージソースも読み込むかどうか指定します。
     *      引数baseNameの値が {@literal sample.SampleMessages}のとき、クラスパスのルート上にある「SampleMessages」を読み込みます。
     * @throws IllegalArgumentException {@literal baseName is null or empty.}
     */
    public ResourceBundleMessageResolver(final String baseName, final boolean appendUserResource) {
        ArgUtils.notEmpty(baseName, "baseName");
        
        addResourceBundle(ResourceBundle.getBundle(baseName, new EncodingControl("UTF-8")));
        
        // ユーザ定義のリソースを読み込む
        if(appendUserResource) {
            // リソース名の切り出し
            final int index = baseName.lastIndexOf(".");
            if(index > 0) {
                final String userName = baseName.substring(index+1);
                try {
                    addResourceBundle(ResourceBundle.getBundle(userName, new EncodingControl("UTF-8")));
                } catch(Throwable e) { }
            }
        }
        
    }
    
    /**
     * デフォルトのコンストラクタ。
     * <p>デフォルトのメッセージソース{@link #DEFAULT_MESSAGE}が自動的に読み込まれます。</p>
     * 
     */
    public ResourceBundleMessageResolver() {
        this(DEFAULT_MESSAGE, true);
    }
    
    /**
     * 独自のメッセージソースを指定してインスタンスを作成する。
     * <p>デフォルトのメッセージソース{@link #DEFAULT_MESSAGE}が自動的に読み込まれます。</p>
     * @param resourceBundle 独自のメッセージメース
     * @throws IllegalArgumentException resourceBundle is null.
     */
    public ResourceBundleMessageResolver(final ResourceBundle resourceBundle) {
        this();
        Objects.requireNonNull(resourceBundle, "resourceBundle should no be null");
        addResourceBundle(resourceBundle);
    }
    
    /**
     * {@inheritDoc}
     */
    public Optional<String> getMessage(final String code) {
        for(final ResourceBundle bundle : messageBundles) {
            final List<String> keys = messageBundleKeys.get(bundle);
            if(keys.contains(code)) {
                try {
                    return Optional.of(bundle.getString(code));
                } catch(MissingResourceException e) {
                    return Optional.empty();
                }
            }
        }
        
        return Optional.empty();
    }
    
    /**
     * メッセージソースを追加します。
     * @param resourceBundle 追加するメッセージソース。
     * @return 既に追加済みの場合はfalseを返します。
     * @throws IllegalArgumentException resourceBundle is null.
     */
    public final boolean addResourceBundle(final ResourceBundle resourceBundle) {
        Objects.requireNonNull(resourceBundle, "resourceBundle should not be null.");
        
        if(messageBundles.contains(resourceBundle)) {
            return false;
        }
        
        messageBundles.addFirst(resourceBundle);
        final List<String> keys = new ArrayList<String>();
        
        for(final Enumeration<String> keysEnum = resourceBundle.getKeys(); keysEnum.hasMoreElements();) {
            keys.add(keysEnum.nextElement());
        }
        
        messageBundleKeys.put(resourceBundle, keys);
        
        return true;
    }
    
   /**
     * メッセージソースを削除する。
     * @param resourceBundle 削除対象のメッセージソース
     * @return 登録されているメッセージソースがある場合はtrueを返します。
     * @throws IllegalArgumentException resourceBundle is null.
     */
    public boolean removeResourceBundle(final ResourceBundle resourceBundle) {
        Objects.requireNonNull(resourceBundle, "resourceBundle should not be null.");
        
        if(!messageBundles.contains(resourceBundle)) {
            return false;
        }
        
        messageBundles.remove(resourceBundle);
        return true;
    }
}